module IntegerSmithNormalForm

export SNF!, SNF, SNFWithoutTransform

"""
    (S,B,T) = negateRow(A,i)

  Negate the i-th row of the integer matrix A such that B = SAT holds true
"""
function negateRow(A::AbstractArray{I,2},i) where I
  S = eye(I,size(A,1))
  T = eye(I,size(A,2))
  S[i,i] = -1
  B = S*A*T
  (S,B,T)
end

"""
    (S,B,T) = negateCol(A,i)

  Negate the i-th column of the integer matrix A such that B = SAT holds true
"""
function negateCol(A::AbstractArray{I,2},i) where I
  (S,B,T) = negateRow(A',i)
  (T',B',S')
end

"""
    (S,B,T) = swapRows(A,i,j)

  Negate the i-th and j-th row of the integer matrix A such that B = SAT holds true
"""
function swapRows(A::AbstractArray{I,2},i,j) where I
  S = eye(I,size(A,1))
  T = eye(I,size(A,2))

  S[i,i] = 0
  S[j,j] = 0
  S[i,j] = 1
  S[j,i] = 1

  B = S*A*T
  (S,B,T)
end

"""
    (S,B,T) = swapCols(A,i,j)

  Negate the i-th and j-th column of the integer matrix A such that B = SAT holds true
"""
function swapCols(A::AbstractArray{I,2},i,j) where I
  (S,B,T) = swapRows(A',i,j)
  (T',B',S')
end

"""
    (S,B,T) = addRow(A,i,j,a)

  Add the j-th row a times to the i-th row of the integer matrix A such that B = SAT holds true
"""
function addRow(A::AbstractArray{I,2},i,j,a::I) where I
  S = eye(I,size(A,1))
  T = eye(I,size(A,2))

  S[i,j] = a

  B = S*A*T
  (S,B,T)
end

"""
    (S,B,T) = addCol(A,i,j,a)

  Add the j-th column a times to the i-th column of the integer matrix A such that B = SAT holds true
"""
function addCol(A::AbstractArray{I,2},i,j,a::I) where I
  (S,B,T) = addRow(A',i,j,a)
  (T',B',S')
end

"""
    (S,B,T) = SNF!(A)

Computes the Smith normal form according to
https://www.charite.de/sysbio/people/hoppe/Diplomarbeit_Hoppe.pdf
Algorithmus 1

The matrices fulfill:
* B is a diagonal matrix
* B[i,i] >= 0 for all i
* B[i,i] divides all B[j,j] for all j > i
"""
function SNF!(A::AbstractArray{I,2}) where {I<:Integer}
  d1 = size(A,1)
  d2 = size(A,2)

  S = eye(I,d1)
  T = eye(I,d2)

  for subMatrixInd in 1:(min(d1,d2))
    ASub = A[subMatrixInd:end,subMatrixInd:end]
    d1Sub = size(ASub,1)
    d2Sub = size(ASub,2)

    SSub = eye(I,d1Sub)
    TSub = eye(I,d2Sub)

    nextPivot = findnext(ASub,1)

    if nextPivot != 0

      pivot = ind2sub(size(ASub),nextPivot)

      #Step 2: Put pivot to (1,1)-position
      (S_,ASub,T_) = swapRows(ASub,1,pivot[1])
      SSub = S_ * SSub
      TSub = TSub * T_
      (S_,ASub,T_) = swapCols(ASub,1,pivot[2])
      SSub = S_ * SSub
      TSub = TSub * T_

      divideMatrix = false
      while !divideMatrix
        #check if ASub[1,1] divides all elements in the
        #column and row
        divideCol = false
        divideRow = false
        while !(divideRow & divideCol)
          divideCol=true
          #check if ASub[1,1] divides all elements in its column
          if d1Sub > 1
            for i in 2:d1Sub
              #does not divide
              if mod.(ASub[i,1],ASub[1,1]) != 0
                divideCol=false
                q = div(ASub[i,1],ASub[1,1])
                #Add row 1 q-times to row i
                (S_,ASub,T_) = addRow(ASub,i,1,-q)
                SSub = S_ * SSub
                TSub = TSub * T_
                #Make (1,i) the new pivot on position (1,1)
                (S_,ASub,T_) = swapRows(ASub,1,i)
                SSub = S_ * SSub
                TSub = TSub * T_
                break
              end
            end
            divideCol || break
          end

          divideRow=true
          #check if ASub[1,1] divides all elements in its row
          if d2Sub > 1
            for i in 1:d2Sub
              #does not divide
              if mod.(ASub[1,i],ASub[1,1]) != 0
                divideRow=false
                q = div(ASub[1,i],ASub[1,1])
                #Add col 1 q-times to col i
                (S_,ASub,T_) = addCol(ASub,i,1,-q)
                SSub = S_ * SSub
                TSub = TSub * T_
                #Make (1,i) the new pivot on position (1,1)
                (S_,ASub,T_) = swapCols(ASub,1,i)
                SSub = S_ * SSub
                TSub = TSub * T_
                break
              end
            end
          end
        end

        #eliminate all entries in the pivot row and column
        if d1Sub > 1
          for i = 2:d1Sub
            q = div(ASub[i,1],ASub[1,1])
            (S_,ASub,T_) = addRow(ASub,i,1,-q)
            SSub = S_ * SSub
            TSub = TSub * T_
          end
        end

        if d2Sub > 1
          for i = 2:d2Sub
            q = div(ASub[1,i],ASub[1,1])
            (S_,ASub,T_) = addCol(ASub,i,1,-q)
            SSub = S_ * SSub
            TSub = TSub * T_
          end
        end

        #check if the pivot element divides all elements of the matrix
        divideMatrix = mod.(ASub,ASub[1,1]) == zeros(size(ASub))

        if !divideMatrix
          #Add row of non-dividing index to pivot row
          nonDividing = ind2sub(size(ASub),findnext(mod.(ASub,ASub[1,1]),1))
          (S_,ASub,T_) = addRow(ASub,1,nonDividing[1],1)
          SSub = S_ * SSub
          TSub = TSub * T_
        end
      end

      S_ = eye(I,d1)
      T_ = eye(I,d2)
      S_[subMatrixInd:end,subMatrixInd:end] = SSub
      T_[subMatrixInd:end,subMatrixInd:end] = TSub
      S = S_ * S
      T = T * T_
      A[subMatrixInd:end,subMatrixInd:end] = ASub
    end
  end

  #make diagonal element positive
  for i in 1:min(d1,d2)
    if A[i,i] < 0
      (S_,A,T_) = negateRow(A,i)
      S = S_ * S
      T = T * T_
    end
  end

  (S,A,T)
end

"""
    (S,B,T) = SNF(A)

Computes the Smith normal form according to
https://www.charite.de/sysbio/people/hoppe/Diplomarbeit_Hoppe.pdf
Algorithmus 1

The matrices fulfill:
* B = SAT
* B is a diagonal matrix
* B[i,i] >= 0 for all i
* B[i,i] divides all B[j,j] for all j > i
"""
function SNF(A)
  B = copy(A)
  SNF!(B)
end

"""
    B = SNFWithoutTransform(A)

Computes the Smith normal form according to
https://www.charite.de/sysbio/people/hoppe/Diplomarbeit_Hoppe.pdf
Algorithmus 1

The matrix fulfills:
* There are matrices S,T with |det(S)| = |det(T)| = 1 such that B = SAT
* B is a diagonal matrix
* B[i,i] >= 0 for all i
* B[i,i] divides all B[j,j] for all j > i
"""
function SNFWithoutTransform(A)
  (S,B,T) = SNF(A)
  B
end

end # module


using StaticArrays

struct Sphere{T}
    midpoint::SVector{3,T}
    r::T
end

struct SphereGenerator{Fr}
    r::Fr
end

function (g::SphereGenerator)(midpoint, rot)
    Sphere(midpoint, g.r())
end

volume(shape::Sphere) = 4/3 * π * shape.r^3

function set_midpoint(shape::Sphere, midpoint)
    return Sphere(midpoint, t.r)
end

function bounding_box(shape::Sphere)
    lower = shape.midpoint .- shape.r
    upper = shape.midpoint .+ shape.r
    return lower, upper
end

function inside(shape::Sphere, p0)
    p = p0 - shape.midpoint
    return shape.r^2 < sum(p.^2)
end


#workspace()

using Test

#Arithmetic library imports
using IntervalArithmetic
using IntervalContractors

#Preamble
setprecision(53)
setprecision(Interval, Float64)

# Using approximate checks for validaty update later?
import Base.isapprox
isapprox(x::Interval,y::Interval) = isapprox(x.lo,y.lo,atol=1E-4) && isapprox(x.hi,y.hi,atol=1E-4)

c = ∅
d = entireinterval(Float64)
a,b = exp2_rev(c, d)


"""
    MonteCarloModel(core, dates, paths)

A `MonteCarloModel` is the result of a simulation of a series of asset prices.

* `core`: a reference `CoreModel`
* `dates`: an `AbstractVector{Date}`
* `paths`: a matrix of the scenario paths: the rows are the scenarios, and the columns are the values at each date in `dates`.
"""
struct MonteCarloModel{C,D,T} <: AbstractModel
    core::C
    dates::D
    paths::Matrix{T}
end

"""
    MonteCarloScenario(core, dates, path)

A `MonteCarloScenario` is a single simulation scenario of a `MonteCarloModel`.

* `core`: a reference `CoreModel`
* `dates`: an `AbstractVector{Date}`
* `paths`: an `AbstractVector` of the values at each date in `dates`.
"""
struct MonteCarloScenario{C,D,S} <: AbstractModel
    core::C
    dates::D
    path::S
end

numeraire(m::Union{MonteCarloModel, MonteCarloScenario}) = numeraire(m.core)
startdate(m::Union{MonteCarloModel, MonteCarloScenario}) = startdate(m.core)

yearfractionto(m::Union{MonteCarloModel, MonteCarloScenario}, dt::Date) =
    yearfractionto(m.core,  dt)

# the value of currency is the same under every scenario
value(m::MonteCarloModel, c::WhenAt{Receive{T}}) where {T} =
    value(m.core,c)
value(m::MonteCarloScenario, c::WhenAt{Receive{T}}) where {T} =
    value(m.core,c)

struct ScenarioIterator{M<:MonteCarloModel}
    m::M
    n::Int
end

"""
    scenarios(m::MonteCarloModel)

Returns an iterator over each `MonteCarloScenario` in `m`.
"""
scenarios(m::MonteCarloModel) = ScenarioIterator(m, size(m.paths, 1))

Base.length(sc::ScenarioIterator) = sc.n
Base.iterate(sc::ScenarioIterator, i::Int=1) = i > sc.n ? nothing :
    (MonteCarloScenario(sc.m.core, sc.m.dates, view(sc.m.paths, i, :)), i+1)

"""
    date2index(m::Union{MonteCarloScenario, MonteCarloModel}, dt::Date)

Returns the index of `dt` in the path(s) of `m`.
"""
function date2index(m::Union{MonteCarloScenario, MonteCarloModel}, dt::Date)
    ii = searchsorted(m.dates, dt)
    isempty(ii) && throw(DomainError(dt))
    return ii[1]
end

index2date(m::Union{MonteCarloScenario, MonteCarloModel}, i) = m.dates[i]

function forwardprice(m::MonteCarloScenario, s::SingleStock, dt::Date)
    valueat(m,s,date2index(m,dt))
end
function forwardprice(m::MonteCarloModel, s::SingleStock, dt::Date)
    mean(forwardprice(ms, s, dt) for ms in scenarios(m))
end

valueat(m::MonteCarloScenario, ::SingleStock, i::Int) = m.path[i]
valueat(m::MonteCarloScenario, ::SingleStock, i::Int, ::Type{Dual}) =
    Dual(m.path[i], 1)

# fallback
function value(m::MonteCarloModel, c::WhenAt)
    N = date2index(m, maturitydate(c))
    discount(m.core.yieldcurve, maturitydate(c)) * mean(valueat(ms, c.c, N) for ms in scenarios(m))
end

"""
    montecarlo(m::GeomBMModel, dates, npaths)

Sample `npaths` Monte Carlo paths of the model `m`, at time `dates`.
"""
function montecarlo(m::GeomBMModel{CoreModel{T,R,Q}, V}, dates::StepRange{Date}, npaths::Integer) where {T,R,Q,V}
    σ = m.volatility
    S = typeof(m.core.yieldcurve.rate)
    Xt = Array{promote_type(T,V,S)}(undef, length(dates), npaths)
    Δt = yearfraction(daycount(m.core.yieldcurve), step(dates))
    df = discount(m.core.carrycurve, Δt) / discount(m.core.yieldcurve, Δt)
    for i = 1:npaths
        x = value(m, SingleStock())
        for (j, dt) in enumerate(dates)
            if j == 1
                Δt1 = yearfraction(daycount(m.core.yieldcurve), startdate(m), first(dates))
                df1 = Δt1 == 0 ? 1.0 : discount(m.core.carrycurve, Δt1) / discount(m.core.yieldcurve, Δt1)
                x *= df1 * exp(-σ^2*Δt1/2 + σ*sqrt(Δt1)*randn())
            else
                x *= df * exp(-σ^2*Δt/2 + σ*sqrt(Δt)*randn())
            end
            Xt[j,i] = x
        end
    end
    MonteCarloModel(m.core, dates, copy(transpose(Xt)))
end

function value(m::GeomBMModel, c::Contract, ::Type{MonteCarloModel}, dates::StepRange{Date}, npaths::Integer)
    mcm = montecarlo(m, dates, npaths)
    value(mcm, c)
end

value(m::GeomBMModel, c::Contract, ::Type{MonteCarloModel}, npaths::Integer) =
    value(m, c, MonteCarloModel, startdate(m):Day(1):maturitydate(c), npaths)


using FileIO
using DelimitedFiles

# Load the dataset and create a matrix with the inputs and a vector for the targets
dataset = DelimitedFiles.readdlm("datasets/561_cpu.tsv");
inputs  = Float64.(dataset[2:end, 1:end-1]);
targets = Float64.(dataset[2:end, end]);

# Load the DoME system
include("DoME.jl");
# Run DoME with these parameters
(trainingMSE, validationMSE, testMSE, bestTree) = dome(inputs, targets;
   minimumReductionMSE = 1e-6,
   maximumNodes = 30,
   strategy = StrategyExhaustive,
   showText = true
);
# Write the expression on screen
println("Best expression found: ", string(bestTree));
println("Best expression found (written in Latex): ", latexString(bestTree));
# If you want to rename the variable names, one of the easiest way is to do something like:
expression = string(bestTree);
expression = replace(expression, "X1" => "vendor");
expression = replace(expression, "X2" => "MYCT");
expression = replace(expression, "X3" => "MMIN");
expression = replace(expression, "X4" => "MMAX");
expression = replace(expression, "X5" => "CACH");
expression = replace(expression, "X6" => "CHMIN");
expression = replace(expression, "X7" => "CHMAX");
println("Best expression found (with the real names of the variables): ", expression);


using Clustering, Distances

type kMeansModel <: DischargeModel
    M::Int
    k::Int
    λ::Real
    centers::Matrix{Float64}
    β::Matrix{Float64}
    H::Matrix{Float64}
    Q::Vector{Float64}
end

kMeansModel(M,N,k=1,λ=0.0) = kMeansModel(M,k,λ,zeros(M,k),zeros(M,k),zeros(M,N),zeros(N))

function clusters(M::kMeansModel,H)
    D = pairwise(Euclidean(),H,M.centers)
    map(x->ind2sub(size(D),x)[2],findmin(D,2)[2])
end

function StatsBase.fit(M::kMeansModel,H::Matrix{Float64},Q::Vector{Float64})
    km = kmeans(H,M.k,init=:kmcen,tol=1e-32)
    β = zeros(M.M,M.k)
    for i in 1:M.k
        Hi = H[:,assignments(km).==i]
        Qi = Q[assignments(km).==i]
        β[:,i] = (Hi*Hi'+M.λ*I)\(Hi*Qi)
    end
    kMeansModel(M.M,M.k,M.λ,km.centers,β,H,Q)
end

function StatsBase.fit!(M::kMeansModel,H::Matrix{Float64},Q::Vector{Float64})
    km = kmeans(H,M.k,init=:kmcen,tol=1e-32)
    M.centers = km.centers
    β = zeros(M.M,M.k)
    for i in 1:M.k
        Hi = H[:,assignments(km).==i]
        Qi = Q[assignments(km).==i]
        β[:,i] = (Hi*Hi'+M.λ*I)\(Hi*Qi)
    end
    M.β = β
    M.H = H
    M.Q = Q
    M
end

StatsBase.coef(M::kMeansModel) = M.β
StatsBase.nobs(M::kMeansModel) = length(M.Q)
StatsBase.model_response(M::kMeansModel) = M.Q

function StatsBase.predict(M::kMeansModel,H)
    clus = clusters(M,H)
    Q = zeros(size(H,2))
    for i in 1:length(Q)
        Q[i] = (H[:,i]'M.β[:,clus[i]])[1]
    end
    Q
end

StatsBase.predict(M::kMeansModel) = predict(M,M.H)

StatsBase.residuals(M::kMeansModel) = M.Q-predict(M)
StatsBase.residuals(M::kMeansModel,H,Q) = Q-predict(M,H)


export trouve_intervalleASecA
function trouve_intervalleASecA(h :: LineModel,
                                t₀ :: Float64,
                                tₘ :: Float64;
                                ϵ :: Float64=1e-10,
                                verbose :: Bool=false)

        h₀=obj(h,t₀)
        dh₀=grad(h,t₀)

        γ=0.8
        t=tₘ
        tp=t₀
        tqnp=t₀
        hk=0
        hkm1=0
        gkm1=0
        hp=0
        i=0

        hk=obj(h,t)
        gk=grad(h,t)
        hkm1=obj(h,tqnp)
        gkm1=grad(h,tqnp)

        verbose && @printf("iter tp        gkm1        hkm1         t        gk        hk\n")
        verbose && @printf("%4d %7.2e %7.2e  %7.2e  %7.2e  %7.2e  %7.2e \n", i, tp,gkm1,hkm1,t,gk,hk)

        while  i<50
          if (hk>h₀+0.01*(t-t₀)*dh₀) ||((hk>hkm1) & (i>1))
            (topt,iter)=zoom_SecA(h,tp,t,verbose=false)
            return (topt,iter)
          end

          if (abs(gk)<=ϵ)
            iter=i
            topt=t
            return (topt,iter)
          end

          if (gk>=0)
            (topt,iter)=zoom_SecA(h,t,tp,verbose=false)
            return (topt,iter)
         end

          s = t-tqnp
          y = gk-gkm1

          Γ=3*(gk+gkm1)*s-6*(hk-hkm1)
          if y*s+Γ < eps(Float64)*(s^2)
            yt=y
          else
            yt=y+Γ/s
          end

          dS=-gk*s/yt

          if ((tp-t)*dS>0) & (dS/(tp-t)<γ)
            tplus = t + dS
            hplus = obj(h, tplus)
            gplus = grad(h,tplus)
            verbose && println("N")
          else
            tplus = (t+tp)/2
            hplus = obj(h, tplus)
            gplus = grad(h,tplus)
            verbose && println("B")
          end

          if t>tp
            if gplus<0
              tp=t
              tqnp=t
              t=tplus
            else
              tqnp=t
              t=tplus
            end
          else
            if gplus>0
              tp=t
              tqnp=t
              t=tplus
            else
              tqnp=t
              t=tplus
            end
          end

          #mise à jour des valeurs
          hkm1=hk
          gkm1=gk
          hk=hplus
          gk=gplus
          i=i+1
          verbose && @printf("%4d %7.2e %7.2e  %7.2e  %7.2e  %7.2e  %7.2e \n", i, tim1,gkm1,hkm1,ti,gk,hk)
        end
end


#
# InterpolationKernels.jl --
#
# Kernel functions used for linear filtering, windowing or linear
# interpolation.
#
#------------------------------------------------------------------------------
#
# This file is part of the InterpolationKernels package licensed under the MIT
# "Expat" License.
#
# Copyright (C) 2016-2019, Éric Thiébaut.
#

module InterpolationKernels

export
    Boundaries,
    CardinalCubicSpline,
    CardinalCubicSplinePrime,
    CatmullRomSpline,
    CatmullRomSplinePrime,
    CubicSpline,
    CubicSplinePrime,
    Flat,
    Kernel,
    KeysSpline,
    KeysSplinePrime,
    LanczosKernel,
    LanczosKernelPrime,
    LinearSpline,
    LinearSplinePrime,
    MitchellNetravaliSpline,
    MitchellNetravaliSplinePrime,
    Periodic,
    QuadraticSpline,
    QuadraticSplinePrime,
    RectangularSpline,
    RectangularSplinePrime,
    Reflect,
    SafeFlat,
    boundaries,
    getweights,
    iscardinal,
    isnormalized,
    brief

import Base: convert, adjoint
using Printf
using InteractiveUtils

#------------------------------------------------------------------------------
# EXTRAPOLATION METHODS

"""
All extrapolation methods (a.k.a. boundary conditions) are singletons and
inherit from the abstract type `Boundaries`.
"""
abstract type Boundaries end

struct Flat     <: Boundaries; end
struct SafeFlat <: Boundaries; end
struct Periodic <: Boundaries; end
struct Reflect  <: Boundaries; end

#------------------------------------------------------------------------------
# INTERPOLATION KERNELS

@inline frac(::Type{T}, num::Integer, den::Integer) where {T<:AbstractFloat} =
    (T(num)/T(den))

@inline square(x) = x*x
@inline cube(x) = x*x*x

@inline signabs(x::Real) = ifelse(x < 0, (oftype(one(x),-1), -x), (one(x), x))

"""
# Interpolation Kernels

An interpolation kernel `Kernel{T,S,B}` is parametrized by the
floating-point type `T` of its coefficients, by the size `S` of its support
and by the boundary conditions `B` to apply for extrapolation.  For
efficiency reasons, only kernels with (small) finite size supports are
implemented.

A kernel may be used as a function wit a real argument:

    ker(x::Real)

yields kernel value at offset `x`.  All kernel supports are symmetric; that is
`ker(x)` is zero if `abs(x) > S/2`.

## Kernel conversion

The argument of a kernel be a floating-point type and/or a boundary conditions
type:

    ker(::Type{T}, ::Type{B}) where {T<:AbstractFloat, B<:Boundaries}

to convert the kernel to operate with given floating-point type `T` and use
boundary conditions `B` (any of which can be omitted and their order is
irrelevant).  Beware that changing the floating-point type may lead to a
loss of precision if the new floating-point type has more digits).

It is possible to change the floating-point type of a kernel or its boundary
conditions by something like:

```julia
Float32(ker)    # change floating-point type of kernel `ker`
SafeFlat(ker)   # change boundary conditions of kernel `ker`
```

## Available methods

The following methods are available for any interpolation kernel `ker`:

```julia
eltype(ker) -> T
```

yields the floating-point type for calculations,

```julia
length(ker) -> S
size(ker)   -> (S,)
```

yield the size the support of `ker` which is also the number of neighbors
involved in an interpolation by this kernel,

```julia
boundaries(ker) -> B
```

yields the type of the boundary conditions applied for extrapolation; finally:

```julia
getweights(ker, t) -> w1, w2, ..., wS
```

yields the `S` interpolation weights for offset `t`

```
t = x - floor(x)        if s is even
    x - round(x)        if s is odd
```

 `t ∈ [0,1]` if `S` is even or
for offset `t ∈ [-1/2,+1/2]` if `S` is odd.

"""
abstract type Kernel{T<:AbstractFloat,S,B<:Boundaries} <: Function end

Base.eltype(::Kernel{T,S,B})         where {T,S,B} = T
Base.eltype(::Type{<:Kernel{T,S,B}}) where {T,S,B} = T
Base.length(::Kernel{T,S,B})         where {T,S,B} = S
Base.length(::Type{<:Kernel{T,S,B}}) where {T,S,B} = S
Base.size(::Kernel{T,S,B})           where {T,S,B} = (S,)
Base.size(::Type{<:Kernel{T,S,B}})   where {T,S,B} = (S,)

"""
`boundaries(ker)` yields the type of the boundary conditions applied for
extrapolation with kernel `ker`.
"""
boundaries(::Kernel{T,S,B})         where {T,S,B} = B
boundaries(::Type{<:Kernel{T,S,B}}) where {T,S,B} = B

"""
`isnormalized(ker)` returns a boolean indicating whether the kernel `ker` has
the partition of unity property.  That is, the sum of the values computed by
the kernel `ker` on a unit spaced grid is equal to one.
"""
function isnormalized end

"""
`iscardinal(ker)` returns a boolean indicating whether the kernel `ker`
is zero for non-zero integer arguments.
"""
function iscardinal end

"""
```julia
getweights(ker, t) -> w1, w2, ..., wS
```

yields the interpolation weights for the `S` neighbors of a position `x`.
Offset `t` between `x` and the nearest neighbor is in the range `[0,1]` for `S`
even and `[-1/2,+1/2]` for `S` odd.

"""
function getweights end

#------------------------------------------------------------------------------
"""
# Rectangular Spline

The rectangular spline (also known as box kernel or Fourier window or Dirichlet
window) is the 1st order (constant) B-spline equals to `1` on `[-1/2,+1/2)`,
and `0` elsewhere.

A rectangular spline instance is created by:

```julia
RectangularSpline([T=Float64,] B=Flat)
```

Its derivative is created by:

```julia
RectangularSplinePrime([T=Float64,] B=Flat)
```

"""
struct RectangularSpline{T,B} <: Kernel{T,1,B}; end
struct RectangularSplinePrime{T,B} <: Kernel{T,1,B}; end
@doc @doc(RectangularSpline) RectangularSplinePrime

iscardinal(::Union{K,Type{K}}) where {K<:RectangularSpline} = true
iscardinal(::Union{K,Type{K}}) where {K<:RectangularSplinePrime} = false

isnormalized(::Union{K,Type{K}}) where {K<:RectangularSpline} = true
isnormalized(::Union{K,Type{K}}) where {K<:RectangularSplinePrime} = false

Base.show(io::IO, ::RectangularSpline) = print(io, "RectangularSpline()")
Base.show(io::IO, ::RectangularSplinePrime) = print(io, "RectangularSplinePrime()")

(::RectangularSpline{T,B})(x::T) where {T,B} =
    frac(T,-1,2) ≤ x < frac(T,1,2) ? one(T) : zero(T)

@inline getweights(::RectangularSpline{T,B}, t::T) where {T,B} = one(T)

(::RectangularSplinePrime{T,B})(x::T) where {T,B} = zero(T)

@inline getweights(::RectangularSplinePrime{T,B}, t::T) where {T,B} = zero(T)

#------------------------------------------------------------------------------
"""
# Linear Spline

The linear spline (also known as triangle kernel or Bartlett window or Fejér
window) is the 2nd order (linear) B-spline.

A linear spline instance is created by:

```julia
LinearSpline([T=Float64,] B=Flat)
```

Its derivative is created by:

```julia
LinearSplinePrime([T=Float64,] B=Flat)
```

"""
struct LinearSpline{T,B} <: Kernel{T,2,B}; end
struct LinearSplinePrime{T,B} <: Kernel{T,2,B}; end
@doc @doc(LinearSpline) LinearSplinePrime

iscardinal(::Union{K,Type{K}}) where {K<:LinearSpline} = true
iscardinal(::Union{K,Type{K}}) where {K<:LinearSplinePrime} = false

isnormalized(::Union{K,Type{K}}) where {K<:LinearSpline} = true
isnormalized(::Union{K,Type{K}}) where {K<:LinearSplinePrime} = false

Base.show(io::IO, ::LinearSpline) = print(io, "LinearSpline()")
Base.show(io::IO, ::LinearSplinePrime) = print(io, "LinearSplinePrime()")

(::LinearSpline{T,B})(x::T) where {T<:AbstractFloat,B} =
    (a = abs(x); a < 1 ? 1 - a : zero(T))

@inline getweights(::LinearSpline{T,B}, t::T) where {T<:AbstractFloat,B} =
    (1 - t, t)

# The derivative of the linear B-spline must be non-symmetric for tests to
# succeed.  In particular we want that interpolating with the derivative of the
# linear B-spline amounts to taking the finite difference when 0 ≤ t < 1.
# This implies that f'(x) = 1 for x ∈ [-1,0), f'(x) = -1 for x ∈ [0,1), and
# f'(x) = 0 elsewhere.
(::LinearSplinePrime{T,B})(x::T) where {T<:AbstractFloat,B} =
    -1 ≤ x < 1 ? (x < 0 ? one(T) : -one(T)) : zero(T)

@inline getweights(::LinearSplinePrime{T,B}, t::T) where {T<:AbstractFloat,B} =
    (-one(T), one(T))

#------------------------------------------------------------------------------
"""
# Quadratic Spline

The quadratic spline is the 3rd order (quadratic) B-spline.

A quadratic spline instance is created by:

```julia
QuadraticSpline([T=Float64,] B=Flat)
```

Its derivative is created by:

```julia
QuadraticSplinePrime([T=Float64,] B=Flat)
```

"""
struct QuadraticSpline{T,B} <: Kernel{T,3,B}; end
struct QuadraticSplinePrime{T,B} <: Kernel{T,3,B}; end
@doc @doc(QuadraticSpline) QuadraticSplinePrime

iscardinal(::Union{K,Type{K}}) where {K<:QuadraticSpline} = false
iscardinal(::Union{K,Type{K}}) where {K<:QuadraticSplinePrime} = false

isnormalized(::Union{K,Type{K}}) where {K<:QuadraticSpline} = true
isnormalized(::Union{K,Type{K}}) where {K<:QuadraticSplinePrime} = false

Base.show(io::IO, ::QuadraticSpline) = print(io, "QuadraticSpline()")
Base.show(io::IO, ::QuadraticSplinePrime) = print(io, "QuadraticSplinePrime()")

function (::QuadraticSpline{T,B})(x::T) where {T<:AbstractFloat,B<:Boundaries}
    a = abs(x)
    return (a ≥ frac(T,3,2) ? zero(T) :
            a ≤ frac(T,1,2) ? frac(T,3,4) - a*a :
            square(a - frac(T,3,2))*frac(T,1,2))
end

@static if false
    # Compute quadratic B-spline weights in 8 operations.
    @inline function getweights(::QuadraticSpline{T,B},
                                t::T) where {T<:AbstractFloat,B}
        # w1 = (1/8)*(1 - 2*t)^2 = (1/2)*((1/2) - t)^2
        # w2 = (3/4) - t^2
        # w3 = (1/8)*(1 + 2*t)^2 = (1/2)*((1/2) + t)^2
        h = frac(T,1,2)
        return (h*square(h - t), frac(T,3,4) - t*t, h*square(h + t))
    end
else
    # Same result, but with 7 operations.
    @inline function getweights(::QuadraticSpline{T,B},
                                t::T) where {T<:AbstractFloat,B}
        # c1 = 1/sqrt(8)
        c1 = T(0.35355339059327376220042218105242451964241796884424)
        # c2 = 2/sqrt(8)
        c2 = T(0.70710678118654752440084436210484903928483593768847)
        # c2 = 3/4
        c3 = frac(T,3,4)
        c2t = c2*t
        q1 = c1 - c2t
        q3 = c1 + c2t
        return (q1*q1, c3 - t*t, q3*q3)
    end
end

(::QuadraticSplinePrime{T,B})(x::T) where {T,B} =
    frac(T,-3,2) < x < frac(T,3,2) ? (
        x < frac(T,-1,2) ? x + frac(T,3,2) :
        x ≤ frac(T,1,2) ? -2x : x - frac(T,3,2)
    ) : zero(T)

@inline function getweights(::QuadraticSplinePrime{T,B},
                            t::T) where {T<:AbstractFloat,B}
    h = frac(T,1,2)
    return (t - h, -2t, t + h)
end

#------------------------------------------------------------------------------
"""
# Cubic Spline

    CubicSpline([T=Float64,] B=Flat)

where `T <: AbstractFloat` and `B <: Boundaries` yields a cubic spline kernel
which operates with floating-point type `T` and use boundary conditions `B`
(any of which can be omitted and their order is irrelevant).

The 4th order (cubic) B-spline kernel is also known as Parzen window or de la
Vallée Poussin window.

Its derivative is given by:

```julia
CubicSplinePrime([T=Float64,] B=Flat)
```

"""
struct CubicSpline{T,B} <: Kernel{T,4,B}; end
struct CubicSplinePrime{T,B} <: Kernel{T,4,B}; end
@doc @doc(CubicSpline) CubicSplinePrime

iscardinal(::Union{K,Type{K}}) where {K<:CubicSpline} = false
iscardinal(::Union{K,Type{K}}) where {K<:CubicSplinePrime} = false

isnormalized(::Union{K,Type{K}}) where {K<:CubicSpline} = true
isnormalized(::Union{K,Type{K}}) where {K<:CubicSplinePrime} = false

Base.show(io::IO, ::CubicSpline) = print(io, "CubicSpline()")
Base.show(io::IO, ::CubicSplinePrime) = print(io, "CubicSplinePrime()")

function (::CubicSpline{T,B})(x::T) where {T,B}
    a = abs(x)
    return (a ≥ 2 ? zero(T) :
            a ≥ 1 ? cube(2 - a)*frac(T,1,6) :
            (frac(T,1,2)*a - 1)*a*a + frac(T,2,3))
end

@inline function getweights(ker::CubicSpline{T,B},
                            t::T) where {T,B}
    # The weights are:
    #     w1 = 1/6 - t/2 + t^2/2 - t^3/6
    #        = 1/6 + (t^2 - t)/2 - t^3/6
    #        = (1 - t)^3/6
    #     w2 = 2/3 - t^2 + t^3/2
    #        = 2/3 + (t/2 - 1)*t^2
    #     w3 = 1/6 + t/2 + t^2/2 - t^3/2
    #        = 1/6 + (t + t^2 - t^3)/2
    #        = 1/6 - ((t - 1)*t - 1)*t/2
    #        = 4/6 - (1 - t)^2*(t + 1)/2
    #     w4 = t^3/6
    #
    # Horner's scheme takes 6 operations per cubic polynomial, 24 operations
    # for the 4 weights.  Precomputing the powers of t, t^2 and t^3, takes 2
    # operations, then 6 operations per cubic polynomial are needed.
    #
    # Using factorizations, I manage to only use 15 operations.
    h = frac(T,1,2)
    p = frac(T,2,3)
    q = frac(T,1,6)
    r = 1 - t
    r2 = r*r
    t2 = t*t
    w1 = q*r2*r
    w2 = p + (h*t - 1)*t2
    w3 = p - h*r2*(t + 1)
    w4 = q*t2*t
    return w1, w2, w3, w4
end

(::CubicSplinePrime{T,B})(x::T) where {T,B} =
    -2 < x < 2 ? (
        x < -1 ? frac(T,1,2)*square(x + 2) :
        x <  0 ? frac(T,-3,2)*x*(x + frac(T,4,3)) :
        x <  1 ? frac(T,+3,2)*x*(x - frac(T,4,3)) :
        frac(T,-1,2)*square(x - 2)
    ) : zero(T)

@inline function getweights(ker::CubicSplinePrime{T,B},
                            t::T) where {T,B}
    return (frac(T,-1,2)*(t - 1)^2,
            frac(T, 3,2)*(t - frac(T,4,3))*t,
            frac(T, 1,2) + (1 - frac(T,3,2)*t)*t,
            frac(T, 1,2)*t^2)
end

#------------------------------------------------------------------------------
# Catmull-Rom kernel is a special case of Mitchell & Netravali kernel.

"""
```julia
CatmullRomSpline([T=Float64,] B=Flat) -> ker
```

yields a Catmull-Rom interpolation kernel for floating-point type `T` and
boundary conditions `B`.

Catmull-Rom interpolation kernel is a piecewise cardinal cubic spline defined
by:

```
ker(x) = ((3/2)*|x| - (5/2))*x^2 + 1             if |x| ≤ 1
         (((5/2) - (1/2)*|x|)*|x| - 4)*|x| + 2   if 1 ≤ |x| ≤ 2
         0                                       if |x| ≥ 2
```

It derivative is given by:

```julia
CatmullRomSplinePrime([T=Float64,] B=Flat) -> ker′
```

with:

```
ker′(x) = ((9/2)*|x| - 5)*x                      if a = |x| ≤ 1
          (5 - (3/2)*|x|)*x - 4*sign(x)          if 1 ≤ |x| ≤ 2
          0                                      if |x| ≥ 2
```

"""
struct CatmullRomSpline{T,B} <: Kernel{T,4,B}; end
struct CatmullRomSplinePrime{T,B} <: Kernel{T,4,B}; end
@doc @doc(CatmullRomSpline) CatmullRomSplinePrime

iscardinal(::Union{K,Type{K}}) where {K<:CatmullRomSpline} = true
iscardinal(::Union{K,Type{K}}) where {K<:CatmullRomSplinePrime} = false

isnormalized(::Union{K,Type{K}}) where {K<:CatmullRomSpline} = true
isnormalized(::Union{K,Type{K}}) where {K<:CatmullRomSplinePrime} = false

Base.summary(::CatmullRomSpline) = "CatmullRomSpline()"
Base.summary(::CatmullRomSplinePrime) = "CatmullRomSplinePrime()"

@inline function (::CatmullRomSpline{T,B})(x::T) where {T<:AbstractFloat,B}
    a = abs(x)
    return (a ≥ 2 ? zero(T) :
            a ≤ 1 ? (frac(T,3,2)*a - frac(T,5,2))*a*a + T(1) :
            ((frac(T,5,2) - frac(T,1,2)*a)*a - T(4))*a + T(2))
end

@inline function (::CatmullRomSplinePrime{T,B})(x::T) where {T<:AbstractFloat,B}
    return (x < 0 ?
            (x ≤ -2 ? zero(T) :
             x ≥ -1 ? (frac(T,-9,2)*x - T(5))*x :
             (T(5) + frac(T,3,2)*x)*x + T(4)) :
            (x ≥ 2 ? zero(T) :
             x ≤ 1 ? (frac(T,9,2)*x - T(5))*x :
             (T(5) - frac(T,3,2)*x)*x - T(4)))
end

@inline function getweights(::CatmullRomSpline{T,B},
                            t::T) where {T<:AbstractFloat,B}
    # 10 operations
    s = 1 - t
    q = frac(T,-1,2)*t*s
    w1 = q*s
    w4 = q*t
    r = w4 - w1
    w2 = s - w1 + r
    w3 = t - w4 - r
    return (w1, w2, w3, w4)
end

@inline function getweights(::CatmullRomSplinePrime{T,B},
                            t::T) where {T<:AbstractFloat,B}
    # Weights (18 operations):
    #   w1 = -(3/2)*t^2 + 2*t - (1/2);
    #   w2 =  (9/2)*t^2 - 5*t;
    #   w3 = -(9/2)*t^2 + 4*t + (1/2);
    #   w4 =  (3/2)*t^2 - t;
    #
    #   w4 = ((3/2)*t - 1)*t;
    #   w1 = t - w4 - (1/2);
    #   w2 = 3*w4 - 2*t;
    #   w3 = t - 3*w4 + (1/2);
    #
    # 11 operations:
    w4 = frac(T,3,2)*t*t - t;
    w1 = t - w4 - frac(T,1,2);
    w2 = T(3)*w4 - 2*t;
    w3 = t - T(3)*w4 + frac(T,1,2);
    return (w1, w2, w3, w4)
end

#------------------------------------------------------------------------------
"""
```julia
CardinalCubicSpline([T=Float64,] c, B=Flat) -> ker
```

yields a cardinal cubic spline interpolation kernel for floating-point type `T`
tension parameter `c` and boundary conditions `B`.  The slope at `x = ±1` is
`±(c - 1)/2`.  Usually `c ≤ 1`, choosing `c = 0` yields a Catmull-Rom spline,
`c = 1` yields all zero tangents, `c = -1` yields a truncated approximation of
a cardinal sine, `c = -1/2` yields an interpolating cubic spline with
continuous second derivatives (inside its support).

Its derivative is given by:

```julia
CardinalCubicSplinePrime([T=Float64,] c, B=Flat) -> ker
```

"""
struct CardinalCubicSpline{T,B} <: Kernel{T,4,B}
    c::T # cardinal cubic spline parameter
    p::T # slope at x=1
    q::T
    r::T

    function CardinalCubicSpline{T,B}(c_::Real) where {T,B}
        c = T(c_)
        q = (c + 1)/2
        new{T,B}(c, q - 1, q, q + 1)
    end
end

function CardinalCubicSpline(::Type{T}, c::Real,
                             ::Type{B} = Flat) where {T<:AbstractFloat,
                                                      B<:Boundaries}
    CardinalCubicSpline{T,B}(c)
end

CardinalCubicSpline(c::Real, ::Type{B} = Flat) where {B<:Boundaries} =
    CardinalCubicSpline(Float64, c, B)

iscardinal(::Union{K,Type{K}}) where {K<:CardinalCubicSpline} = true

isnormalized(::Union{K,Type{K}}) where {K<:CardinalCubicSpline} = true

Base.show(io::IO, ker::CardinalCubicSpline) =
    print(io, "CardinalCubicSpline(", @sprintf("%.1f", ker.c), ")")

function convert(::Type{CardinalCubicSpline{T,B}},
                 ker::CardinalCubicSpline) where {T<:AbstractFloat,
                                                  B<:Boundaries}
    CardinalCubicSpline(T, ker.c, B)
end

function (ker::CardinalCubicSpline{T,B})(x::T) where {T<:AbstractFloat,B}
    a = abs(x)
    return (a ≥ 2 ? zero(T) :
            a ≥ 1 ? ker.p*(a - 1)*square(2 - a) :
            ((ker.r*a - 1)*a - 1)*(a - 1))
end

@inline function getweights(ker::CardinalCubicSpline{T,B},
                            t::T) where {T<:AbstractFloat,B}
    p, q = ker.p, ker.q
    # Computation of:
    #     w1 = p t u²
    #     w2 = u + t u² - q t² u
    #     w3 = t + t² u - q t u²
    #     w4 = p t² u
    # with u = 1 - t in 13 operations.
    u = 1 - t
    tu = t*u
    ptu = p*tu
    return (ptu*u,
            (u - q*t)*tu + u,
            (t - q*u)*tu + t,
            ptu*t)
end

# First derivative of the cardinal cubic spline.

struct CardinalCubicSplinePrime{T,B} <: Kernel{T,4,B}
    c::T
    p::T
    q::T
    r::T
    s::T

    function CardinalCubicSplinePrime{T,B}(c_::Real) where {T,B}
        c = convert(T, c_)
        t = 3c + 9
        return new{T,B}(c,
                        (3c - 3)/2,
                        t/2,
                        (2c + 10)/t,
                        (c - 1)/t)
    end
end

@doc @doc(CardinalCubicSpline) CardinalCubicSplinePrime

function CardinalCubicSplinePrime(::Type{T}, c::Real,
                              ::Type{B} = Flat) where {T<:AbstractFloat,
                                                       B<:Boundaries}
    CardinalCubicSplinePrime{T,B}(c)
end

CardinalCubicSplinePrime(c::Real, ::Type{B} = Flat) where {B<:Boundaries} =
    CardinalCubicSplinePrime(Float64, c, B)

(ker::CardinalCubicSplinePrime{T,B})(x::T) where {T<:AbstractFloat,B} =
    x < 0 ? (
        x ≤ -2 ? zero(T) :
        x < -1 ? -(x + 2)*(x + frac(T,4,3))*ker.p :
        -(x + ker.r)*x*ker.q
    ) : (
        x ≥ 2 ? zero(T) :
        x > 1 ? (x - 2)*(x - frac(T,4,3))*ker.p :
        (x - ker.r)*x*ker.q
    )

iscardinal(ker::CardinalCubicSplinePrime) = (ker.c == 1)

isnormalized(::CardinalCubicSplinePrime) = false

Base.show(io::IO, ker::CardinalCubicSplinePrime) =
    print(io, "CardinalCubicSplinePrime(", @sprintf("%.1f", ker.c), ")")

function convert(::Type{CardinalCubicSplinePrime{T,B}},
                 ker::CardinalCubicSplinePrime) where {T<:AbstractFloat,
                                                   B<:Boundaries}
    CardinalCubicSplinePrime(T, ker.c, B)
end

@inline function getweights(ker::CardinalCubicSplinePrime{T,B},
                            t::T) where {T<:AbstractFloat,B}

    # Computation of:
    #     w1 = p*(t - 1)*(t - 1/3)
    #     w2 = q*(t - r)*t
    #     w3 = q*(t - 1)*(s - t)
    #     w4 = p*t*(2/3 - t)
    # in 13 operations.
    u = t - 1
    return (ker.p*u*(t - frac(T,1,3)),
            ker.q*(t - ker.r)*t,
            ker.q*u*(ker.s - t),
            ker.p*t*(frac(T,2,3) - t))
end

#------------------------------------------------------------------------------
"""
# Mitchell & Netravali Kernels

```julia
MitchellNetravaliSpline([T=Float64,] [b=1/3, c=1/3,] B=Flat) -> ker
```

yields an interpolation kernel of the Mitchell & Netravali family of kernels
for floating-point type `T`, parameters `b` and `c` and boundary conditions
`B`.

These kernels are cubic splines which depends on 2 parameters `b` and `c`.
whatever the values of `(b,c)`, all these kernels are "normalized", symmetric
and their value and first derivative are continuous.

Taking `b = 0` is a sufficient and necessary condition to have cardinal
kernels.  This correspond to Keys's family of kernels.

Using the constraint: `b + 2c = 1` yields a cubic filter with, at least,
quadratic order approximation.

Some specific values of `(b,c)` yield other well known kernels:

    (b,c) = (1,0)     ==> cubic B-spline
    (b,c) = (0,-a)    ==> Keys's cardinal cubics
    (b,c) = (0,1/2)   ==> Catmull-Rom cubics
    (b,c) = (b,0)     ==> Duff's tensioned B-spline
    (b,c) = (1/3,1/3) ==> recommended by Mitchell-Netravali

Reference:

* Mitchell & Netravali, "*Reconstruction Filters in Computer Graphics*",
  in Computer Graphics, Vol. 22, Num. 4 (1988).
  http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf.

"""
struct MitchellNetravaliSpline{T,B} <: Kernel{T,4,B}
    b ::T
    c ::T
    p0::T
    p2::T
    p3::T
    q0::T
    q1::T
    q2::T
    q3::T
    function MitchellNetravaliSpline{T,B}(_b::Real,
                                          _c::Real) where {T,B}
        b, c = T(_b), T(_c)
        new{T,B}(
            b, c,
            (   6 -  2*b       )/6,
            ( -18 + 12*b +  6*c)/6,
            (  12 -  9*b -  6*c)/6,
            (        8*b + 24*c)/6,
            (     - 12*b - 48*c)/6,
            (        6*b + 30*c)/6,
            (     -    b -  6*c)/6)
    end
end

struct MitchellNetravaliSplinePrime{T,B} <: Kernel{T,4,B}
    b ::T
    c ::T
    p1::T
    p2::T
    q0::T
    q1::T
    q2::T
    function MitchellNetravaliSplinePrime{T,B}(_b::Real,
                                               _c::Real) where {T,B}
        b, c = T(_b), T(_c)
        new{T,B}(
            b, c,
            ( -18 + 12*b +  6*c)/3,
            (  12 -  9*b -  6*c)/2,
            (     - 12*b - 48*c)/6,
            (        6*b + 30*c)/3,
            (     -    b -  6*c)/2)
    end
end

@doc @doc(MitchellNetravaliSpline) MitchellNetravaliSplinelPrime

function MitchellNetravaliSpline(::Type{T}, b::Real, c::Real,
                                 ::Type{B} = Flat) where {T<:AbstractFloat,
                                                          B<:Boundaries}
    MitchellNetravaliSpline{T,B}(b, c)
end

function MitchellNetravaliSpline(b::Real, c::Real,
                                 ::Type{B} = Flat) where {B<:Boundaries}
    MitchellNetravaliSpline(Float64, b, c, B)
end

function MitchellNetravaliSplinePrime(::Type{T}, b::Real, c::Real,
                                      ::Type{B} = Flat) where {T<:AbstractFloat,
                                                               B<:Boundaries}
    MitchellNetravaliSplinePrime{T,B}(b, c)
end

function MitchellNetravaliSplinePrime(b::Real, c::Real,
                                      ::Type{B} = Flat) where {B<:Boundaries}
    MitchellNetravaliSplinePrime(Float64, b, c, B)
end

# Create Mitchell-Netravali kernel with default "good" parameters.
function MitchellNetravaliSpline(::Type{T} = Float64,
                                 ::Type{B} = Flat) where {T<:AbstractFloat,
                                                          B<:Boundaries}
    MitchellNetravaliSpline{T,B}(frac(T,1,3), frac(T,1,3))
end

function MitchellNetravaliSplinePrime(::Type{T} = Float64,
                                      ::Type{B} = Flat) where {T<:AbstractFloat,
                                                               B<:Boundaries}
    MitchellNetravaliSplinePrime{T,B}(frac(T,1,3), frac(T,1,3))
end

iscardinal(ker::MitchellNetravaliSpline{T,B}) where {T,B} = (ker.b == 0)
iscardinal(ker::MitchellNetravaliSplinePrime{T,B}) where {T,B} = false

isnormalized(::Union{K,Type{K}}) where {K<:MitchellNetravaliSpline} = true
isnormalized(::Union{K,Type{K}}) where {K<:MitchellNetravaliSplinePrime} = false

Base.summary(ker::MitchellNetravaliSpline{T,B}) where {T,B} =
    @sprintf("MitchellNetravaliSpline(%.1f,%.1f)", ker.b, ker.c)

Base.summary(ker::MitchellNetravaliSplinePrime{T,B}) where {T,B} =
    @sprintf("MitchellNetravaliSplinePrime(%.1f,%.1f)", ker.b, ker.c)

function convert(::Type{MitchellNetravaliSpline{T,B}},
                 ker::MitchellNetravaliSpline) where {T<:AbstractFloat,
                                                      B<:Boundaries}
    MitchellNetravaliSpline(T, ker.b, ker.c, B)
end

function convert(::Type{MitchellNetravaliSplinePrime{T,B}},
                 ker::MitchellNetravaliSplinePrime) where {T<:AbstractFloat,
                                                           B<:Boundaries}
    MitchellNetravaliSplinePrime(T, ker.b, ker.c, B)
end

@inline _p(ker::MitchellNetravaliSpline{T,B}, x::T) where {T,B} =
    (ker.p3*x + ker.p2)*x*x + ker.p0

@inline _q(ker::MitchellNetravaliSpline{T,B}, x::T) where {T,B} =
    ((ker.q3*x + ker.q2)*x + ker.q1)*x + ker.q0

function (ker::MitchellNetravaliSpline{T,B})(x::T) where {T<:AbstractFloat,B}
    a = abs(x)
    return (a ≥ 2 ? zero(T) : a ≤ 1 ? _p(ker, a) : _q(ker, a))
end

@inline function getweights(ker::MitchellNetravaliSpline{T,B},
                            t::T) where {T<:AbstractFloat,B}
    # 25 operations
    return (_q(ker, t + 1),
            _p(ker, t),
            _p(ker, 1 - t),
            _q(ker, 2 - t))
end

@inline _p(ker::MitchellNetravaliSplinePrime{T,B}, x::T) where {T,B} =
    (ker.p2*x + ker.p1)*x

@inline _q(ker::MitchellNetravaliSplinePrime{T,B}, x::T) where {T,B} =
    (ker.q2*x + ker.q1)*x + ker.q0

function (ker::MitchellNetravaliSplinePrime{T,B})(x::T) where {T<:AbstractFloat,B}
    s, a = signabs(x)
    return (a ≥ 2 ? zero(T) : a ≤ 1 ? s*_p(ker, a) : s*_q(ker, a))
end

@inline function getweights(ker::MitchellNetravaliSplinePrime{T,B},
                            t::T) where {T<:AbstractFloat,B}
    # 19 operations
    return (_q(ker, t + 1),
            _p(ker, t),
            -_p(ker, 1 - t),
            -_q(ker, 2 - t))
end

#------------------------------------------------------------------------------
"""
# Keys cardinal kernels

```julia
KeysSpline([T=Float64,] a, B=Flat) -> ker
```

yields an interpolation kernel of the Keys family of cardinal kernels for
floating-point type `T`, parameter `a` and boundary conditions `B`.

These kernels are piecewise normalized cardinal cubic spline which depend on
one parameter `a` which is the slope of the spline at abscissa 1.

Keys splines are defined by:

```
ker(x) = p(abs(x))   if abs(x) ≤ 1
         q(abs(x))   if 1 ≤ abs(x) ≤ 2
         0           if abs(x) ≥ 2
```

with:

```
p(x) = 1 - (a + 3)*x^2 + (a + 2)*x^3
q(x) = -4a + 8a*x - 5a*x^2 + a*x^3
```

Their derivatives are given by:

```julia
KeysSplinePrime([T=Float64,] a, B=Flat) -> ker′
```

defined by:

```
ker′(x) = p′(abs(x))*sign(x)   if abs(x) ≤ 1
          q′(abs(x))*sign(x)   if 1 ≤ abs(x) ≤ 2
          0                    if abs(x) ≥ 2
```

with:
```
p(x) = -2*(a + 3)*x + 3*(a + 2)*x^2
q(x) = 8a - 10a*x + 3a*x^2
```

Reference:

* Keys, Robert, G., "Cubic Convolution Interpolation for Digital Image
  Processing", IEEE Trans. Acoustics, Speech, and Signal Processing,
  Vol. ASSP-29, No. 6, December 1981, pp. 1153-1160.

"""
struct KeysSpline{T,B} <: Kernel{T,4,B}
    a ::T
    p0::T
    p2::T
    p3::T
    q0::T
    q1::T
    q2::T
    q3::T
    function KeysSpline{T,B}(a::Real) where {T,B}
        new{T,B}(a,
                 1, -a - 3, a + 2,
                 -4a, 8a, -5a, a)
    end
end

struct KeysSplinePrime{T,B} <: Kernel{T,4,B}
    a::T
    b::T # 2*(a + 3)
    c::T # a + 2
    p1::T
    p2::T
    q0::T
    q1::T
    q2::T
    function KeysSplinePrime{T,B}(a::Real) where {T,B}
        new{T,B}(a, 2a + 6, a + 2,
                 -2a - 6, 3a + 6,
                 8a, -10a, 3a)
    end
end

@doc @doc(KeysSpline) KeysSplinePrime

function KeysSpline(::Type{T}, a::Real,
                    ::Type{B} = Flat) where {T<:AbstractFloat, B<:Boundaries}
    KeysSpline{T,B}(a)
end

KeysSpline(a::Real, ::Type{B} = Flat) where {B<:Boundaries} =
    KeysSpline(Float64, a, B)

function KeysSplinePrime(::Type{T}, a::Real,
                    ::Type{B} = Flat) where {T<:AbstractFloat, B<:Boundaries}
    KeysSplinePrime{T,B}(a)
end

KeysSplinePrime(a::Real, ::Type{B} = Flat) where {B<:Boundaries} =
    KeysSplinePrime(Float64, a, B)

iscardinal(::Union{K,Type{K}}) where {K<:KeysSpline} = true
isnormalized(::Union{K,Type{K}}) where {K<:KeysSpline} = true
Base.summary(ker::KeysSpline) = @sprintf("KeysSpline(%.1f)", ker.a)

iscardinal(::Union{K,Type{K}}) where {K<:KeysSplinePrime} = false
isnormalized(::Union{K,Type{K}}) where {K<:KeysSplinePrime} = false
Base.summary(ker::KeysSplinePrime) = @sprintf("KeysSplinePrime(%.1f)", ker.a)

function convert(::Type{KeysSpline{T,B}},
                 ker::KeysSpline) where {T<:AbstractFloat, B<:Boundaries}
    KeysSpline(T, ker.a, B)
end

function convert(::Type{KeysSplinePrime{T,B}},
                 ker::KeysSplinePrime) where {T<:AbstractFloat, B<:Boundaries}
    KeysSplinePrime(T, ker.a, B)
end

@inline _p(ker::KeysSpline{T,B}, x::T) where {T,B} =
    (ker.p3*x + ker.p2)*x*x + ker.p0

@inline _q(ker::KeysSpline{T,B}, x::T) where {T,B} =
    ((ker.q3*x + ker.q2)*x + ker.q1)*x + ker.q0

function (ker::KeysSpline{T,B})(x::T) where {T<:AbstractFloat,B}
    a = abs(x)
    return (a ≥ 2 ? zero(T) : a ≤ 1 ? _p(ker, a) : _q(ker, a))
end

@inline _p(ker::KeysSplinePrime{T,B}, x::T, a::T) where {T,B} =
    (ker.p2*a + ker.p1)*x

@inline _q(ker::KeysSplinePrime{T,B}, x::T, s::T, a::T) where {T,B} =
    ((ker.q2*a + ker.q1)*x + ker.q0*s)

function (ker::KeysSplinePrime{T,B})(x::T) where {T<:AbstractFloat,B}
    s, a = signabs(x)
    return (a ≥ 2 ? zero(T) : a ≤ 1 ? _p(ker, x, a) : _q(ker, x, s, a))
end

@inline function getweights(ker::KeysSpline{T,B},
                            t::T) where {T<:AbstractFloat,B}
    # t ∈ [0,1), S=4
    # w1 = f(t1) = f4(t1) = q(t1)     t1 = t + 1 ∈ [1,2)
    # w2 = f(t2) = f3(t2) = p(t2)     t2 = t     ∈ [0,1)
    # w3 = f(t3) = f2(t3) = p(-t3)    t3 = t - 1 ∈ [-1,0)
    # w4 = f(t4) = f1(t4) = q(-t4)    t4 = t - 2 ∈ [-2,0)
    #
    # w1 = a*(1 - t)^2*t
    # w2 = 1 - (3 + a)*t^2 + (2 + a)*t^3
    # w3 = -t*(a*(1 - t)^2 + t*(2*t - 3))
    # w4 = a*(1 - t)*t^2
    #
    # in 12 operations (instead of 25 with the polynomials):
    a = ker.a
    r = 1 - t
    s = (ker.p3*r + 1)*t*t
    art = a*r*t
    w1 = art*r
    w4 = art*t
    w2 = 1 - s
    w3 = s - w1 - w4
    return w1, w2, w3, w4
end

@inline function getweights(ker::KeysSplinePrime{T,B},
                            t::T) where {T<:AbstractFloat,B}
    # t ∈ [0,1), S=4
    # w1 = f(t1) = f4(t1) =  q(t1)     t1 = t + 1 ∈ [1,2)
    # w2 = f(t2) = f3(t2) =  p(t2)     t2 = t     ∈ [0,1)
    # w3 = f(t3) = f2(t3) = -p(-t3)    t3 = t - 1 ∈ [-1,0)
    # w4 = f(t4) = f1(t4) = -q(-t4)    t4 = t - 2 ∈ [-2,0)
    #
    # w1 = a*(1 - t)*(1 - 3*t)
    # w2 = (3*(2 + a)*t - 2*(3 + a))*t
    # w3 = (1 - t)*(3*(2 + a)*t - a)
    # w4 = a*(2 - 3*t)*t
    #
    # in 13 operations:
    a = ker.a
    b = ker.b
    c = ker.c
    q = 3*t
    r = 1 - t
    s = c*q
    w1 = (a - a*q)*r
    w2 = (s - b)*t
    w3 = (s - a)*r
    w4 = (2 - q)*t*a
    return w1, w2, w3, w4
end

#------------------------------------------------------------------------------
"""
# Lanczos Resampling Kernel

```julia
LanczosKernel([T=Float64,] S, B=Flat)
```

yields a Lanczos kernel of support size `S` (which must be even), for
floating-point type `T` and boundary conditions `B`..

The Lanczos kernels doe not have the partition of unity property.  However,
Lanczos kernels tend to be normalized for large support size.

The derivative of Lanczos kernel of support size `S` is given by:

```julia
LanczosKernelPrime([T=Float64,] S, B=Flat)
```

See also: [link](https://en.wikipedia.org/wiki/Lanczos_resampling).

"""
struct LanczosKernel{T,S,B} <: Kernel{T,S,B}
    a::T   # 1/2 support
    b::T   # a/pi^2
    c::T   # pi/a
    function LanczosKernel{T,S,B}() where {T,S,B}
        @assert typeof(S) == Int && S > 0 && iseven(S)
        a = frac(T,S,2)
        new{T,S,B}(a, a/T(π)^2, T(π)/a)
    end
end

struct LanczosKernelPrime{T,S,B} <: Kernel{T,S,B}
    a::T   # 1/2 support
    c::T   # pi/a
    function LanczosKernelPrime{T,S,B}() where {T,S,B}
        @assert typeof(S) == Int && S > 0 && iseven(S)
        a = T(S)/T(2)
        new{T,S,B}(a, T(π)/a)
    end
end

@doc @doc(LanczosKernel) LanczosKernelPrime

function LanczosKernel(::Type{T}, s::Integer,
                       ::Type{B} = Flat) where {T<:AbstractFloat,
                                                B<:Boundaries}
    return LanczosKernel{T,Int(s),B}()
end

function LanczosKernelPrime(::Type{T}, s::Integer,
                            ::Type{B} = Flat) where {T<:AbstractFloat,
                                                     B<:Boundaries}
    return LanczosKernelPrime{T,Int(s),B}()
end

LanczosKernel(s::Integer, ::Type{B} = Flat) where {B<:Boundaries} =
    LanczosKernel{Float64,Int(s),B}()

LanczosKernelPrime(s::Integer, ::Type{B} = Flat) where {B<:Boundaries} =
    LanczosKernelPrime{Float64,Int(s),B}()

iscardinal(::Union{K,Type{K}}) where {K<:LanczosKernel} = true
isnormalized(::Union{K,Type{K}}) where {K<:LanczosKernel} = false
Base.summary(::LanczosKernel{T,S,B}) where {T,S,B} = "LanczosKernel($S)"

iscardinal(::Union{K,Type{K}}) where {K<:LanczosKernelPrime} = false
isnormalized(::Union{K,Type{K}}) where {K<:LanczosKernelPrime} = false
Base.summary(::LanczosKernelPrime{T,S,B}) where {T,S,B} = "LanczosKernelPrime($S)"

# `convert` should give something which is almost equivalent, so here we
# enforce the same support size.
function convert(::Type{LanczosKernel{T,S,B}},
                 ::LanczosKernel{<:AbstractFloat,S,<:Boundaries}
                 ) where {T<:AbstractFloat,S,B<:Boundaries}
    LanczosKernel{T,S,B}()
end

function convert(::Type{LanczosKernelPrime{T,S,B}},
                 ::LanczosKernelPrime{<:AbstractFloat,S,<:Boundaries}
                 ) where {T<:AbstractFloat,S,B<:Boundaries}
    LanczosKernelPrime{T,S,B}()
end

# Expression for non-zero argument in the range (-S/2,S/2).
@inline _p(ker::LanczosKernel{T,S,B}, x::T) where {T,S,B} =
    ker.b*sin(π*x)*sin(ker.c*x)/(x*x)

# Expression for non-zero argument in the range (-S/2,S/2).
@inline function _p(ker::LanczosKernelPrime{T,S,B}, x::T) where {T,S,B}
    x1 = π*x
    s1, c1 = sin(x1), cos(x1)
    r1 = s1/x1
    x2 = ker.c*x # π*x/a
    s2, c2 = sin(x2), cos(x2)
    r2 = s2/x2
    return (c1*r2 + c2*r1 - 2*r1*r2)/x
end

(ker::LanczosKernel{T,S,B})(x::T) where {T,S,B} =
    (abs(x) ≥ ker.a ? zero(T) : x == 0 ? one(T) : _p(ker, x))

(ker::LanczosKernelPrime{T,S,B})(x::T) where {T,S,B} =
    (abs(x) ≥ ker.a ? zero(T) : x == 0 ? one(T) : _p(ker, x))

@generated function getweights(ker::LanczosKernel{T,S,B}, t::T) where {T,S,B}
    c = (S >> 1) # central index
    W = [Symbol("w",i) for i in 1:S] # all weights
    Expr(:block,
         Expr(:meta, :inline),
         Expr(:local, [:($w::T) for w in W]...),
         Expr(:if, :(t == zero(T)),
              Expr(:block, [:($(W[i]) = $(i == c ? 1 : 0)) for i in 1:S]...),
              Expr(:block,
                   [:($(W[i]) = _p(ker, t + $(c - i))) for i in 1:c-1]...,
                   :($(W[c]) = _p(ker, t)),
                   [:($(W[i]) = _p(ker, t - $(i - c))) for i in c+1:S]...)),
         Expr(:return, Expr(:tuple, W...)))
end

@generated function getweights(ker::LanczosKernelPrime{T,S,B}, t::T) where {T,S,B}
    c = (S >> 1) # central index
    W = [Symbol("w",i) for i in 1:S] # all weights
    Expr(:block,
         #Expr(:meta, :inline),
         Expr(:local, [:($w::T) for w in W]...),
         [:($(W[i]) = ker(t + $(c - i))) for i in 1:c-1]...,
         :($(W[c]) = ker(t)),
         [:($(W[i]) = ker(t - $(i - c))) for i in c+1:S]...,
         Expr(:return, Expr(:tuple, W...)))
end

#------------------------------------------------------------------------------

# Manage to call the short version of `show` for MIME output.
Base.show(io::IO, ::MIME"text/plain", ker::Kernel) = show(io, ker)

"""
```julia
brief(ker)
```

yields the name of kernel `ker`.

""" brief

for (T, str) in (
    (:RectangularSpline, "rectangular B-spline"),
    (:RectangularSplinePrime, "derivative of rectangular B-spline"),
    (:LinearSpline, "linear B-spline"),
    (:LinearSplinePrime, "derivative of linear B-spline"),
    (:QuadraticSpline, "quadratic B-spline"),
    (:QuadraticSplinePrime, "derivative of quadratic B-spline"),
    (:CubicSpline, "cubic B-spline"),
    (:CubicSplinePrime, "derivative of cubic B-spline"),
    (:CardinalCubicSpline, "cardinal cubic spline"),
    (:CardinalCubicSplinePrime, "derivative of cardinal cubic spline"),
    (:CatmullRomSpline, "Catmull & Rom cubic spline"),
    (:CatmullRomSplinePrime, "derivative of Catmull & Rom cubic spline"),
    (:MitchellNetravaliSpline, "Mitchell & Netravali cubic spline"),
    (:MitchellNetravaliSplinePrime, "derivative of Mitchell & Netravali cubic spline"),
    (:KeysSpline, "Keys cubic spline"),
    (:KeysSplinePrime, "derivative of Keys cubic spline"))
    @eval brief(::$T) = $str
end

brief(::LanczosKernel{T,S,B}) where {T,S,B} =
    "Lanczos resampling kernel of size $S"

brief(::LanczosKernelPrime{T,S,B}) where {T,S,B} =
    "derivative of Lanczos resampling kernel of size $S"

# Manage to yield the derivative of (some) kernels when the notation `ker'` is
# used.
for T in (:RectangularSpline, :LinearSpline, :QuadraticSpline,
          :CubicSpline, :CatmullRomSpline)
    @eval adjoint(ker::$T{T,B}) where {T,B} = $(Symbol(T,:Prime)){T,B}()
end

adjoint(ker::CardinalCubicSpline{T,B}) where {T,B} =
    CardinalCubicSplinePrime{T,B}(ker.c)

adjoint(ker::LanczosKernel{T,S,B}) where {T,S,B} =
    LanczosKernelPrime{T,S,B}()

adjoint(ker::MitchellNetravaliSpline{T,B}) where {T,B} =
    MitchellNetravaliSplinePrime{T,B}(ker.b, ker.c)

adjoint(ker::KeysSpline{T,B}) where {T,B} =
    KeysSplinePrime{T,B}(ker.a)

# Provide methods for parameter-less kernels.
for K in (:RectangularSpline, :RectangularSplinePrime,
          :LinearSpline, :LinearSplinePrime,
          :QuadraticSpline, :QuadraticSplinePrime,
          :CubicSpline, :CubicSplinePrime,
          :CatmullRomSpline, :CatmullRomSplinePrime)
    @eval begin

        # Constructors.
        function $K(::Type{T} = Float64, ::Type{B} = Flat
                    ) where {T<:AbstractFloat,B<:Boundaries}
            $K{T,B}()
        end

        function $K(::Type{B}, ::Type{T} = Float64
                    ) where {T<:AbstractFloat,B<:Boundaries}
            $K{T,B}()
        end

        # Conversion to different types.
        function convert(::Type{$K{T,B}}, ::$K
                         ) where {T<:AbstractFloat,B<:Boundaries}
            $K{T,B}()
        end

    end
end

# Provide methods for all kernels.
for K in subtypes(Kernel)

    lanczos = (K <: LanczosKernel || K <: LanczosKernelPrime)

    # We want that calling the kernel on a different type of real argument than
    # the floting-point type of the kernel convert the argument.
    # Unfortunately, defining:
    #
    #     (ker::$K{T,B})(x::Real) where {T<:AbstractFloat,B<:Boundaries} =
    #         ker(convert(T,x))
    #
    # leads to ambiguities, the following is ugly but works...
    for T in subtypes(AbstractFloat), R in (subtypes(AbstractFloat)..., Integer)
        if R != T
            if lanczos
                @eval @inline (ker::$K{$T,S,B})(x::$R) where {S,B<:Boundaries} =
                    ker($T(x))
            else
                @eval @inline (ker::$K{$T,B})(x::$R) where {B<:Boundaries} =
                    ker($T(x))
            end
        end
    end

    # Change type.
    for R in (:Float16, :Float32, :Float64)
        if lanczos
            @eval Base.$R(ker::$K{T,S,B}) where {T,S,B} =
                convert($K{$R,S,B}, ker)
        else
            @eval Base.$R(ker::$K{T,B}) where {T,B} =
                convert($K{$R,B}, ker)
        end
    end

    # Change boundary conditions.
    for C in (:Flat, :SafeFlat)
        if lanczos
            @eval $C(ker::$K{T,S,B}) where {T,S,B} =
                convert($K{T,S,$C}, ker)
        else
            @eval $C(ker::$K{T,B}) where {T,B} =
                convert($K{T,$C}, ker)
        end
    end

    # Calling the kernel on an array.  FIXME: should be deprecated!
    if lanczos
        @eval function (ker::$K{T,S,B})(A::AbstractArray
                                        ) where {T<:AbstractFloat,S,
                                                 B<:Boundaries}
            map((x) -> ker(x), A)
        end
    else
        @eval function (ker::$K{T,B})(A::AbstractArray
                                      ) where {T<:AbstractFloat,B<:Boundaries}
            map((x) -> ker(x), A)
        end
    end

    # Calling the kernel as a function to convert to another floating-point
    # type and/or other boundary conditions.
    if lanczos
        @eval begin
            (ker::$K{T,S,B})(::Type{newT}, ::Type{newB}=B) where {
                T, S, B, newT<:AbstractFloat, newB<:Boundaries
            } = convert($K{newT,S,newB}, ker)

            (ker::$K{T,S,B})(::Type{newB}, ::Type{newT}=T) where {
                T, S, B, newT<:AbstractFloat, newB<:Boundaries
            } = convert($K{newT,S,newB}, ker)

            convert(::Type{$K{T,S,B}}, ker::$K{T,S,B}) where {T,S,B} = ker
        end
    else
        @eval begin
            (ker::$K{T,B})(::Type{newT}, ::Type{newB}=B) where {
                T, B, newT<:AbstractFloat, newB<:Boundaries
            } = convert($K{newT,newB}, ker)

            (ker::$K{T,B})(::Type{newB}, ::Type{newT}=T) where {
                T, B, newT<:AbstractFloat, newB<:Boundaries
            } = convert($K{newT,newB}, ker)

            convert(::Type{$K{T,B}}, ker::$K{T,B}) where {T,B} = ker
        end
    end

end

convert(::Type{Kernel}, ker::Kernel) = ker
convert(::Type{Kernel{T}}, ker::Kernel{T}) where {T<:AbstractFloat} = ker
convert(::Type{Kernel{T}}, ker::Kernel) where {T<:AbstractFloat} = ker(T)
convert(::Type{Kernel{T,S}}, ker::Kernel{T,S}) where {T<:AbstractFloat,S} = ker
convert(::Type{Kernel{T,S,B}}, ker::Kernel{T,S,B}) where {T<:AbstractFloat,S,B} = ker
convert(::Type{Kernel{T,S,B}}, ker::Kernel{<:Any,S,<:Any}) where {T<:AbstractFloat,S,B} = ker(T, B)

# Deprecations.
# Prime = ′    \prime + [tab]
# Second = ″
# Third = ‴

@deprecate          KeysSpline′                  KeysSplinePrime
@deprecate         CubicSpline′                 CubicSplinePrime
@deprecate        LinearSpline′                LinearSplinePrime
@deprecate       LanczosKernel′               LanczosKernelPrime
@deprecate     QuadraticSpline′             QuadraticSplinePrime
@deprecate    CatmullRomSpline′            CatmullRomSplinePrime
@deprecate CardinalCubicSpline′         CardinalCubicSplinePrime
@deprecate MitchellNetravaliSpline′ MitchellNetravaliSplinePrime

end # module


mutable struct MLP3{T}
    W1::AbstractMatrix{T}
    b1::AbstractVector{T}
    W2::AbstractMatrix{T}
    b2::AbstractVector{T}
    W3::AbstractMatrix{T}
    b3::AbstractVector{T}
end

MLP3{T}(n1, n2, n3, n4) where T = MLP3(randn(T, n2, n1), randn(T, n2),
                                       randn(T, n3, n2), randn(T, n3),
                                       randn(T, n4, n3), randn(T, n4))

MLP3(n1, n2, n3, n4) = MLP3{Float64}(n1, n2, n3, n4)

function Base.show(io::IO, m::MLP3{T}) where T
    n2, n1 = size(m.W1)
    n4, n3 = size(m.W3)
    print(io, "MLP{$T}($n1, $n2, $n3, $n4)")
end

function (m::MLP3{T})(x) where T
    x = relu(m.W1 * x .+ m.b1)
    x = relu(m.W2 * x .+ m.b2)
    x = relu(m.W3 * x .+ m.b3)
    return x
end

loss(m::MLP3, x) = sum(m(x))

function main()
    m = MLP3{Float32}(5, 3, 3, 5)
    x = rand(5, 10)
    v, g = grad(loss, m, x)
end

mutable struct Coupling
    mask  # {0, 1} array for input splitting
    s     # callable struct representing s function (scale)
    t     # callable struct representing t function (translation)
end

function f_func(c::Coupling, x)
    mask, s, t = c.mask, c.s, c.t
    xp = mask .* x
    sv = s(xp) .* (1 .- mask)
    tv = t(xp) .* (1 .- mask)
    z = (1 .- mask) .* (x .- tv) .* exp.(-sv) .+ xp
    return z
end

function g_func(c::Coupling, z)
    mask, s, t = c.mask, c.s, c.t
    zp = mask .* z
    sv = s(zp) .* (1 .- mask)
    tv = t(zp) .* (1 .- mask)
    x = zp .+ (1 .- mask) .* (z .* exp.(sv) .+ tv)
    return x
end


module RayTracer

using StaticArrays
using LinearAlgebra

global ray_count = 0

const Vec3 = SArray{Tuple{3}, Float64, 1, 3}
const Point3 = SArray{Tuple{3}, Float64, 1, 3}
const Color = SArray{Tuple{3}, Float64, 1, 3}
import Base.zero
zero(::Type{Point3}) = Point3(0,0,0)
zero(::Type{Color}) = Color(0,0,0)

magnitude(x,y) = sqrt(x^2 + y^2)
magnitude(x,y,z) = sqrt(x^2 + y^2 + z^2)
magnitude(v::Vec3) = magnitude(v...)

unit_vector(v::Vec3) = v / magnitude(v)

near_zero(v) = v.x < 1e-8 && v.y < 1e-8 && v.z < 1e-8

function random_in_unit_disk()
	x,y = randf(-1, 1), randf(-1, 1)
	while magnitude(x,y)^2 >= 1
     	x,y = randf(-1, 1), randf(-1, 1)
	end
	x,y
end

function random_in_unit_sphere()
	x,y,z = randf(-1,1), randf(-1,1), randf(-1,1)
	while magnitude(x,y,z)^2 >= 1
		x,y,z = randf(-1,1), randf(-1,1), randf(-1,1)
	end
	Point3(x,y,z)
end

random_unit_vector() = unit_vector(random_in_unit_sphere())

function random_in_hemisphere(normal)
    in_unit_sphere = random_in_unit_sphere()
    dot(in_unit_sphere, normal) > 0.0 ? in_unit_sphere : -in_unit_sphere
end

reflect(v, n) =  v - 2dot(v,n)*n

function refract(uv, n, etai_over_etat)
    cos_theta = min(dot(-uv, n), 1.0)
    r_out_perp = etai_over_etat * (uv + cos_theta*n)
    r_out_parallel = -sqrt(abs(1.0 - magnitude(r_out_perp)^2)) * n
    r_out_perp + r_out_parallel
end

struct Ray
	origin::Point3
	direction::Vec3
	udirection::Vec3
	tm::Float64
	Ray(o, d, m) = new(o, d, unit_vector(d), m)
	Ray(o, d) = Ray(o, d, 0)
	Ray() = new(Vec3(Inf, Inf, Inf), Vec3(Inf, Inf, Inf))
end

at(r::Ray, t) = r.origin + t * r.direction

struct Camera
	origin::Point3
	lower_left_corner::Point3
	horizontal::Vec3
	vertical::Vec3
	u::Vec3
	v::Vec3
	w::Vec3
	lens_radius::Float64
	time0::Float64
	time1::Float64
	function Camera(lookfrom, lookat, vup, vfov, aspect_ratio, aperture, focus_dist, _time0=0, _time1=0)
		viewport_height = 2.0 * tan(deg2rad(vfov)/2)
		viewport_width = aspect_ratio * viewport_height

		w = unit_vector(lookfrom - lookat)
		u = unit_vector(cross(vup, w))
		v = cross(w, u)

		origin = lookfrom
		horizontal = focus_dist * viewport_width * u
		vertical = focus_dist * viewport_height * v
		lower_left_corner = origin - horizontal/2 - vertical/2 - focus_dist*w

		lens_radius = aperture / 2
		time0 = _time0
		time1 = _time1
		new(origin, lower_left_corner, horizontal, vertical, u, v, w, lens_radius, time0, time1)
	end
	Camera() = Camera(Point3(0,0,-1), Point3(0,0,0), Vec3(0,1,0), 40, 1, 0, 10)
end

abstract type Material end
abstract type Hitable end

struct Scene
	camera::Camera
	hitables::Vector{Hitable}
	Scene(cam) = new(cam, Vector{Hitable}())
end

randf(fmin, fmax) = fmin + (fmax-fmin)*rand()

get_ray(scene::Scene, s, t) = get_ray(scene.camera, s, t)

function get_ray(cam::Camera, s, t)
	x,y = cam.lens_radius .* random_in_unit_disk()
	offset = cam.u * x + cam.v *y
	Ray(cam.origin + offset, cam.lower_left_corner + s*cam.horizontal + t*cam.vertical - cam.origin - offset, randf(cam.time0, cam.time1))
end

#==
import Base.+
+(c1::Color, c2::Color) = Color(c1.rgb[1]+c2.rgb[1], c1.rgb[2]+c2.rgb[2], c1.rgb[3]+c2.rgb[3])

import Base.*
*(f::Float64, c::Color) = Color(f*c.rgb[1], f*c.rgb[2], f*c.rgb[3])
*(c1::Color, c2::Color) = Color(c1.rgb[1]*c2.rgb[1], c1.rgb[2]*c2.rgb[2], c1.rgb[3]*c2.rgb[3])

import Base./
/(c::Color, f) = Color(c.rgb[1]/f, c.rgb[2]/f, c.rgb[3]/f)
==#

clamp(f, b, t) = f < b ? b : f > t ? t : f

function write(io::IO, c::Color)
	val(rgb) = round(Int, 255clamp(sqrt(rgb), 0, 1))
	println(io, "$(val(c[1])) $(val(c[2])) $(val(c[3]))")
end

struct Hit
	p::Point3
	normal::Vec3
	material::Material
	t::Float64
	front_face::Bool
	function Hit(s::Sphere, t, ray)
		p = at(ray, t)
		outward_normal = (p - s.center) / s.radius
		ff = dot(ray.direction, outward_normal)
		norm = ff < 0 ? outward_normal : -outward_normal
		new(p, norm, s.material, t, ff < 0)
	end
	Hit() = new()
end

function trace(scene::Scene, ray::Ray, t_min, t_max)
	closest_t = t_max
	local struck::Hitable

	for hitable in scene.hitables
		t = trace(hitable, ray, t_min, closest_t)
		if t >= 0
			closest_t = t
			struck = hitable
		end
	end

	if @isdefined struck
		Hit(struck, closest_t, ray)
	else
		Hit()
	end
end

struct Lambertian <: Material
	albedo::Color
	Lambertian(c::Color) = new(c)
	Lambertian(r,g,b) = new(Color(r,g,b))
	Lambertian() = Lambertian(Color(rand()*rand(), rand()*rand(), rand()*rand()))
end

function scatter(l::Lambertian, ray::Ray, hit::Hit)
	scatter_direction = hit.normal + random_unit_vector()
	if near_zero(scatter_direction)
		scatter_direction = hit.normal
	end
	Ray(hit.p, scatter_direction), l.albedo
end

struct Metal <: Material
	albedo::Color
	fuzz::Float64
	Metal(a, f) = new(a, f)
	Metal(r,g,b,f) = Metal(Color(r,g,b), f)
end

function scatter(m::Metal, ray::Ray, hit::Hit)
	reflected = reflect(ray.udirection, hit.normal)
        scattered = Ray(hit.p, reflected + m.fuzz * random_in_unit_sphere())
	dot(scattered.direction, hit.normal) > 0 ? (scattered, m.albedo) : (Ray(), zero(Color))
end

struct Dielectric <: Material
	ir::Float64
end

function reflectance(cosine, ref_idx)
	r0 = ((1-ref_idx) / (1+ref_idx))^2
    r0 + (1-r0)*(1 - cosine)^5
end

function scatter(d::Dielectric, ray::Ray, hit::Hit)
	refraction_ratio = hit.front_face ? (1.0/d.ir) : d.ir

	cos_theta = min(dot(-ray.udirection, hit.normal), 1.0)
	sin_theta = sqrt(1.0 - cos_theta^2)
	cannot_refract = refraction_ratio * sin_theta > 1.0
	direction = if cannot_refract || reflectance(cos_theta, refraction_ratio) > rand()
			reflect(ray.udirection, hit.normal)
		else
			refract(ray.udirection, hit.normal, refraction_ratio)
		end

	Ray(hit.p, direction), Color(1,1,1)
end

abstract type Hitable end

struct Sphere <: Hitable
	center::Point3
	radius::Float64
	material::Material
end

function trace(sphere::Sphere, ray::Ray, t_min, t_max)
	oc = ray.origin - sphere.center
	a = magnitude(ray.direction)^2
	half_b = dot(oc, ray.direction)
	c = magnitude(oc)^2 - sphere.radius^2
	discriminant = half_b^2 - a*c
	if discriminant < 0
		return -1
	end

	sqrtd = sqrt(discriminant)
	root = (-half_b - sqrtd) / a
	if root < t_min || t_max < root
		root = (-half_b + sqrtd) / a
		if root < t_min || t_max < root
			return -1
		end
	end

	root
end

function ray_color(scene::Scene, ray::Ray, depth)::Tuple{Float64, Float64, Float64}
	if depth <= 0
        	return 0,0,0
	end

	hit = trace(scene, ray, 0.001, Inf)
	if isdefined(hit, :material)
		s, a = scatter(hit.material, ray, hit)
		if s.origin.x == Inf
			return 0,0,0
		end
		r,g,b = ray_color(scene, s, depth-1)
		return a[1] * r, a[2] * g, a[3] * b
	end

	t::Float64 = 0.5*(ray.udirection.y + 1.0)
	t1m = 1.0 - t
	t1m + 0.5t, t1m + 0.7t, t1m + t
end

add!(s::Scene, h::Hitable) = push!(s.hitables, h)

function add_random_scene!(scene::Scene)

	add!(scene, Sphere(Point3(0,-1000,0), 1000, Lambertian(Color(0.5, 0.5, 0.5))))

	rand_material(p) = if p < 0.8
				Lambertian()
			elseif p < 0.95
				rf = randf(0.5, 1)
				Metal(Color(rf, rf, rf), 0.5rand())
			else
				Dielectric(1.5)
			end

	for a in -11:10, b in -11:10
		center = Point3(a + 0.9rand(), 0.2, b + 0.9rand())
		if magnitude(center - Point3(4, 0.2, 0)) > 0.9
			add!(scene, Sphere(center, 0.2, rand_material(rand())))
		end
	end

	add!(scene, Sphere(Point3(0, 1, 0), 1.0, Dielectric(1.5)))
	add!(scene, Sphere(Point3(-4, 1, 0), 1.0, Lambertian(0.4,0.2,0.1)))
	add!(scene, Sphere(Point3(4, 1, 0), 1.0, Metal(Color(0.7,0.6,0.5), 0.0)))
end

const Scanline = Vector{Color}

function trace_scanline(world, y, samples, width, height, max_depth)
	scanline = Scanline(undef, width)
	@simd for x in 1:width
		r=g=b=0.0
		for i in 1:samples
			(r,g,b) = (r,g,b) .+ ray_color(world, get_ray(world, (x + rand()) / width, (y + rand()) / height), max_depth)
		end
		@inbounds scanline[x] = Color(r/samples, g/samples, b/samples)
	end
	scanline
end

function write_ppm(io, scanlines, w, h)
	println(io, "P3\n$w $h\n255")
	foreach(scanline->foreach(p->write(io, p), scanline), scanlines[end:-1:1])
end

function main(io; image_width=1200, aspect_ratio=16/9, samples_per_pixel=10, max_depth=50)

	image_height = round(Int, image_width / aspect_ratio)

	world = Scene(Camera(Point3(13.,2.,3.), zero(Point3), Vec3(0,1,0), 20, aspect_ratio, 0.1, 10.0))
	add_random_scene!(world)

	scanlines = Vector{Scanline}(undef, image_height)

	#Threads.@threads
	for y in 1:image_height
		@inbounds scanlines[y] = trace_scanline(world, y, samples_per_pixel, image_width, image_height, max_depth)
    end

	write_ppm(io, scanlines, image_width, image_height)
end

function go()
	open("r.ppm", "w") do io
		@time main(io, image_width=1200, samples_per_pixel=10)
	end
end

function bmark()
	# using BenchmarkTools
	world = Scene(Camera(Point3(13.,2.,3.), zero(Point3), Vec3(0,1,0), 20, 16/9, 0.1, 10.0))
	add_random_scene!(world)
	#@benchmark trace_scanline(world, 400, 10, 1200, 800, 50) setup=(evals=3)
end
#==
includet("main.jl")
world = Scene(Camera(Point3(13.,2.,3.), zero(Point3), Vec3(0,1,0), 20, 16/9, 0.1, 10.0))
@code_warntype ray_color(world, get_ray(world, 0.5, 0.5), 5)
==#


function steiner(n)
    model = Model(optimizer_with_attributes(CS.Optimizer,
        "logging" => [],
        "seed" => 4,

        "traverse_strategy" => :BFS,
        # "traverse_strategy"=>:DFS,
        # "traverse_strategy"=>:DBFS,

        # "branch_split"=>:Smallest,
        # "branch_split"=>:Biggest,
        "branch_split" => :InHalf,

        # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
        # "branch_strategy" => :IMPS, # default
        "branch_strategy" => :ABS, # Activity Based Search
        "activity.decay" => 0.999, # default 0.999
        "activity.max_probes" => 1, # default, 10
        "activity.max_confidence_deviation" => 20, # default 20

        # "simplify"=>false,
        # "simplify"=>true, # default

        # "backtrack" => false, # default true
        # "backtrack_sorting" => false, # default true

        # "lp_optimizer" => cbc_optimizer,
        # "lp_optimizer" => glpk_optimizer,
        # "lp_optimizer" => ipopt_optimizer,
    ))

    @assert (n % 6 == 1 || n % 6 == 3)

    nb = round(Int, (n * (n - 1)) / 6) # number of sets

    @variable(model, x[1:nb,1:n], Bin)
    @constraint(model, x[1,1] == 1) # symmetry breaking

    # atmost 1 element in common
    for i in 1:nb
        @constraint(model,sum(x[i,:]) == 3)

        for j in i + 1:nb
            b = @variable(model, [1:n], Bin)
            for k in 1:n
                @constraint(model, b[k] := { x[i,k] && x[j,k] })
            end
            @constraint(model, sum(b) <= 1)
        end
    end

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    @assert status == MOI.OPTIMAL
end


# kspace.jl

using MIRT: ir_mri_kspace_ga_radial

using Plots: plot, plot!, scatter
using Test: @test, @inferred

Nro = 30
delta_ro = 1
k = @inferred ir_mri_kspace_ga_radial(Nro=Nro, Nspoke=13, start=pi,
	shift=-0.5, delta_ro=delta_ro)
@test size(k) == (30, 13, 2)

kx = k[:,:,1]
ky = k[:,:,2]
scatter(kx, ky, label="", aspect_ratio=1,
	markerstrokecolor=:auto, markersize=3, markershape=:circle)
kmax = delta_ro * Nro / 2
plot!(xlim = [-1,1]*kmax)
plot!(ylim = [-1,1]*kmax)
plot!(xtick = (-1:1) * kmax, xlabel = "k_x")
plot!(ytick = (-1:1) * kmax, ylabel = "k_y")


function dp_discontinuous(C::Matrix{T}, M::Integer)
    N = size(C,2)
    D = Array{PiecewiseQuadratic{T}}(undef, N, M)

    for m=2:M
        for i=1:N-m
            for ip=i+1:N-m+1
                D[i, m] = max(D[i, m], D[ip, m-1] + C[i, ip])
            end
        end
    end
end

##
compute_discrete_transition_costs(randn(srand(1), 30))


""" Day19 Advent of Code 2021 """
function day19()
    part = [0, 0]

    scannerblocks = split(read("AoCdata/AoC_2021_day19.txt", String), "\n\n")
    scanners = [Vector{Int}[] for _ in 1:length(scannerblocks)]
    for (i, block) in enumerate(scannerblocks), line in strip.(split(block, "\n"))
        !isempty(line) && line[end] != '-' && push!(scanners[i], parse.(Int, split(line, ",")))
    end
    converted, checked, distances = Set(1), Set{Int}(), Dict{Int, Tuple{Int, Int, Int}}()
    sort!(scanners[1])

    # simulate all configurations of scanner j and then look for a set of 12 between the two
    # which differ by a constant xyz vector such as [0, 0, 0].
    while length(converted) < length(scanners)
        for i in converted
            i in checked && continue
            push!(checked, i)
            mat = sort(scanners[i])
            for j in 1:length(scanners)
                j ∈ converted && continue
                print("Compare $i to $j...\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b")
                for xsign in [1, -1], ysign in [1, -1], zsign in [1, -1], ax in ([1,2,3], [2,1,3], [3,2,1], [1,3,2], [2,3,1], [3,1,2])
                    newmat = deepcopy(scanners[j])
                    for i in eachindex(newmat)
                        newmat[i] .*= [xsign, ysign, zsign]
                    end
                    for (i, t) in enumerate(newmat)
                        newmat[i] .= [t[ax[1]], t[ax[2]], t[ax[3]]]
                    end
                    for b in mat, t in newmat
                        diff = t .- b
                        minindex = min(length(mat), length(newmat))
                        samediffs = count(newmat[n] - mat[m] == diff for n in 1:minindex, m in 1:minindex)
                        if samediffs > 4
                            push!(converted, j)
                            scanners[j] .= [p .-= diff for p in sort!(newmat)]
                            distances[j] = (diff[1], diff[2], diff[3])
                            break
                        end
                    end
                end
            end
        end
    end

    part[1] = length(unique(reduce(vcat, scanners)))
    part[2] = maximum(sum(abs.(d1 .- d2)) for d1 in values(distances), d2 in values(distances))

    return part
end

part = day19()

println("\nPart 1: ", part[1]) # 425
println("Part 2: ", part[2]) # 13354


# Functions to aid in discretizing sets.

" Discretize a given extent into smaller extents with a grid size of delta.
# Arguments
- `set::Dict` - Set to discretize
- `grid_sizes::Dict` - Discretization delta for each component
"
function discretize_set(set::Dict, grid_sizes::Dict)

    extents = []
    for dim_key in keys(set)
        x = set[dim_key]
        x_d = x[1]:grid_sizes[dim_key]:x[2]
        push!(extents, [[x_d[i], x_d[i+1]] for i=1:length(x_d)-1])
    end

    state_extents = Base.product(extents...)
    discrete_sets = Dict()
    for (i, state) in enumerate(state_extents)
        for (j, dim_key) in enumerate(collect(keys(set)))
            if j==1
                discrete_sets[i] = Dict(dim_key => state[j])
            else
                discrete_sets[i][dim_key] = state[j]
            end
        end
    end

    return discrete_sets
end

" Discretize a given extent into smaller extents with a grid size of delta.
# Arguments
- `set::Dict` - Set to discretize
- `grid_sizes::Dict` - Discretization delta for each component
"
function discretize_set_lazy(set::Dict, grid_sizes::Dict)
    # TODO: Generalize this for n-dimensions
    x = set["x1"]
    y = set["x2"]

    x_epsilons = x[1]:grid_sizes["x1"]:x[2]
    y_epsilons = y[1]:grid_sizes["x2"]:y[2]

    discrete_sets = Dict()
    discrete_extents = Dict()
    idx = 1
    for i = 1:length(x_epsilons)-1
        for j = 1:length(y_epsilons)-1
            new_extent = Dict("x1" => [x_epsilons[i], x_epsilons[i+1]], "x2" => [y_epsilons[j], y_epsilons[j+1]])
            new_polygon = VPolygon([[x_epsilons[i], y_epsilons[j]], [x_epsilons[i], y_epsilons[j+1]],
                                    [x_epsilons[i+1], y_epsilons[j+1]], [x_epsilons[i+1], y_epsilons[j]]])
            discrete_sets[idx] = new_polygon
            discrete_extents[idx] = new_extent
            idx += 1
        end
    end
    return discrete_sets, discrete_extents
end

" Refine a discrete state uniformly and update the region info dict.
# Arguments
- `region_id::Int` - Region id to refine
- `region_info_dict:Dict` - Dictionary containing all of the region info
"
function uniform_refinement(region_id, extent_dict)
    extent = extent_dict[region_id]
    delta_dict = Dict()
    for dim in keys(extent)
        delta_dict[dim] = (extent[dim][2] - extent[dim][1])/2
    end

    refined_sets = discretize_set(extent_dict[region_id], delta_dict)

    # ! Does ordering matter? I don't think it should
    N_old = maximum(keys(extent_dict))
    last_extent = extent_dict[N_old]

    # Remove old state
    pop!(extent_dict, region_id)
    new_keys = []
    for i=1:refined_sets.count
        new_key = N_old + i - 1
        extent_dict[N_old + i - 1] = refined_sets[i]
        new_keys = new_keys ∪ [new_key]
    end

    extent_dict[N_old + refined_sets.count + 1] = last_extent

    return extent_dict, new_keys
end


using Flux
using Statistics: mean, std
using Random
#import MultiTheisDP.MultiTheis
#using PyPlot
#ioff()
#using PyCall
#gridspec = pyimport("matplotlib.gridspec")
#using LaTeXStrings
using JLD2
#include("plot_train.jl")
#include("theislike.jl")
push!(LOAD_PATH,"../../src")
push!(LOAD_PATH,"./")
import TheisLike

########################################################
# User input
# Location to monitor pressure at
mon_well_crds = [[-75.0,-75.0]]
# Location of injection and extraction wells
well_crds = [[-50.0,-50.0],
			[50.0,50.0]]
# Injection rate
Qinj = 0.031688 # [m^3/s] (1 MMT water/yr)
# Time to check pressure
t = 30.0*86400.0 # 30 days converted to seconds
# Create xy grid for plotting pressures
xs = ys = -100.0:1.0:100.0
# Training epochs
epochs = 1:120
########################################################

# Calculate distance between extraction and injection wells
rs = [sqrt((sum((cs-mon_well_crds[1]).^2))) for cs in well_crds]

# Input/output normalization function
lerp(x, lo, hi) = x*(hi-lo)+lo

global nevals = 0
# Wrapper around MultiTheis solve returning pressure head at a point
function head(T::Float64,S::Float64,Q1::Float64,Qinj::Float64,rs::Array{Float64,1},t::Float64;train=false)
	Qs = [Q1,Qinj]
	if train
		global nevals = nevals + 1
	end
	#MultiTheis.solve(Qs,T,S,t,rs)
	TheisLike.solve_numerical(Qs, T, S, t, rs)
end

# This helper function doesn't work with DPFEHM. Not really needed anyhow.
#function head(T::Number,S::Number,Q1::Number;train=true)
#	# Injection rate
#	Qinj = 0.031688 # [m^3/s] (1 MMT water/yr)
#	# Time to check pressure
#	t = 30.0*86400.0 # 30 days converted to seconds
#	# Calculate distance between extraction and injection wells
#	rs = [sqrt((sum((cs-mon_well_crds[1]).^2))) for cs in well_crds]
#	#Qs = [Q1,Qinj]
#	return head(T,S,Q1,Qinj,rs,t,train=train)
#end

# Set random seed for repeatability
Random.seed!(0)

# Neural network structure
model = Chain(Dense(3, 8, σ),
              Dense(8, 16, σ),
              Dense(16, 8, σ),
              Dense(8, 1)) |> f64

# Make neural network parameters trackable by Flux
θ = params(model)

# Neural network function (T, S, target)->(Q1,Q2)
# 'target' is the target pressure head at the monitoring location
function aim(T, S, target)
  Q1 = model([T, S, target])
  # Make is so that Q1 and Q2 are always negative for extraction
  Q1 = -softplus(Q1[1])
  #Q2 = -softplus(Q2)
  Q1
end

# Set ranges for P, T, S
# Fix pressure target for now. It should be removed, but when I removed it with MultiTheis, the PIML framework didn't work as well. Moral of the story, NN's are squirrelly things that don't always make sense.
PRES = (0.0,0.0) # Pressure target range [m]
TRANS = (-2.0,0.0) # log10 transmissivity [m^2/s]
# Assume that reservoir thickness is 100m (S = S_s*b)
STOR = (-4.0,-1.0) # log10 storativity

# Random T, S, and P generator
sample() = [10^lerp(rand(), TRANS...), 10^lerp(rand(), STOR...), lerp(rand(), PRES...)]

# Physics model vs NN
function mydiff(T, S, target; train=false)
    (head(T, S, aim(T, S, target), Qinj, rs, t, train=train) - target)
end
function loss(T, S, target; train=true)
	mydiff(T, S, target, train=train)^2
end
loss(x; train=true) = sum(map(x->loss(x[1],x[2],x[3];train=train),x))
mydiff(x; train=true) = sum(map(x->mydiff(x[1],x[2],x[3];train=train),x))

function cb2()
	# Terminal output
	loss_train = sum(map(x->loss(x,train=false),data_train_batch))
	loss_test = sum(map(x->loss(x,train=false),data_test))
	println(string("callback: train loss: ", loss_train, " test loss: ", loss_test))
end

global losses_train = Float64[]
global losses_test = Float64[]
global rmses_train = Float64[]
global rmses_test = Float64[]
global meandiffs_train = Float64[]
global stddiffs_train = Float64[]
global meandiffs_test = Float64[]
global stddiffs_test = Float64[]
global train_time = Float64[]

cb = Flux.throttle(cb2, 10)

opt = ADAM()


# QP example
#
#    minimize 2 x^2 + y^2 + xy + x + y
#
#       s.t.  x, y >= 0
#             x + y = 1
#
#    solution: (0.25, 0.75), objv = 1.875
#

using CPLEX, Base.Test

@testset "QP 01" begin
    env = CPLEX.Env()
    CPLEX.set_param!(env, "CPX_PARAM_SCRIND", 0)

    model = CPLEX.Model(env, "qp_02")

    CPLEX.add_vars!(model, [1., 1.], 0., Inf)

    CPLEX.add_qpterms!(model, [1, 1, 2], [1, 2, 2], [2., 1., 1.])
    CPLEX.add_constr!(model, [1., 1.], '=', 1.)

    CPLEX.optimize!(model)

    sol = CPLEX.get_solution(model)
    @test abs(sol[1]) < 1e-4
    @test isapprox(sol[2], 1, rtol=1e-4)
    @test isapprox(CPLEX.get_objval(model), 1.5, rtol=1e-8)
end


"""
    Wagner(; C1=0.165, C2=0.335, eps1 = 0.0455, eps2 = 0.3)

Two-dimensional aerodynamic model based on Wagner's function with state variables
``\\lambda_1, \\lambda_2``, inputs ``u, v, \\omega``, and parameters ``a, b, a_0,
\\alpha_0, c_{d0}, c_{m0}``
"""
struct Wagner{TF}
    C1::TF
    C2::TF
    eps1::TF
    eps2::TF
end

"""
    Wagner(; C1=0.165, C2=0.335, eps1 = 0.0455, eps2 = 0.3)

Initialize a model of type [`Wagner`](@ref)
"""
Wagner(; C1=0.165, C2=0.335, eps1 = 0.0455, eps2 = 0.3) = Wagner(promote(C1, C2, eps1, eps2)...)

# --- Submodel Creation --- #

function Submodel(model::Wagner)

    C1 = model.C1
    C2 = model.C2
    eps1 = model.eps1
    eps2 = model.eps2

    # residual function
    fresid = (dx, x, y, p, t) -> wagner_residual(dx, x, y, p, t; C1, C2, eps1, eps2)

    # number of state, input, and parameters (use Val(N) to use inferrable dimensions)
    nx = Val(2)
    ny = Val(3)
    np = Val(6)

    # jacobian definitions
    ratejac = Identity()
    statejac = Linear((dx, x, y, p, t) -> wagner_state_jacobian(dx, x, y, p, t; model.eps1, model.eps2))
    inputjac = Nonlinear((dx, x, y, p, t) -> wagner_input_jacobian(dx, x, y, p, t; model.C1, model.C2, model.eps1, model.eps2))
    paramjac = Nonlinear((dx, x, y, p, t) -> wagner_parameter_jacobian(dx, x, y, p, t; model.C1, model.C2, model.eps1, model.eps2))
    tgrad = Zeros()

    # convenience functions for setting states, inputs, and parameters
    setstate = wagner_set_states!
    setinput = wagner_set_inputs!
    setparam = wagner_set_parameters!

    # convenience functions for separating states, inputs, and parameters
    sepstate = wagner_separate_states
    sepinput = wagner_separate_inputs
    sepparam = wagner_separate_parameters

    # model definition
    return Submodel{false}(fresid, nx, ny, np;
        ratejac = ratejac,
        statejac = statejac,
        inputjac = inputjac,
        paramjac = paramjac,
        tgrad = tgrad,
        setstate = setstate,
        setinput = setinput,
        setparam = setparam,
        sepstate = sepstate,
        sepinput = sepinput,
        sepparam = sepparam,
    )
end

# --- Internal Methods --- #

# residual function
function wagner_residual(dx, x, y, p, t; C1, C2, eps1, eps2)
    # extract state rates
    dλ1, dλ2 = dx
    # extract states
    λ1, λ2 = x
    # extract inputs
    u, v, ω = y
    # extract parameters
    a, b, a0, α0, cd0, cm0 = p
    # return residual
    return SVector(
        dλ1 + eps1*u/b*λ1 - C1*eps1*u/b*(v + (b/2-a*b)*ω - u*α0),
        dλ2 + eps2*u/b*λ2 - C2*eps2*u/b*(v + (b/2-a*b)*ω - u*α0)
    )
end

# state jacobian function
function wagner_state_jacobian(dx, x, y, p, t; eps1, eps2)
    # extract inputs
    u, v, ω = y
    # extract parameters
    a, b, a0, α0, cd0, cm0 = p
    # return jacobian of residual with respect to the states
    return @SMatrix [eps1*u/b 0; 0 eps2*u/b]
end

# input jacobian function
function wagner_input_jacobian(dx, x, y, p, t; C1, C2, eps1, eps2)

    λ1, λ2 = x
    u, v, ω = y
    a, b, a0, α0, cd0, cm0 = p

    tmp1 = v/b + (1/2-a)*ω - 2*α0*u/b
    λ1dot_u = eps1/b*λ1 - C1*eps1*tmp1
    λ2dot_u = eps2/b*λ2 - C2*eps2*tmp1

    tmp2 = u/b
    λ1dot_v = -C1*eps1*tmp2
    λ2dot_v = -C2*eps2*tmp2

    tmp3 = u*(1/2-a)
    λ1dot_ω = -C1*eps1*tmp3
    λ2dot_ω = -C2*eps2*tmp3

    return @SMatrix [λ1dot_u λ1dot_v λ1dot_ω; λ2dot_u λ2dot_v λ2dot_ω]
end

# parameter jacobian function
function wagner_parameter_jacobian(dx, x, y, p, t; C1, C2, eps1, eps2)

    λ1, λ2 = x
    u, v, ω = y
    a, b, a0, α0, cd0, cm0 = p

    λ1dot_a = C1*eps1*u*ω
    λ1dot_b = -eps1*u/b^2*λ1 + C1*eps1/b^2*(u*v - u^2*α0)
    λ1dot_a0 = 0
    λ1dot_α0 = C1*eps1*u^2/b
    λ1dot_cd0 = 0
    λ1dot_cm0 = 0

    λ2dot_a = C2*eps2*u*ω
    λ2dot_b = -eps2*u/b^2*λ2 + C2*eps2/b^2*(u*v - u^2*α0)
    λ2dot_a0 = 0
    λ2dot_α0 = C2*eps2*u^2/b
    λ2dot_cd0 = 0
    λ2dot_cm0 = 0

    return @SMatrix [
        λ1dot_a λ1dot_b λ1dot_a0 λ1dot_α0 λ1dot_cd0 λ1dot_cm0;
        λ2dot_a λ2dot_b λ2dot_a0 λ2dot_α0 λ2dot_cd0 λ2dot_cm0;
        ]
end

# convenience function for defining this model's state vector
wagner_set_states!(x; lambda) = x .= lambda

# convenience function for defining this model's input vector
function wagner_set_inputs!(y; u, v, omega)
    y[1] = u
    y[2] = v
    y[3] = omega
    return y
end

# convenience function for defining this model's parameter vector
function wagner_set_parameters!(p; a, b, a0, alpha0, cd0, cm0)
    p[1] = a
    p[2] = b
    p[3] = a0
    p[4] = alpha0
    p[5] = cd0
    p[6] = cm0
    return p
end

# convenience function for separating this model's state vector
wagner_separate_states(x) = (lambda = x,)

# convenience function for separating this model's input vector
wagner_separate_inputs(y) = (u=y[1], v=y[2], omega=y[3])

# convenience function for separating this model's parameter vector
wagner_separate_parameters(p) = (a=p[1], b=p[2], a0=p[3], alpha0=p[4], cd0=p[5], cm0=p[6])

# aerodynamic loads per unit span
function wagner_loads(a, b, ρ, c, a0, α0, cd0, cm0, C1, C2, u, v, ω, vdot, ωdot, λ1, λ2)
    # circulatory load factor
    tmp1 = a0*ρ*u*b
    # non-circulatory load factor
    tmp2 = pi*ρ*b^3
    # constant based on geometry
    d = b/2 - a*b
    # Wagner's function at t = 0.0
    ϕ0 = 1 - C1 - C2
    # Velocity Magnitude (squared)
    V2 = u^2 + v^2
    # Mach Number (squared)
    M2 = V2/c^2
    # Prandtl-Glauert correction factor
    beta = sqrt(1 - min(0.99, M2))
    # normal force at reference point
    N = tmp1*((v + d*ω - u*α0)*ϕ0 + λ1 + λ2) + tmp2*(vdot/b + u/b*ω - a*ωdot)
    # axial force at reference point
    A = -a0*ρ*b*((v + d*ω - u*α0)*ϕ0 + λ1 + λ2)^2
    # moment at reference point
    M = -tmp2*(vdot/2 + u*ω + b*(1/8 - a/2)*ωdot) + 2*ρ*b^2*u^2*cm0 + (b/2 + a*b)*N
    # apply compressibility correction
    N = N / beta
    A = A / beta
    M = M / beta
    # add skin friction drag
    A += ρ*b*u^2*cd0
    # return loads
    return SVector(N, A, M)
end


__precompile__()

using Ensemblearn

export
    DecisionTree,
    fit!,
    predict,
    predict_probs,
    print_tree

# Based on https://github.com/eriklindernoren/ML-From-Scratch/blob/master/mlfromscratch/supervised_learning/decision_tree.py

mutable struct DecisionNode
    feature_i::Union{Int64, Nothing}          # Index for the feature that is tested
    threshold::Union{Float64, Nothing}          # Threshold value for feature
    value#::Union{Float64, Nothing}                  # Value if the node is a leaf in the tree
    true_branch::Union{DecisionNode,Nothing}      # 'Left' subtree
    false_branch::Union{DecisionNode, Nothing}    # 'Right' subtree

    function DecisionNode(;feature_i=nothing::Union{Int64, Nothing}, threshold=nothing::Union{Float64, Nothing},
                          value=nothing, true_branch=nothing::Union{DecisionNode,Nothing}, false_branch=nothing::Union{DecisionNode, Nothing})
        new(feature_i, threshold, value, true_branch, false_branch)
    end
end

# Super class of ClassificationTree
mutable struct DecisionTree
    # Minimum n of samples to justify split
    min_samples_split::Int64
    # The minimum impurity to justify split
    min_impurity::Float64
    # The maximum depth to grow the tree to
    max_depth::Int64
    # Impurity method to use on information gain calculation
    impurity::String
    # Function to determine prediction of y at leaf
    #_leaf_value_calculation = None
    root  # Root node in dec. tree
    classes

    function DecisionTree(min_samples_split=2::Int64, min_impurity=1e-7::Float64, max_depth=10::Int64,
                          impurity="gini"::String, classes=nothing)
        # TODO: here assert impurity ?
        new(min_samples_split, min_impurity, max_depth, impurity, nothing, classes)
    end
end

impurity_methods = Dict("entropy" => entropy, "gini" => gini_index, "cerr" => classification_error)

function calculate_information_gain(y::Array{T,2}, y1::Array{T2,1}, y2::Array{T2,1}, impurity="gini"::String) where{T <: Real, T2 <: Real}  # TODO: y::Array{T,1}

    @assert haskey(impurity_methods, impurity)
    i = impurity_methods[impurity]

    # Calculate information gain
    p = length(y1) / length(y)
    info_gain = i(y) - p * i(y1) - (1 - p) * i(y2)

    return info_gain
end

function fill_dict!(d, ks, v=0)
    for k in ks
        if !haskey(d, k)
            d[k] = v;
        end
    end
end

# TODO: partial fit, by fingerprinting samples to add counts (it is necessary to fingerprint?)

function fit!(tree::DecisionTree, x::Array{T,2}, y::Array{T2,1}; current_depth=0::Int64) where {T <: Real, T2 <: Real}
    """ Recursive method which builds out the decision tree and splits x and respective y
    on the feature of x which (based on impurity) best separates the data"""

    largest_impurity = 0
    best_criteria = nothing    # Feature index and threshold
    best_sets = nothing        # Subsets of the data

    # Check if expansion of y is needed
    # TODO: is this necesary?
    if ndims(y) == 1
        y = reshape(y, length(y), 1)
    end

    # Add y as last column of x
    xy = hcat(x, y)

    if tree.classes == nothing
        tree.classes = unique(y)
    end

    n_samples, n_features = size(x)

    if (n_samples >= tree.min_samples_split) && (current_depth <= tree.max_depth)
        # Calculate the impurity for each feature
        for feature_i in 1:n_features
            # All values of feature_i
            feature_values = x[:, feature_i]
            unique_values = unique(feature_values)

            # Iterate through all unique values of feature column i and
            # calculate the impurity
            for threshold in unique_values
                # Divide X and y depending on if the feature value of X at index feature_i
                # meets the threshold

                # TODO: just get indexes to avoid mixing x and y types in a single array
                xy1, xy2 = divide_on_feature(xy, feature_i, threshold)

                if (size(xy1,1) > 0) && (size(xy2,1) > 0)
                    # Select the y-values of the two sets
                    y1 = xy1[:, end]
                    y2 = xy2[:, end]

                    # Calculate information gain
                    # TODO: impurity (general method) instead of information gain (particular)
                    impurity = calculate_information_gain(y, y1, y2, tree.impurity)

                    # If this threshold resulted in a higher information gain than previously
                    # recorded save the threshold value and the feature
                    # index
                    if impurity > largest_impurity
                        largest_impurity = impurity
                        best_criteria = Dict("feature_i"=> feature_i, "threshold"=> threshold)
                        best_sets = Dict(
                            "leftx"=> xy1[:, 1:end-1],   # X of left subtree
                            "lefty"=> y1,   # y of left subtree
                            "rightx"=> xy2[:, 1:end-1],  # X of right subtree
                            "righty"=> y2   # y of right subtree
                        )
                    end
                end
            end
        end
    end

    #tree.one_dim = size(y, 1) == 1
    #tree.loss = nothing

    if largest_impurity > tree.min_impurity
        # Build subtrees for the right and left branches
        true_branch = DecisionTree(tree.min_samples_split, tree.min_impurity, tree.max_depth, tree.impurity, tree.classes)
        false_branch = DecisionTree(tree.min_samples_split, tree.min_impurity, tree.max_depth, tree.impurity, tree.classes)

        fit!(true_branch, best_sets["leftx"], best_sets["lefty"], current_depth=current_depth + 1)
        fit!(false_branch, best_sets["rightx"], best_sets["righty"], current_depth=current_depth + 1)

        tree.root = DecisionNode(feature_i=best_criteria["feature_i"], threshold=best_criteria["threshold"],
                                 true_branch=true_branch.root, false_branch=false_branch.root)
    else
        # We're at leaf => determine value
        leaf_value = count_distinct(y)
        fill_dict!(leaf_value, tree.classes)

        tree.root = DecisionNode(value=leaf_value)
    end
end

function get_ratios_dict(d)
    n = sum(values(d))
    return Dict([(x[1], x[2]/n) for x in d])
end

function likelihood(n, x, delta=1e-4)
    # TODO: test this!
    # From https://www.datascience.com/blog/introduction-to-bayesian-inference-learn-data-science-tutorials
    """
    likelihood function for a binomial distribution

    n: [int] the number of experiments
    x: [int] the number of successes
    theta: [float] the proposed probability of success
    """
    lk = theta ->  (factorial(n) / (factorial(x) * factorial(n - x))) * (theta ^ x) * ((1 - theta) ^ (n - x))

    probs = 0:delta:1

    likelihoods = map(lk, probs)

    return probs[argmax(likelihoods)]
end

function predict_value(model::DecisionTree, x::Array{T,1}, node=nothing::Union{DecisionNode, Nothing};
                       get_probs=false::Bool) where T <: Real
    """ Do a recursive search down the tree and make a prediction of the data sample by the
        value of the leaf that we end up at """

    if node == nothing
        node = model.root
    end

    # If we have a value (i.e we're at a leaf) => return value as the prediction
    if node.value != nothing
        if get_probs == true
            v = get_ratios_dict(node.value)
        else
            v = findmax(node.value)[2]
        end

        return v
    end

    # Choose the feature that we will test)
    feature_value = x[node.feature_i]

    # Determine if we will follow left or right branch
    branch = node.false_branch
    if feature_value isa Number
        if feature_value >= node.threshold
            branch = node.true_branch
        end
    elseif feature_value == node.threshold
        branch = node.true_branch
    end

    # Test subtree
    return predict_value(model, x, branch, get_probs=get_probs)
end

function predict(model::DecisionTree, x::Array{T,2}) where T <: Real
    """ Classify samples one by one and return the set of labels """
    pred = []

    for i in 1:size(x, 1)
        y  = predict_value(model, x[i,:], get_probs=false)
        push!(pred, y)
    end

    pred
end

function predict_probs(model::DecisionTree, x::Array{T,2}) where T <: Real
    """ Classify samples one by one and return the set of labels """
    pred = Dict{Any,Array{Float64,1}}()

    for i in 1:size(x, 1)
        y  = predict_value(model, x[i,:], get_probs=true)

        for kv in y
            if !haskey(pred, kv[1])
                pred[kv[1]] = []
            end
            push!(pred[kv[1]], kv[2])
        end
    end

    pred
end

function print_tree(tree::DecisionTree, node=nothing::Union{DecisionNode,Nothing}; indent=" "::String)
    """ Recursively print the decision tree """
    if node == nothing
        node = tree.root
    end

    # If we're at leaf => print the label
    if node.value != nothing
        println(node.value)
    # Go deeper down the tree
    else
        # Print test
        # TODO: print column name when trees are fitted with DFs
        println("Feature_$(node.feature_i):$(node.threshold)?")
        # Print the true scenario
        println("$(indent)T->")
        print_tree(tree, node.true_branch, indent=indent*indent)
        # Print the false scenario
        println("$(indent)F->")
        print_tree(tree, node.false_branch, indent=indent*indent)
    end
end