#const CIS_LUT = SVector{64}(cis.((0:63) / 64 * 2π))
#function cis_fast(x)
#    @inbounds CIS_LUT[(floor(Int, x / 2π * 64) & 63) + 1]
#end

"""
$(SIGNATURES)

Get sine approximation very fast. It is valid between -π and π.
```julia-repl
julia> sin_vfast(π / 4)
```
"""
@inline function sin_vfast(x::T) where T <: Real
    B = T(4 / π)
    C = T(-4 / (π * π))
    B * x + C * x * abs(x)
end

"""
$(SIGNATURES)

Get cosine approximation very fast. It is valid between -π and π.
```julia-repl
julia> cos_vfast(π / 4)
```
"""
@inline function cos_vfast(x::T) where T <: Real
    x += T(π / 2)
    x -= (x > T(π)) * T(2π)
    sin_vfast(x)
end

"""
$(SIGNATURES)

Get sine approximation fast. It is valid between -π and π.
```julia-repl
julia> sin_fast(π / 4)
```
"""
@inline function sin_fast(x::T) where T <: Real
    P = T(0.225)
    y = sin_vfast(x)
    P * (y * abs(y) - y) + y
end

"""
$(SIGNATURES)

Get cosine approximation fast. It is valid between -π and π.
```julia-repl
julia> cos_fast(π / 4)
```
"""
@inline function cos_fast(x::T) where T <: Real
    x += T(π / 2)
    x -= (x > T(π)) * T(2π)
    sin_fast(x)
end

"""
$(SIGNATURES)

Get \exp(iz) approximation fast. It is valid between -π and π.
```julia-repl
julia> cos_fast(π / 4)
```
"""
function cis_fast(x)
    complex(cos_fast(x), sin_fast(x))
end

"""
$(SIGNATURES)

Get \exp(iz) approximation very fast. It is valid between -π and π.
```julia-repl
julia> cis_vfast(π / 4)
```
"""
function cis_vfast(x)
    complex(cos_vfast(x), sin_vfast(x))
end

"""
$(SIGNATURES)

Get \exp(iz) approximation fast. It is valid between -π and π.
```julia-repl
julia> get_carrier_fast_unsafe(π / 4)
```
"""
@inline function get_carrier_fast_unsafe(x)
    cis_fast(x)
end

"""
$(SIGNATURES)

Get \exp(iz) approximation very fast. It is valid between -π and π.
```julia-repl
julia> get_carrier_vfast_unsafe(π / 4)
```
"""
@inline function get_carrier_vfast_unsafe(x)
    cis_vfast(x)
end


using DSP
using FFTW

"Make window function in frequenVV domain with 3x3 size for crop=1."
function make_freq_window(sz, crop=1; option1=3.0, window_type="kaiser")
    # Ref: https://github.com/miaosiSari/Regridding/blob/master/get_window.m
    if window_type == "hanning"
        vec_ = DSP.hanning(sz)
    elseif window_type == "kaiser"
        vec_ = DSP.kaiser(sz, option1)
    end

    ws = vec_ * vec_'
    wf = fftshift(fft(ifftshift(ws)))

    if Bool(sz & 1)
        wf_crop = wf[div(sz+1,2)-crop:div(sz+1,2)+crop, div(sz+1,2)-crop:div(sz+1,2)+crop]
    else
        wf_crop = wf[div(sz,2)-crop:div(sz+1,2)+crop, div(sz+1,2)-crop:div(sz+1,2)+crop]
    end

    return wf_crop, ws
end

"""
    recon2d_gridrec(p::Array{T, 3}, angles::Array{T, 1}) where {T<:AbstractFloat}

Reconstruct based on [1] gridrec from p

# Args
- p [nangles x nslices x detcount] : (for the moment) detcount should be a power of 2

[1] : Marone, F., Stampanoni, M., 2012. Regridding reconstruction algorithm
    for real-time tomographic imaging. Journal of Synchrotron Radiation
"""
function recon2d_gridrec(p::Array{T, 3}, angles::Array{T, 1}) where {T<:AbstractFloat}
    nangles, nslices, detcount = size(p)

    if detcount % 2 != 0
        println("!TODO: Odd number of detector size can have problems for the moment.")
    end

    #------------- step1 padding
    # we assume that detcount is a power of 2
    # pad 2 times
    sz_pad = Int(2^ceil(log(detcount+1)/log(2)))
    sz_half = sz_pad >> 1 # even number
    padding = sz_pad - detcount
    padding_half = padding >> 1

    UU = zeros(nangles, sz_half)
    VV = zeros(nangles, sz_half)

    tau = cat(Array(1:2:Int(sz_pad/2-1)), Array(Int(sz_pad/2-1):-2:0), dims=1)
    spatial_filter = zeros(sz_pad)
    spatial_filter[1] = 0.25
    spatial_filter[2:2:end] .= -1.0 ./ (pi * tau).^2
    ramlak = fftshift( fft(spatial_filter) )
    # ramlak = fftshift(fft(ifftshift(spatial_filter))) # even function

    UU = zeros(Int, sz_pad)
    VV = zeros(Int, sz_pad)

    sz_window = 9
    sz_window_half = div(sz_window-1, 2)
    kernel, kernel_spatial = make_freq_window(sz_pad, sz_window_half)
    # ws ./= sum(ws)
    kernel_idx = CartesianIndices(kernel)

    # assume that H=detcount, W=detcount
    H, W = detcount, detcount
    rec_img = zeros(H,W,nslices)

    # 1:sz_pad - div(detcount, 2) + 0.5 - 1.0
    offset = div(sz_pad, 2) + 0.5
    dtheta = angles[2]-angles[1]
    halfsz = Int(floor(sz_window/2))

    UU0 = collect(1:sz_pad) .- offset
    one_index = CartesianIndex(1, 1)

    Threads.@threads for slice=1:nslices
        println("slice no. $slice")
        # (optional) add the current slice and the next slice
        # if slice < nslices-1
        #     p_pad[:,1:detcount] .= view(p,:,slice,:) + view(p,:,slice+1,:)
        # else
        #     p_pad[:,1:detcount] .= view(p,:,slice,:)
        # end

        # for multithreading, give up saving memory allocations
        Q = zeros(ComplexF64, sz_pad, sz_pad)
        q = zeros(sz_pad, sz_pad)
        p_pad = zeros(nangles, sz_pad)
        # fill!(Q, 0.0)
        # fill!(p_pad, 0.0)

        for i=1:detcount
            p_pad[:, padding_half+1:end-padding_half] .= view(p,:,slice,:)
        end

        P = fftshift(fft(ifftshift(p_pad), 2));
        P .*= reshape(ramlak, (1,length(ramlak)))
        out_conv = zeros(ComplexF64, sz_pad+sz_window-1, sz_pad+sz_window-1)

        for iang=1:nangles
            cos_ = cos(angles[iang])
            sin_ = sin(angles[iang])

            #---------------------------
            # convolution of P[nangles x detcount] and C
            # continuous H = F * W (4)
            # discrete   Q = P * C (6)
            #---------------------------
            UU .= Int.(round.( UU0 .* cos_ .+ offset ) )
            # UU .-= sz_window_half
            UU[UU.>sz_pad] .-= sz_pad
            UU[UU.<1] .+= sz_pad

            VV .= Int.(round.( UU0 .* sin_ .+ offset ) )
            # VV .-= sz_window_half
            VV[VV.>sz_pad] .-= sz_pad
            VV[VV.<1] .+= sz_pad

            P_idx = CartesianIndex.(VV,UU)
            fill!(out_conv, 0.0)

            for (idx_detcount, ci_P) in enumerate(P_idx)
                Pf = P[iang, idx_detcount]
                # println(ci_P)
                @simd for ci_C in kernel_idx
                     @inbounds out_conv[ci_P+ci_C-one_index] += Pf * kernel[ci_C]
                end
            end

            Q .+= out_conv[halfsz+1:end-halfsz, halfsz+1:end-halfsz]
        end

        Q .*= dtheta / (detcount^2)
        q .= real( fftshift(ifft!(ifftshift(Q))) ./ kernel_spatial ) #./ kernel_weights )
        # q .*= size(Q, 2)
        # print(size(Q))
        # q[q .< 0] .= 0
        # q[q .> 1] .= 1

        rec_img[:,:,slice] .= reverse( q[padding_half+1:end-padding_half, padding_half+1:end-padding_half], dims=1 )
    end

    return rec_img
end

"Gridrec for 2D image"
function recon2d_gridrec(p::Array{T, 2}, angles::Array{T, 1}) where {T<:AbstractFloat}
    p_3d_ = reshape(p, size(p)..., 1)
    p_3d = permutedims(p_3d_, 1, 3, 2)
    rec = recon2d_gridrec(p_3d, angles)
    return rec[:,:,1]
end


using Plots
using Distributions

μfast = 0
σfast = 1
dfast = LogNormal(μfast, σfast)
x = rand(dfast, 1000)
histogram(x, dpi=600, xlabel="Page Load Time (s)", ylabel="Number of Loads", title="Fast User Experience", legend = false)
png("images/goodpageload.png")

dslow = LogNormal(3, 0.4)
x = rand(dslow, 1000)
histogram(x, dpi=600, xlabel="Page Load Time (s)", ylabel="Number of Loads", title="Slow User Experience", legend = false)
png("images/badpageload.png")

users = zeros(100)
usercount = zeros(100)

for i in 1:100
    while true
        if i < 90
            users[i] += rand(dfast)
        else
            users[i] += rand(dslow)
        end
        usercount[i] += 1.0
        if users[i] > 3600
            break
        end
    end
end

pageloads = rand(dfast, Int64(sum(usercount[1:90])))
append!(pageloads, rand(dslow, Int64(sum(usercount[91:100]))))
pageloads = log.(pageloads)
histogram(pageloads, label = "Simulated Page Loads", xlabel="Logarithm of Page Load Times", ylabel="Number of Page Loads", dpi=600)
vline!([μfast], label="Metric 1", linewidth=2)
vline!([log(mean(exp.(pageloads)))], label="Metric 2", linewidth=2)
vline!([log(median(users./usercount))], label="Metric 3", linewidth=2)
vline!([log(mean(users./usercount))], label="Metric 4", linewidth=2)
png("images/examplesimulation.png")

for k in 75:99
    users = zeros(100)
    usercount = zeros(100)

    for i in 1:100
        while true
            if i < k
                users[i] += rand(dfast)
            else
                users[i] += rand(dslow)
            end
            usercount[i] += 1.0
            if users[i] > 3600
                break
            end
        end
    end

    pageloads = rand(dfast, Int64(sum(usercount[1:k])))
    append!(pageloads, rand(dslow, Int64(sum(usercount[k+1:100]))))
    pageloads = log.(pageloads)
    if k==75
        scatter([k], [exp(μfast)], label="Metric 1", linewidth=2, color="red", dpi=600, xlabel="Percentage of Users with fast experience", ylabel="Metric Calculated Page Load Time (s)")
        scatter!([k], [mean(exp.(pageloads))], label="Metric 2", linewidth=2, color="black")
        scatter!([k], [median(users./usercount)], label="Metric 3", linewidth=2, color="green")
        scatter!([k], [mean(users./usercount)], label="Metric 4", linewidth=2, color="blue")
    else
        scatter!([k], [exp(μfast)], linewidth=2, color="red", label=false)
        scatter!([k], [mean(exp.(pageloads))], linewidth=2, color="black", label=false)
        scatter!([k], [median(users./usercount)], linewidth=2, color="green", label=false)
        scatter!([k], [mean(users./usercount)], linewidth=2, color="blue", label=false)
    end
end
png("images/varyingusers.png")


# Average waves are usually numerical solutions of the ensemble average wave equations. Here we mostly deal with the scenario where a halfspace filled with particles are ensemble averaged. The resulting govering equations are a 1D Fredholm integral equation.

export intergrand_kernel, discrete_wave_system, discrete_system, discrete_system_radial, integrate_S, integrate_B
export integration_scheme, trapezoidal_scheme, simpson_scheme
export discretewave_error, x_mesh

include("discrete_waves.jl")

include("planewaves/wave_types.jl")
include("planewaves/discrete_systems.jl")
include("planewaves/integral_form.jl")
include("planewaves/reflection.jl")

include("regular/discrete_systems.jl")
include("regular/material_scattering_coefficients.jl")

include("numerical_integration.jl")


using JuMP;
using Ipopt;
using DataFrames;

my_table0 = readtable( "C:/Users/mdzhd/Documents/partial_linear/lin_12_07_2016/newdata.csv");

my_table = convert( DataFrame, my_table0 );

( N, K1 ) = size( my_table );

m = Model(solver=IpoptSolver());

@defVar( m, 0.0 <= b[1:(K1-1)] <= 100.0 )

@NLobjective( m, Min, sum{ ( my_table[i,1] - sum{ my_table[i,j] * b[ j - 1] , j in 2:K1} )^2 , i in 1:N} )

statusM = solve( m )

df = DataFrame( beta = getvalue( b ) )

writetable( "C:/Users/mdzhd/Documents/partial_linear/lin_12_07_2016/solution_lin.csv", df)

# ----------------------- LOGISTIC --------------------------------

using JuMP, Ipopt, DataFrames
my_table = readtable( "/home/iriadmin/g/JStack/src/tst/newdata.csv");
( N, K1 ) = size( my_table );
m = Model(solver=IpoptSolver());
@defVar( m, 0.0 <= b[1:(K1-1)] <= 100.0 )
@NLobjective( m, Min, sum{ ( my_table[i,1] - sum{ my_table[i,j] * b[ j - 1] , j in 2:K1} )^2 , i in 1:N} )
statusM = solve( m )
df = DataFrame( beta = getvalue( b ) )

dfd = readtable( "/home/iriadmin/g/JStack/src/tst/logreg.csv")
dfd=dfd[setdiff(names(dfd),[:x0])]
mm = ModelMatrix(ModelFrame(y ~ 1 + x1 + x2 + x3 + x4 + x6 + x7, dfd))
g = glm(y ~ 1 + x1 + x2 + x3 + x4 + x6 + x7, dfd,  Bernoulli())   #m = glm(y ~ 1 + x1 + x2 + x3 + x4 + x6 + x7, dfd, Poisson())
a = convert(Array,dfd) #array(dfd[:, 1:end])
B = zeros(length(mm.m[1,2:end]))
x = a[:,2:end]
y = a[:,1]
# sum( -1 * y .* (x*beta) + log(1+exp((x*beta))) )

m = Model(solver=IpoptSolver());
@variable(m, B[i=1:6], start=0)

@defVar( m, 0.0 <= b[1:(K1-1)] <= 100.0 )
@NLobjective( m, Min, sum{ ( my_table[i,1] - sum{ my_table[i,j] * b[ j - 1] , j in 2:K1} )^2 , i in 1:N} )
statusM = solve( m )
df = DataFrame( beta = getvalue( b ) )


# Computational cores of BBOB separable functions f1-f5

# f1 = Sphere function
sphere_function(x::AbstractVector{<:Number}) = sum(abs2, x, 1)


using Sigma
using Base.Test

X = uniform(0,10)
Y = uniform(0,10)
Z = uniform(0,10)

formula = 3X + 2Y - Z >= 4
x,y,z = rand((X, Y, Z), formula)
@test 3x + 2y - z >= 4
@test 0 < x < 10
@test 0 < y < 10
@test 0 < z < 10


# maybe put this script with the other one together?
export sudoku_1, sudoku_2
sudoku_1 = [0 0 4 7 2 0 9 0 0; 0 3 9 0 0 8 0 0 5; 0 0 1 5 0 6 0 0 4; 0 4 0 0 1 0 5 2 0; 0 2 8 0 5 0 1 7 0; 0 1 6 0 3 0 0 9 0; 4 0 0 9 0 1 3 0 0; 1 0 0 3 0 0 8 4 0; 0 0 7 0 8 5 6 0 0];

sudoku_2 = [5 3 0 0 7 0 0 0 0; 6 0 0 1 9 5 0 0 0; 0 9 8 0 0 0 0 6 0; 8 0 0 0 6 0 0 0 3; 4 0 0 8 0 3 0 0 1; 7 0 0 0 2 0 0 0 6;0 6 0 0 0 0 2 8 0; 0 0 0 4 1 9 0 0 5; 0 0 0 0 8 0 0 7 9];


using BenchmarkTools
using SPH

function M6_hardcoded(q)
    if q < 0.0
        return 0.0
    elseif q < 1.0
        return (3 - q)^5 - 6*(2 - q)^5 + 15*(1 - q)^5
    elseif q < 2.0
        return (3 - q)^5 - 6*(2 - q)^5
    elseif q < 3.0
        return (3 - q)^5
    else
        return 0.0
    end
end

println("Kernel function benchmarks")
println("Hardcoded M6")
@btime $M6_hardcoded(x) setup=(x=3*rand())

println("Programmatic M6 (and 1st and second derivatives")
@btime $M6[1](x) setup=(x=3*rand())
@btime $(differentiate(M6[1]))(x) setup=(x=3*rand())
@btime $(differentiate(M6[1], 2))(x) setup=(x=3*rand())


using DelimitedFiles
using Printf

function count_trees(sf, dh, dv)
    nrows = size(sf)[1]
    ncols = length(sf[1,1][:])

    pos = 1
    ntree = 0

    for i in 1+dv:dv:nrows
        # println(i)
        # println(sf[i,1][:])

        pos = pos + dh
        if pos > ncols
            pos = pos - ncols
        end

        if sf[i,1][pos] == '#'
            ntree += 1
        end
    end
    return ntree
end

slope = readdlm("./input.dat")

println("Part a:\t", count_trees(slope, 3, 1))

a = count_trees(slope, 1, 1)
b = count_trees(slope, 3, 1)
c = count_trees(slope, 5, 1)
d = count_trees(slope, 7, 1)
e = count_trees(slope, 1, 2)

println("===================")
println("R1, D1\t", a)
println("R3, D1\t", b)
println("R5, D1\t", c)
println("R7, D1\t", d)
println("R1, D2\t", e)
println("===================")
println("Product: ", a*b*c*d*e)


function demoHMMburglar()

    # setup a grid representation of the rooom
    Gx = 5 # two dimensional grid size
    Gy = 5
    H = Gx * Gy # number of states on grid

    # matrix representing the possible states of the system
    st = reshape(1:H, Gx, Gy) # assign each grid point a state

    # make a deterministic state transition matrix HxH on a 2D grid:
    phgh = zeros(H, H) # transition from state j to state i
    for x = 1:Gx
        for y = 1:Gy
            # from the current state-cell (j coord in state transition matrix)
            # to next state-cell on (i coord in state transition matrix):
            #   the next row, same column
            if validgridposition(x + 1, y, Gx, Gy) # sample for x = 1, y = 2
                phgh[st[x + 1, y], st[x, y]] = 1 # 2,2=7  1,2=6
            end
            #   the previous row, same column
            if validgridposition(x - 1, y, Gx, Gy)
                phgh[st[x - 1, y], st[x, y]] = 1 # 0,2  1,2
            end
            #   the same row, next column
            if validgridposition(x, y + 1, Gx, Gy)
                phgh[st[x, y + 1], st[x, y]] = 1 # 1,3=11 1,2=6
            end
            #   the same row, previous column
            if validgridposition(x, y - 1, Gx, Gy)
                phgh[st[x, y - 1], st[x, y]] = 1 # 1,1=1 1,2=6
            end
        end
    end
    # conditional distribution from state transition matrix
    phghm = condp(phgh) # matrix with sum(phghm, 1) = 1 with phghm[i, j] = p(hg=i | hm=j)
    ph1=condp(ones(H,1)) # initialise probabilities for the states of the hidden variable at timestep 1
    pvgh=zeros(4,H) # initialise emision matrix

    pv1gh = 0.01 * ones(1,H); r = randperm(H); pv1gh[r[1:10]] = 0.9; # Creaks in 10 randomly chosen cells
    pv2gh = 0.01 * ones(1,H); r = randperm(H); pv2gh[r[1:10]] = 0.9; # Bumps in 10 randomly chosen cells

    PyPlot.figure()
    axc = PyPlot.subplot(2, 1, 1)
    axc[:set_title]("creaks layout")
    PyPlot.imshow(reshape(pv1gh, Gx, Gy), cmap="bone");
    axb = PyPlot.subplot(2, 1, 2)
    axb[:set_title]("bumps layout")
    PyPlot.imshow(reshape(pv2gh, Gx, Gy), cmap="bone");
    map([axb, axc]) do axesfig
        PyPlot.setp(axesfig[:get_xticklines](), visible=false)
        PyPlot.setp(axesfig[:get_xticklabels](), visible=false)
        PyPlot.setp(axesfig[:get_yticklines](), visible=false)
        PyPlot.setp(axesfig[:get_yticklabels](), visible=false)
    end

    # Form the joint distribution p(v|h)=p(v1|h)p(v2|h)
    # v = (v1, v2) and v1 and v2 are independent given h
    vv = zeros(4, 2)
    for i = 1:4
        pvgh[1, :] =  pv1gh .* pv2gh; vv[1, :] = [1 1]; # p(v1=1|h)*p(v2=1|h)
        pvgh[2, :] =  pv1gh.*(1-pv2gh); vv[2, :] = [1 2]; # p(v1=1|h)*p(v2=1|h)
        pvgh[3, :] =  (1-pv1gh).*pv2gh; vv[3, :] = [2 1]; # p(v1=1|h)*p(v2=1|h)
        pvgh[4, :] =  (1-pv1gh).*(1-pv2gh); vv[4, :] = [2 2]; # p(v1=1|h)*p(v2=1|h)
    end

    # draw some random samples:
    T=10
    h = zeros(Integer, 1, T) # holds the state value for the hidden variable at a specific timestep
    v = zeros(Integer, 1, T) # holds the state value for the visible variable at a specific timestep

    h[1]=randgen(ph1) # initialize the hidden variable @t=1 with a random state based on ph1 distribution
    v[1]=randgen(pvgh[:, h[1]]) # initialize the visible variable @t=1 with a random state based on pvgh( vg | h@t=1)

    for t=2:T
        h[t]=randgen(phghm[:, h[t-1]]) # set the hidden variable state @t based on h@t-1 using the transition matrix
        v[t]=randgen(pvgh[:, h[t]]) # set the visible variable state @t based on h@t using the emission matrix
    end

    # Perform inference based on the observed v:
    (alpha, loglik) = HMMforward(v, phghm, ph1, pvgh) # filtering
    phtgV1t = alpha # filtered posterior - infer the current hidden state p(ht | v_1:t)

    phtgV1T = HMMgamma(alpha, phghm) # Smoothed Burglar distribution
    maxstate, logprob = HMMviterbi(v, phghm, ph1, pvgh) # Most likely Burglar path

    PyPlot.figure()
    for t = 1:T
        axg = PyPlot.subplot(5, T, t); PyPlot.imshow(repmat(vv[v[t], :], 2, 1), cmap="bone");
        if t == 2 # used t == 1 or t == 2 for title alignment only
            axg[:set_title]("Creaks and Bumps")
        end
        # add Filtering data row of T images from the previous row offset
        axf = PyPlot.subplot(5, T, T+t);  PyPlot.imshow(reshape(phtgV1t[:, t], Gx, Gy), cmap="bone");
        if t == 1
            axf[:set_title]("Filtering")
        end
        # add Smoothing data row of T images from the previous row offset
        axs = PyPlot.subplot(5, T, 2*T+t);  PyPlot.imshow(reshape(phtgV1T[:, t], Gx, Gy), cmap="bone");
        if t == 1
            axs[:set_title]("Smoothing")
        end
        z=zeros(H,1); z[maxstate[t]]=1;
        # add Viterbi data row of T images from the previous row offset
        axv = PyPlot.subplot(5,T,3*T+t); PyPlot.imshow(reshape(z,Gx,Gy), cmap="bone")
        if t == 1
            axv[:set_title]("Viterbi")
        end
        z = zeros(H,1); z[h[t]] = 1;
        # add true data row of T images from the previous row offset
        axt = PyPlot.subplot(5,T,4*T+t); PyPlot.imshow(reshape(z,Gx,Gy), cmap="bone")
        if t == 2
            axt[:set_title]("True Burglar position")
        end
        map([axg, axf, axs, axv, axt]) do axesfig
            PyPlot.setp(axesfig[:get_xticklines](), visible=false)
            PyPlot.setp(axesfig[:get_xticklabels](), visible=false)
            PyPlot.setp(axesfig[:get_yticklines](), visible=false)
            PyPlot.setp(axesfig[:get_yticklabels](), visible=false)
        end
    end

end


@testset "Kalman filter" begin
    @testset "Time update inplace functions" begin
        x = randn(2)
        F = randn(2,2)
        x_apri = similar(x)
        @test KalmanFilter.calc_apriori_state!(x_apri, x, F) ≈ F * x

        Q = randn(2,2)
        P = randn(2,2)
        FP = similar(P)
        P_apri = similar(P)
        @test KalmanFilter.calc_apriori_covariance!(P_apri, FP, P, F, Q) ≈ F * P * F' .+ Q
    end

    @testset "Measurement update inplace functions" begin

        x = randn(2)
        y = randn(2)
        H = randn(2,2)
        ỹ = similar(y)
        @test KalmanFilter.calc_innovation!(ỹ, H, x, y) ≈ y .- H * x

        H = randn(2,2)
        R = randn(2,2)
        PHᵀ = randn(2,2)
        S = similar(R)
        @test KalmanFilter.calc_innovation_covariance!(S, H, PHᵀ, R) ≈ H * PHᵀ .+ R

        PHᵀ = randn(2,2)
        SL = randn(2,2)
        S = SL*SL'
        S_lu = similar(S)
        K = similar(PHᵀ)
        @test KalmanFilter.calc_kalman_gain!(S_lu, K, PHᵀ, S) ≈ PHᵀ / S

        x = randn(2)
        x_posterior = similar(x)
        K = randn(2,2)
        ỹ = randn(2)
        @test KalmanFilter.calc_posterior_state!(x_posterior, x, K, ỹ) ≈ x .+ K * ỹ

        P = randn(2,2)
        P_posterior = similar(P)
        PHᵀ = randn(2,2)
        K = randn(2,2)
        @test KalmanFilter.calc_posterior_covariance!(P_posterior, P, PHᵀ, K) ≈ P .- PHᵀ * K'

    end

    @testset "Time update" begin

        x = [1., 1.]
        P = [1. 0.; 0. 1.]
        F = [1. 0.; 0. 2.]
        Q = [1. 0.; 0. 1.]

        tu = time_update(x, P, F, Q)
        @test get_state(tu) == [1., 2.]
        @test get_covariance(tu) == [2. 0.; 0. 5.]

        tu_inter = KFTUIntermediate(2)
        tu = time_update!(tu_inter, x, P, F, Q)
        @test get_state(tu) == [1., 2.]
        @test get_covariance(tu) == [2. 0.; 0. 5.]
    end

    @testset "Measurement update" begin

        y = [1., 1.]
        x = [1., 1.]
        P = [1. 0.; 0. 1.]
        H = [1. 0.; 0. 1.]
        R = [1. 0.; 0. 1.]

        mu = measurement_update(x, P, y, H, R)
        @test get_state(mu) == [1., 1.]
        @test get_covariance(mu) == [0.5 0.; 0. 0.5]
        @test get_innovation(mu) == [0.0, 0.0]
        @test get_innovation_covariance(mu) == [2.0 0.0; 0.0 2.0]
        @test get_kalman_gain(mu) == [0.5 0.0; 0.0 0.5]

        mu_inter = KFMUIntermediate(2,2)
        mu = measurement_update!(mu_inter, x, P, y, H, R)
        @test get_state(mu) == [1., 1.]
        @test get_covariance(mu) ≈ [0.5 0.; 0. 0.5]
        @test get_innovation(mu) == [0.0, 0.0]
        @test get_innovation_covariance(mu) == [2.0 0.0; 0.0 2.0]
        @test get_kalman_gain(mu) ≈ [0.5 0.0; 0.0 0.5]

        y = 1.
        x = [1., 1.]
        P = [1. 0.; 0. 1.]
        H = [1., 0.]'
        R = 1.

        mu = measurement_update(x, P, y, H, R)
        @test get_state(mu) == [1., 1.]
        @test get_covariance(mu) == [0.5 0.; 0. 1.]
        @test get_innovation(mu) == 0.
        @test get_innovation_covariance(mu) == 2.
        @test get_kalman_gain(mu) == [0.5, 0.]

        H_temp = Matrix(H)
        y_temp = [y]
        R_temp = [R]
        mu_inter = KFMUIntermediate(2,1)
        mu = measurement_update!(mu_inter, x, P, y_temp, H_temp, R_temp)
        @test get_state(mu) == [1., 1.]
        @test get_covariance(mu) ≈ [0.5 0.; 0. 1.]
        @test get_innovation(mu) == [0.]
        @test get_innovation_covariance(mu) ≈ [2.]
        @test get_kalman_gain(mu) ≈ [0.5, 0.]

        y = [1., 1.]
        x = 1.
        P = 1.
        H = [1., 0.]
        R = [1. 0.; 0. 1.]

        mu = measurement_update(x, P, y, H, R)
        @test get_state(mu) == 1.
        @test get_covariance(mu) == 0.5
        @test get_innovation(mu) == [0.0, 1.0]
        @test get_innovation_covariance(mu) == [2.0 0.0; 0.0 1.0]
        @test get_kalman_gain(mu) == [0.5 0.0]

        x_temp = [x]
        P_temp = Matrix(adjoint([P]))
        H_temp = Matrix{eltype(H)}(undef, 2, 1)
        H_temp[1] = H[1]
        H_temp[2] = H[2]
        mu_inter = KFMUIntermediate(1,2)
        mu = measurement_update!(mu_inter, x_temp, P_temp, y, H_temp, R)
        @test get_state(mu) == [1.]
        @test get_covariance(mu) ≈ [0.5]
        @test get_innovation(mu) == [0.0, 1.0]
        @test get_innovation_covariance(mu) == [2.0 0.0; 0.0 1.0]
        @test get_kalman_gain(mu) ≈ [0.5 0.0]
    end
end


using Plots

L=2
function φ(x,y;V=10)
    L=2
    s=0
    for n=1:2:100000
        s=s.+map(z  -> sin(n *π *z/L) ,x).*map( z -> exp(-n *π *z/L)./n,y)
    end
    4s*V/ π
end

Space = zeros(100,100)
x=0:2/(99):2;y=x

length(x)
for i ∈ 1:100  , j ∈ 1:100
    Space[i,j]=φ(x[i],y[j])
end
plot(x,y,Space,linetype=:contourf,title = "Analitica")


# Normal evaluation and normalization

#################################################
#
#   Vector norms
#
#################################################

function vnorm(x::NumericArray, p::Real)
    p > 0 || throw(ArgumentError("p must be positive."))
    p == 1 ? sumabs(x) :
    p == 2 ? sqrt(sumsq(x)) :
    isinf(p) ? maxabs(x) :
    sum(FixAbsPow(p), x) .^ inv(p)
end

vnorm(x::NumericArray) = vnorm(x, 2)

function vnorm!(dst::NumericArray, x::NumericArray, p::Real, dims::DimSpec)
    fill!(dst, zero(eltype(dst)))
    p > 0 || throw(ArgumentError("p must be positive."))
    p == 1 ? sumabs!(dst, x, dims) :
    p == 2 ? map1!(SqrtFun(), sumsq!(dst, x, dims)) :
    isinf(p) ? maxabs!(dst, x, dims) :
    map1!(FixAbsPow(inv(p)), sum!(dst, FixAbsPow(p), x, dims))
end

function vnorm(x::NumericArray, p::Real, dims::DimSpec)
    tt = promote_type(fptype(eltype(x)), fptype(typeof(p)))
    r = Array(tt, Base.reduced_dims(size(x), dims))
    vnorm!(r, x, p, dims)
end

# vnormdiff

function vnormdiff(x::DenseArrOrNum, y::DenseArrOrNum, p::Real)
    p > 0 || throw(ArgumentError("p must be positive."))
    p == 1 ? sumabsdiff(x, y) :
    p == 2 ? sqrt(sumsqdiff(x, y)) :
    isinf(p) ? maxabsdiff(x, y) :
    sumfdiff(FixAbsPow(p), x, y) .^ inv(p)
end

vnormdiff(x::DenseArrOrNum, y::DenseArrOrNum) = vnormdiff(x, y, 2)

function vnormdiff!(dst::NumericArray, x::DenseArrOrNum, y::DenseArrOrNum, p::Real, dims::DimSpec)
    fill!(dst, zero(eltype(dst)))
    p > 0 || throw(ArgumentError("p must be positive."))
    p == 1 ? sumabsdiff!(dst, x, y, dims) :
    p == 2 ? map1!(SqrtFun(), sumsqdiff!(dst, x, y, dims)) :
    isinf(p) ? maxabsdiff!(dst, x, y, dims) :
    map1!(FixAbsPow(inv(p)), sumfdiff!(dst, FixAbsPow(p), x, y, dims))
end

function vnormdiff(x::DenseArrOrNum, y::DenseArrOrNum, p::Real, dims::DimSpec)
    tt = promote_type(fptype(promote_type(eltype(x), eltype(y))), fptype(typeof(p)))
    r = Array(tt, Base.reduced_dims(size(x), dims))
    vnormdiff!(r, x, y, p, dims)
end

#################################################
#
#   Normalization
#
#################################################

# normalize an array as a whole

function normalize!(dst::ContiguousRealArray, x::ContiguousRealArray, p::Real)
    return map!(Multiply(), dst, x, inv(vnorm(x, p)))
end
normalize!(x::ContiguousRealArray, p::Real) = normalize!(x, x, p)
normalize(x::ContiguousRealArray, p::Real) = x * inv(vnorm(x, p))

# normalize along specific dimension

function normalize!(dst::NumericArray, x::NumericArray, p::Real, dims::DimSpec)
    p > 0 || throw(ArgumentError("p must be positive."))
    length(dst) == length(x) || throw(ArgumentError("Inconsistent argument dimensions!"))

    if length(dims) == 1 && dims[1] == 1
        n = succ_length(size(x), 1)
        if p == 1
            for j = 1:n
                xj = view(x,:,j)
                map!(Multiply(), view(dst,:,j), xj, inv(sumabs(xj)))
            end
        elseif p == 2
            for j = 1:n
                xj = view(x,:,j)
                map!(Multiply(), view(dst,:,j), xj, inv(sqrt(sumsq(xj))))
            end
        elseif isinf(p)
            for j = 1:n
                xj = view(x,:,j)
                map!(Multiply(), view(dst,:,j), xj, inv(maxabs(xj)))
            end
        else
            for j = 1:n
                xj = view(x,:,j)
                u = sum(FixAbsPow(p), xj) .^ inv(p)
                map!(Multiply(), view(dst,:,j), xj, inv(u))
            end
        end

    elseif length(dims) == 2 && dims[1] == 1 && dims[2] == 2
        n = succ_length(size(x), 2)
        if p == 1
            for j = 1:n
                xj = view(x,:,:,j)
                map!(Multiply(), view(dst,:,:,j), xj, inv(sumabs(xj)))
            end
        elseif p == 2
            for j = 1:n
                xj = view(x,:,:,j)
                map!(Multiply(), view(dst,:,:,j), xj, inv(sqrt(sumsq(xj))))
            end
        elseif isinf(p)
            for j = 1:n
                xj = view(x,:,:,j)
                map!(Multiply(), view(dst,:,:,j), xj, inv(maxabs(xj)))
            end
        else
            for j = 1:n
                xj = view(x,:,:,j)
                u = sum(FixAbsPow(p), xj) .^ inv(p)
                map!(Multiply(), view(dst,:,:,j), xj, inv(u))
            end
        end

    else
        broadcast!(.*, dst, x, rcp!(vnorm(x, p, dims)))
    end
    dst
end

normalize!(x::ContiguousRealArray, p::Real, dims::DimSpec) = normalize!(x, x, p, dims)
normalize(x::ContiguousRealArray, p::Real, dims::DimSpec) = normalize!(similar(x), x, p, dims)


using PGFPlotsX, BasisFunctions

f = exp

F1 = Expansion( ChebyshevT(10), rand(ChebyshevT(10)))
F2 = Expansion( Fourier(10), rand(Fourier(10)))

for F in (F1,F2)
    Plot(F)

    Plot(F;n=300)

    Axis(F,f)
    Axis(F,F)
    Axis(f,F)
end

Plot(F2;plot_extension=true)
Axis(Plot(F2;plot_complex=true)...)


using JuMP
using Hungarian
using LinearAlgebra
import MathOptInterface as MOI

"""
Approximately solve the QAP by linearizing the problem and solving
the resulting linear integer program.
"""
function qap_linearization(A, B, obj)
	P, _ = adams_johnson_linearization(A, B, obj, integer=true)
	matching = [findfirst(==(1), P[i,:]) for i in 1:size(A,1)]
	return P, matching
end

"""
Approximately solve the QAP by linearizing and relaxing the problem
and solving the resulting LIP with linear programming and rounding
via the Hungarian algorithm.
"""
function qap_lprounding(A, B, obj)
	x, _ = adams_johnson_linearization(A, B, obj, integer=false)

	W = maximum(x) .- x
	P = Hungarian.munkres(W)

	P = Matrix(P .== Hungarian.STAR)
	matching = [findfirst(P[i,:]) for i in 1:size(A,1)]

	return P, matching
end

"""
QAP linearization from "Improved linear programming-based
lower bounds for the quadratic assignment problem" by Adams and Johnson.
"""
function adams_johnson_linearization(A, B, obj; integer::Bool=true)
	N = size(A, 1)

	optimizer = get_optimizer()
	model = Model(optimizer)
	set_silent(model)

	@variable(model, x[1:N,1:N], binary=integer)
	@variable(model, y[1:N,1:N,1:N,1:N], binary=integer)

	@constraint(model, [u=1:N], sum(x[u,:]) == 1)
	@constraint(model, [p=1:N], sum(x[:,p]) == 1)
	@constraint(model, [v=1:N,p=1:N,q=1:N], sum(y[:,p,v,q]) == x[v,q])
	@constraint(model, [u=1:N,v=1:N,q=1:N], sum(y[u,:,v,q]) == x[v,q])
	@constraint(model, [u=1:N,p=1:N,v=1:N,q=1:N], y[u,p,v,q] == y[v,q,u,p])

	if !integer
		@constraint(model, [u=1:N,p=1:N], 0 ≤ x[u,p] ≤ 1)
		@constraint(model, [u=1:N,p=1:N,v=1:N,q=1:N], 0 ≤ y[u,p,v,q] ≤ 1)
	end

	model_obj = (obj == :min) ? MOI.MIN_SENSE : MOI.MAX_SENSE
	@objective(model, model_obj, sum(A[u,v] * B[p,q] * y[u,p,v,q] for u=1:N, p=1:N, v=1:N, q=1:N))

	optimize!(model)

	status = Int(termination_status(model))
	@assert status == 1

	x_sol = value.(x)
	y_sol = value.(y)

	clamp!(x_sol, 0, 1)
	clamp!(y_sol, 0, 1)

	if integer
		x_sol = x_sol .> 0.5
		y_sol = y_sol .> 0.5
	end

	return x_sol, y_sol
end


using RandomMatrices
using Test

@testset "TracyWidom" begin

# CDF lies in correct range
@test all(i->(0<i<1), cdf(TracyWidom, randn(5)))

#Test far outside support
@test cdf(TracyWidom, -10.1) ≈ 0 atol=1e-14
@test cdf(TracyWidom, 10.1) ≈ 1 atol=1e-14

# Test exact values
# See https://arxiv.org/abs/0904.1581
@test cdf(TracyWidom,0,beta=1) ≈ 0.83190806620295 atol=1e-14
@test cdf(TracyWidom,0,beta=2) ≈ 0.96937282835526 atol=1e-14

@test isfinite(rand(TracyWidom, 10))
@test isfinite(rand(TracyWidom, 100))

end # testset


module SolveDSGE
using LinearAlgebra

include("types.jl")
include("extra_functions.jl")
include("vec_to_vech.jl")
include("vech_to_vec.jl")
include("derivative.jl")
include("hessian.jl")
include("newton.jl")
include("doubling.jl")
include("dlyap.jl")
include("check_model.jl")
include("solve_re.jl")
include("solve_disc.jl")
include("solve_commit.jl")
include("solve_quasi.jl")
include("solve_timeless.jl")
include("impulses.jl")

export derivative,
       hessian,
       newton,
       solve_re,
       check_model_form,
       convert_second_order

export Blanchard_Kahn_Form,
       Blanchard_Kahn_Soln,
       Klein_Form,
       Klein_Soln,
       Binder_Pesaran_Form,
       Binder_Pesaran_Soln,
       Sims_Form,
       Sims_Soln,
       Gomme_Klein_Form,
       Gomme_Klein_Soln,
       Lombardo_Sutherland_Form,
       Lombardo_Sutherland_Soln

export doubling,
       dlyap,
       solve_disc,
       solve_commit,
       solve_quasi,
       solve_timeless

export State_Space_Objective,
       Structural_Objective

export State_Space_Form,
       Generalized_State_Space_Form,
       Structural_Form,
       Generalized_Structural_Form,
       State_Space_Soln,
       Structural_Soln

export Perturbable_Soln,
       Second_Order_State_Space_Soln

export impulses

end


@testset "decompose" begin
    @testset "x^2+y^2+z^2" begin
        S = semi_elementary_monomial
        x = S((2,0,0),(0,0,0))
        poly = semi_elementary_polynomial(3)
        push!(poly,S((0,0,0),(2,0,0)),1)
        push!(poly,S((0,0,0),(0,1,0)),-2)
        @test decompose(x) == poly
    end

    @testset "x^3+y^3+z^3" begin
        S = semi_elementary_monomial
        x = S((3,0,0),(0,0,0))
        poly = semi_elementary_polynomial(3)
        push!(poly,S((0,0,0),(3,0,0)),1)
        push!(poly,S((0,0,0),(1,1,0)),-3)
        push!(poly,S((0,0,0),(0,0,1)),3)
        @test decompose(x) == poly
    end

    @testset "x^4+y^4+z^4" begin
        S = semi_elementary_monomial
        x = S((4,0,0),(0,0,0))
        poly = semi_elementary_polynomial(3)
        push!(poly,S((0,0,0),(4,0,0)),1)
        push!(poly,S((0,0,0),(2,1,0)),-4)
        push!(poly,S((0,0,0),(1,0,1)),4)
        push!(poly,S((0,0,0),(0,2,0)),2)
        @test decompose(x) == poly
    end

    @testset "x^5+y^5+z^5" begin
        S = semi_elementary_monomial
        x = S((5,0,0),(0,0,0))
        poly = semi_elementary_polynomial(3)
        push!(poly,S((0,0,0),(5,0,0)),1)
        push!(poly,S((0,0,0),(3,1,0)),-5)
        push!(poly,S((0,0,0),(2,0,1)),5)
        push!(poly,S((0,0,0),(1,2,0)),5)
        push!(poly,S((0,0,0),(0,1,1)),-5)
        @test decompose(x) == poly
    end

    @testset "w^5+x^5+y^5+z^5" begin
        S = semi_elementary_monomial
        x = S((5,0,0,0),(0,0,0,0))
        poly = semi_elementary_polynomial(4)
        push!(poly,S((0,0,0,0),(5,0,0,0)),1)
        push!(poly,S((0,0,0,0),(3,1,0,0)),-5)
        push!(poly,S((0,0,0,0),(2,0,1,0)),5)
        push!(poly,S((0,0,0,0),(1,2,0,0)),5)
        push!(poly,S((0,0,0,0),(1,0,0,1)),-5)
        push!(poly,S((0,0,0,0),(0,1,1,0)),-5)
        @test decompose(x) == poly
    end
end


# #Inflación media ponderada
# InflationWeightedMean.jl - Función de inflación media ponderada interanual

"""
    InflationWeightedMean <: InflationFunction
Función de inflación para computar la media ponderada.
"""
struct InflationWeightedMean <: InflationFunction
end

# 2. Extender el método de nombre
measure_name(::InflationWeightedMean) = "Media ponderada interanual"

# Define cómo opera InflationSimpleMean sobre un objeto de tipo VarCPIBase.
function (inflfn::InflationWeightedMean)(base::VarCPIBase{T}) where T
    #Obtener el indice correspondiente a las variaciones intermensuales
    indmat = capitalize(base.v)
    #Cálculo de la variación interanual
    y2ymat = varinteran(indmat)
    #Cálculo de la media ponderada de las variaciones interanuales
    y2yweightedmean = y2ymat*base.w/100
    #Utilizar el índice de la media ponderada de las variaciones intermensuales de los primeros
    #11 meses para completar la serie del índice de media ponderada de variaciones interanuales
    m2mweightedmean = base.v*base.w/100
    weightedmeanind = zeros(T, periods(base)+1); weightedmeanind[1]=100;
    weightedmeanind[2:12] = capitalize(m2mweightedmean[1:11])
    for j = 13:periods(base)+1
        weightedmeanind[j] = (y2yweightedmean[j-12]/100 + 1)*weightedmeanind[j-12]
    end
    #Cálculo de la variación intermensual del índice de media simple
    weightedmean = varinterm(weightedmeanind[2:end])
    weightedmean
end


# using JuMP
# using LazySets
#TODO: You could use Polyhedra to get the feasible set (poly = polyhedron(m) m=Model())
#TODO: Naming of the constraints => in order to have better debugging

# Used for DenseAxisArray ========================================
# x ∈ ℝn×N+1 and 𝒳 ⊂ ℝⁿ
function add_constraint!(m::Model, x::AbstractArray{VariableRef,2}, 𝒳::AbstractPolyhedron)
    a, b, s = get_constraints(𝒳)
    N = size(x,2)-1 # Since the first element of the JumpVariable is 0
    for j=0:N
        @constraint(m, [i=1:s], a[i]'*x[:, j] <= b[i])
    end
end

# x ∈ ℝⁿ and 𝒳 ⊂ ℝⁿ
function add_constraint!(m::Model, x::AbstractVector{VariableRef}, 𝒳::AbstractPolyhedron)
    a, b, s = get_constraints(𝒳)
    @constraint(m, [i=1:s], a[i]'*x <= b[i])
end

# x ∈ ℝⁿ and 𝒳 = 𝓍 ∈ ℝⁿ
function add_constraint!(m::Model, x, 𝓍::AbstractVector{<:Real})
    n = length(𝓍)
    @constraint(m, [i=1:n], x[i] == 𝓍[i])
end

# Used for Array{VariableRef, 2} ========================================
# x ∈ ℝⁿ and 𝒳 = 𝓍 ∈ ℝⁿ
function add_constraint!(m::Model, x::Vector{VariableRef}, 𝓍::AbstractVector{<:Real})
    n = length(𝓍)
    @constraint(m, x .== 𝓍)
end

function add_constraint!(m::Model, x::Array{VariableRef,2}, 𝒳::AbstractPolyhedron)
    a, b, s = get_constraints(𝒳)
    N = size(x,2) # Since, here, the first element of the JumpVariable is 1
    for j=1:N
        @constraint(m, [i=1:s], a[i]'*x[:, j] <= b[i])
    end
end

# # x ∈ ℝⁿ and 𝒳 = 𝓍 ± ϵ ∈ ℝⁿ (relaxed terminal constraint)
# function add_constraint!(m::Model, x, 𝓍::AbstractVector, ϵ=1e-5)
#     n = length(𝓍)
#     @constraint(m, [i=1:n], x[i] <= 𝓍[i] + ϵ)
#     @constraint(m, [i=1:n], x[i] <= 𝓍[i] - ϵ)
# end

# # TODO: make it a macro! such that the constr name can be added to the constraint
# function constrain_variable_by_set!(model::Model, x::AbstractVector{VariableRef},
#                                     𝒳::Interval{Float64,LazySets.IntervalArithmetic.Interval{Float64}})
#     N = length(x)
#     a = [1.0, -1.0]
#     b = [𝒳.dat.hi, -𝒳.dat.lo]
#     for j=0:N-1
#         @constraint(model, [i=1:2], a[i]'*x[j] <= b[i])
#     end
# end
#
# # 1D variable x and 1D Interval set 𝒳
# function constrain_variable_by_set!(model::Model, x::VariableRef,
#                                     𝒳::Interval{Float64,LazySets.IntervalArithmetic.Interval{Float64}})
#     a = [1.0, -1.0]
#     b = [𝒳.dat.hi, -𝒳.dat.lo]
#     @constraint(model, [i=1:2], a[i]'*x <= b[i])
# end

""" Checks JuMP.MOI.TerminationStatusCode """
function nonlin_opt_okay(model::Model)
    term_status = termination_status(model)
    if Int(term_status) == 1
        # everything is fine
    elseif Int(term_status) == 4
        @warn("No global Optimum found: $term_status")
    elseif (Int(term_status) == 7 || Int(term_status) == 10)
        @warn("Solved to relaxed tolerances: $term_status")
    else
        error("Optimization not a local or global Optimum: $term_status")
    end
    nothing
end

function get_ineq_constr(model::Model)
    return all_constraints(model, GenericAffExpr{Float64, VariableRef}, MOI.LessThan{Float64})
end

function get_eq_constr(model::Model)
    return all_constraints(model, GenericAffExpr{Float64, VariableRef}, MOI.EqualTo{Float64})
end

# list_of_constraint_types(model)
# TODO: print name of constraints!
# print(model)
# Min f(l(z + -0.200000000000002, v), l(z + 0.200000000000002, v))
# Subject to
#  0.5 z - v == 0.0
#  z <= 4.799999999999998
#  -z <= 4.799999999999998
#  v <= 2.0
#  -v <= 2.0


struct LinearInterpolant{T,N}
    y::Array{T,N}
    Δx::Float64
end

function (f::LinearInterpolant)(a::Float64)
    Δx = f.Δx
    s = a/(Δx);
    n₁ = floor(Int64,s);
    n₂ = ceil(Int64,s);
    Δn = (s - n₁)
    (1-Δn)*f.y[n₁+1] + Δn*f.y[n₂+1]
end

function qnms(;l=0,m=0,n=0,s=0,amax=0.99, ϵ = 0.01)
    as, ωs, Almss, Cllss, P, ϵ = GetModes(l,m,n,s;amax = amax,ϵ = ϵ)
    ωfunc = LinearInterpolant(ωs,ϵ)
    Afunc = LinearInterpolant(Almss,ϵ)
    Cllss = LinearInterpolant(Cllss,ϵ)
    ωfunc, Afunc, Cllss
end

function qnm(;l=0,m=0,n=0,s=0,a=0.0, ϵ = 0.01)
     _, ωs, Almss, Cllss, P, _ = GetModes(l,m,n,s;amax = a,ϵ = ϵ)
    #ωs[end], Almss[end], Cllss[:,end], P
    Alms, Cll = ComputeAₗₘ(P.s,P.m,P.a*P.ω, P.Alm,P.lmax)
    ωs[end], Alms, Cll, P
end


# ---
# title: 1351. Count Negative Numbers in a Sorted Matrix
# id: problem1351
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array, Binary Search
# link: <https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix/description/>
# hidden: true
# ---
#
# Given a `m * n` matrix `grid` which is sorted in non-increasing order both
# row-wise and column-wise.
#
# Return the number of **negative** numbers in `grid`.
#
#
#
# **Example 1:**
#
#
#
#     Input: grid = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
#     Output: 8
#     Explanation: There are 8 negatives number in the matrix.
#
#
# **Example 2:**
#
#
#
#     Input: grid = [[3,2],[1,0]]
#     Output: 0
#
#
# **Example 3:**
#
#
#
#     Input: grid = [[1,-1],[-1,-1]]
#     Output: 3
#
#
# **Example 4:**
#
#
#
#     Input: grid = [[-1]]
#     Output: 1
#
#
#
#
# **Constraints:**
#
#   * `m == grid.length`
#   * `n == grid[i].length`
#   * `1 <= m, n <= 100`
#   * `-100 <= grid[i][j] <= 100`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


"""
    sample_regular(shape::Tuple{Int64,Int64},redFac::Float64;kargs...)

generates a regular sampling pattern for an Array of size `shape` with a subsampling factor `redFac`.

# Arguments
* `shape::NTuple{N,Int64}` - size of the Array to be sampled
* `redFac::Float64`        - subsampling factor
"""
function sample_regular(shape::Tuple, redFac::Float64; kargs...)
  sample_regular(prod(shape), redFac)
end

function sample_regular(maxInd::Int64, redFact::Float64; kargs...)
  outSet = range(1,step=floor(Int,redFact),length=floor(Int,maxInd/redFact))
  return outSet
end


#---------------------------------------------------------------------#
#This routine advances the solution in time using Split-Explicit using
#a General Order RK method in Butcher tableau form following Schlegel et al. GMD 2012.
#Written by F.X. Giraldo / P.R. Mugg on 9/22/19
#           Department of Applied Mathematics
#           Naval Postgraduate School
#           Monterey, CA 93943-5216
#---------------------------------------------------------------------#
function multirate_erk_butcher_substepping(q0,c,αI,βI,I,αJ,βJ,J,M,Δt,time)

    #Compute Coefficients
    #Outer Loop
    α̃I = zeros(I+1,I)
    c̃I = zeros(I+1,1)
    cI = zeros(I,1)
    for i = 2:I
        cI[i] = sum(αI[i,:])
        α̃I[i,:] = αI[i,:] - αI[i-1,:]
        c̃I[i] = cI[i] - cI[i-1]
    end
    for j = 1:I
        α̃I[I+1,j] = βI[j] - αI[I,j]
    end
    c̃I[I+1] = 1.0 - cI[I]

    #Inner Loop
    α̃J = zeros(J+1,J)
    c̃J = zeros(J+1,1)
    cJ = zeros(J,1)
    for i = 2:J
        cJ[i] = sum(αJ[i,:])
        α̃J[i,:] = αJ[i,:] - αJ[i-1,:]
        c̃J[i] = cJ[i] - cJ[i-1]
    end
    for j = 1:J
        α̃J[J+1,j] = βJ[j] - αJ[J,j]
    end
    c̃J[J+1] = 1.0 - cJ[J]

    #Initialize Arrays
    Q = zeros(I+1,1)
    V = zeros(I+1,J+1)

    #Outer Loop
    Q[1] = q0
    for i = 2:I+1
        r_i = 0
        for j = 1:i-1
            t = time + Δt*cI[j]
            (rhs,f𝑗,g𝑗) = rhs_function(Q[j],c,t)
            r_i += α̃I[i,j]*g𝑗
        end

        #Substepping Loop
        qm = Q[i-1]
        for m = 1:M
            V[i,1] = qm

            #Inner Loop
            for j = 2:J+1
                R_sum = 0
                for k = 1:j-1
                    t = time + Δt*( cI[i-1] + c̃I[i]*cJ[k]/M*(m-1) ) #not correct so am losing accuracy
                    (rhs,f𝑘,g𝑘) = rhs_function(V[i,k],c,t)
                    R_sum += α̃J[j,k]*( r_i + c̃I[i]*f𝑘 )
                end #k
                V[i,j] = V[i,j-1] + Δt/M*R_sum
            end #j
            qm = V[i,J+1]
        end #m
        Q[i] = qm
    end #i

    return qp = Q[I+1]
end #end function


"""
        matrix2graph(sparse_matrix)

A utility function to generate a graph from input
sparse matrix, columns are represented with vertices
and 2 vertices are connected with an edge only if
the two columns are mutually orthogonal.
"""
function matrix2graph(sparse_matrix::SparseMatrixCSC{<:Number, Int}, partition_by_rows::Bool)
    dropzeros(sparse_matrix)
    (rows_index, cols_index, val) = findnz(sparse_matrix)

    ncols = size(sparse_matrix, 2)
    nrows = size(sparse_matrix, 1)

    num_vtx = partition_by_rows ? nrows : ncols

    inner = SimpleGraph(num_vtx)
    graph = VSafeGraph(inner)

    if partition_by_rows
        for i = 1:length(rows_index)
            cur_row = rows_index[i]
            for j = 1:(i-1)
                next_row = rows_index[j]
                if cur_row != next_row
                    if cols_index[i] == cols_index[j]
                        add_edge!(graph, cur_row, next_row)
                    end
                end
            end
        end
    else
        for i = 1:length(cols_index)
            cur_col = cols_index[i]
            for j = 1:(i-1)
                next_col = cols_index[j]
                if cur_col != next_col
                    if rows_index[i] == rows_index[j]
                        add_edge!(graph, cur_col, next_col)
                    end
                end
            end
        end
    end
    return graph
end


"""
    Car

    Unicycle model (http://lavalle.pl/planning/)
"""

struct Car <: Model
    n::Int
    m::Int
    d::Int
end

function f(model::Car, x, u, w)
    @SVector [u[1] * cos(x[3]),
              u[1] * sin(x[3]),
              u[2]]
end

n, m, d = 3, 2, 0
model = Car(n, m, d)


module iss  #jl

using ReachabilityAnalysis, MAT
using ReachabilityModels: @relpath

file = matopen(@relpath "iss.mat")

# system matrix
A = read(file, "A")

# input matrix
B = read(file, "B")

# state domain
X = Universe(270)

# input domain
U = Hyperrectangle([0.05, 0.9, 0.95], [0.05, 0.1, 0.05])

function model(X0)
    S = @system(x' = Ax + Bu, x ∈ X, u ∈ U)
    return IVP(S, X0)
end

end  #jl


module MoistAirProp

import Roots
"""
    psat_water(T)

Calculates the saturation pressure of the water in ``kPa`` at a given temperature in ``Kelvin``.
`T` should range between 173.15 to 473.15.
Ref -> ASHRAE Handbook - Fundamentals - Chapter-1

"""
function psat_water(T)
    C1 = -5.6745359E+03
    C2 = 6.3925247
    C3 = -9.6778430E-03
    C4 = 6.2215701E-07
    C5 = 2.0747825E-09
    C6 = -9.4840240E-13
    C7 = 4.1635019
    C8 = -5.8002206E+03
    C9 = 1.3914993
    C10 = -4.8640239E-02
    C11 = 4.1764768E-05
    C12 = -1.4452093E-08
    C13 = 6.5459673
    if (T > 173.15 && T < 273.15)
        p_ws = exp(C1/T + C2 + C3*T + C4*T^2 + C5*T^3 + C6*T^4 + C7*log(T))
    elseif (T >= 273.15 && T < 473.15)
        p_ws = exp(C8/T + C9 + C10*T + C11*T^2 + C12*T^3 + C13*log(T))
    else
        error("Temperature should be between -100 and 200 deg C")
    end #if
    return p_ws/1000
end

"""
    humidity_ratio(p, t, str, val)

Calculates humidity ratio provided `p` total pressure in ``kPa``, `t` Temperature in kelvin, `str` string could be (``rh`` for relative humidty, ``wbt`` for wet bulb temperature, ``dpt`` for dewpoint temperature, ``w`` for humidity ratio), `val` value of variable specified in the string
"""
function humidity_ratio(p, t, str, val)
    t0 = 273.15
    if str == "rh" || str == "RH"
        rh = val
        p_ws = psat_water(t)
        w = 0.621945*rh*(p_ws)/(p-rh*p_ws)
    elseif str == "WBT" || str == "wbt"
        wbt = val
        p_ws_wbt = psat_water(wbt)
        ws_wbt = 0.621945*(p_ws_wbt)/(p-p_ws_wbt)
        if t >= t0
            w = ((2501-2.326*(wbt-t0))*ws_wbt - 1.006*((t-t0) - (wbt-t0)))/(2501 + 1.86*(t-t0) - 4.186*(wbt-t0))
        else
            w = ((2830-0.24*(wbt-t0))*ws_wbt - 1.006*((t-t0) - (wbt-t0)))/(2830 + 1.86*(t-t0) - 2.1*(wbt-t0))
        end #if
    elseif str == "dpt" || str == "DPT"
        dpt = val
        p_ws = psat_water(dpt)
        w = 0.621945*(p_ws)/(p-p_ws)
    elseif str == "w" || str == "W"
        w = val
    else
        error("use 'rh' for relative humidity,'wbt' for wet bulb temp,'dpt' for dew point temp")
    end #if
    return w
end #function

"""
    density_moistair(p,t,str,val)

Calculates density of moist air in ``kg/m^3`` provided `p` total pressure in ``kPa``, `t` Temperature in kelvin, `str` string could be (``rh`` for relative humidty, ``wbt`` for wet bulb temperature, ``dpt`` for dewpoint temperature, ``w`` for humidity ratio), `val` value of variable specified in the string
"""
function density_moistair(p,t,str,val)
    if str == "w" || str == "w"
        w = val
    else
        w = humidity_ratio(p, t, str, val)
    end
    v = 0.287042*(t)*(1 + 1.607858*w)/p
    return 1/v
end
"""
    dewpoint(p,t,str,val)

Calculates dewpoint temperature of moist air in ``kelvin`` provided `p` total pressure in ``kPa``, `t` Temperature in ``kelvin``, `str` string could be (``rh`` for relative humidty, ``wbt`` for wet bulb temperature, ``w`` for humidity ratio), `val` value of variable specified in the string
"""
function dewpoint(p,t,str,val)
    if str == "w" || str == "W"
        w = val
    else
        w = humidity_ratio(p, t, str, val)
    end
    p_w = p/(0.621945/w +1)
    t_dpt = Roots.find_zero(x -> (psat_water(x)-p_w),(t-100,t))
end
"""
    enthalpy_moistair(p,t,str,val)

Calculates enthalpy of moist air in ``kJ/kg`` provided `p` total pressure in ``kPa``, `t` Temperature in ``kelvin``, `str` string could be (``rh`` for relative humidty, ``wbt`` for wet bulb temperature, ``dpt`` for dewpoint temperature, ``w`` for humidity ratio), `val` value of variable specified in the string
"""
function enthalpy_moistair(p, t, str, val)
    if str == "W" || str == "w"
        w = val
        h = 1.006*(t-273.15)+ w*(2501+1.86*(t-273.15))
    else
        w = humidity_ratio(p, t, str, val)
        h = 1.006*(t-273.15)+ w*(2501+1.86*(t-273.15))
    end
    return h
end
"""
    wetbulb(p,t,str,val)

Calculates wetbulb temperature of moist air in ``Kelvin`` provided `p` total pressure in ``kPa``, `t` Temperature in ``kelvin``, `str` string could be (``rh`` for relative humidty, ``dpt`` for dewpoint temperature, ``w`` for humidity ratio), `val` value of variable specified in the string
"""
function wetbulb(p,t,str,val)
    w = humidity_ratio(p,t,str,val)
    if str == "dpt" || str == "DPT"
        dpt = val
    else
        dpt = dewpoint(p,t,str,val)
    end
    t_wbt = Roots.find_zero(temp -> (humidity_ratio(p,t,"wbt",temp)-w), (dpt,t))
end

export psat_water, humidity_ratio, density_moistair, dewpoint, enthalpy_moistair, wetbulb

end # module


export WaveletOp

mutable struct WaveletOp{T,F1<:FuncOrNothing,F2<:FuncOrNothing,F3<:FuncOrNothing} <: AbstractLinearOperator{T,F1,F2,F3}
  nrow   :: Int
  ncol   :: Int
  symmetric :: Bool
  hermitian :: Bool
  prod   :: F1 # apply the operator to a vector
  tprod  :: F2 # apply the transpose operator to a vector
  ctprod :: F3 # apply the transpose conjugate operator to a vector
end

"""
  WaveletOp(shape, wt=wavelet(WT.db2))

returns a `ẀaveletOp <: AbstractLinearOperator`, which performs a Wavelet transform on
a given input array.

# Arguments

* `shape`                 - size of the Array to transform
* (`wt=wavelet(WT.db2)`)  - Wavelet to apply
"""
function WaveletOp(shape, wt=wavelet(WT.db2))
  return WaveletOp{Float64,Function,Nothing,Function}(maximum(shape)^2, prod(shape), false, false
            , x->waveletProd(x,shape,wt)
            , nothing
            , y->waveletCTProd(y,shape,wt) )
end

function waveletProd(x::Vector{T},shape, wt) where T
  if shape[1] != shape[2]
    xSquare = zeros(T, maximum(shape), maximum(shape))
    xSquare[1:shape[1],1:shape[2]] = reshape(x,shape)
  else
    xSquare = reshape(x,shape)
  end
  return vec( dwt(xSquare, wt) )
end

function waveletCTProd(y::Vector{T},shape, wt) where T
  squareSize = (maximum(shape), maximum(shape))
  x = idwt( reshape(y, squareSize), wt)
  return vec( x[1:shape[1],1:shape[2]] )
end

\(A::WaveletOp, x::Vector) = adjoint(A) * x


using Reexport

@reexport module DelaunayTriangulations
    include("AbstractDelaunayTriangulation.jl")
    include("composite_types/DelaunayTriangulation.jl")

    include("plot_recipes/plot_recipes.jl")
end

"""
    DelaunayTriangulations

A module handling the creation of Delaunay triangulations from data.
"""
DelaunayTriangulations


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ d4260fcc-784e-4341-ae84-c7e8ab288c4b
using LaTeXStrings, DifferentialEquations, LinearAlgebra, SparseArrays

# ╔═╡ 82272c06-3861-4aed-a08d-bed2e84570fc
begin
	using Plots
	gr()
end

# ╔═╡ b91fd324-adf3-11eb-36e5-59bf4de5e0f9
md"# 2D Multiple Attractor System"

# ╔═╡ 41f97746-5818-410f-b3cb-e59aa9a5bc99
md"_Setup vector field equations. Can't split into jump process._"

# ╔═╡ 5a0a71b5-0b18-42be-b567-c01feb0d9af6
begin
	α = 0.1
	β = 3
	n = 4
	dfx(x, y) = 9x + 9y - (1 + 2x^3 + 2y^3)
	dfy(x, y) = 1 + 2x^3 + 11y - (11x + 2y^3)
end

# ╔═╡ f56451d1-6f67-494b-82b0-2369558d6819
begin
	n_min = -3
	n_max = 3
end

# ╔═╡ a342ae72-b363-4142-929c-78e682871d81
md"_Compute the vector field_"

# ╔═╡ 7cb95f1f-8a19-484c-a33f-a13348dce332
begin
	dq = (n_max - n_min) / 10.
	xs = n_min-1:dq:n_max+1
	ys = n_min-1:dq:n_max+1

	dxs = n_min-1:.05:n_max+1
	dys = n_min-1:.05:n_max+1

	df(x, y) = normalize([dfx(x, y), dfy(x,y)]) ./ 1.75

	xxs = [x for x in xs for y in ys]
	yys = [y for x in xs for y in ys]

	quiver(xxs, yys, quiver=df)
	contour!(dxs, dys, dfx, levels=[0], color=:orange, label="X Nullcline", colorbar = false)
	p1 = contour!(dxs, dys, dfy, levels=[0], color=:green, label = "Y Nullcline", colorbar = false)
	p1
end

# ╔═╡ 7d1e052e-c9b6-4fa3-b4aa-980e60e53961
md"_Setup the Stochastic Differential Equation model with diagonal noise_"

# ╔═╡ 601e7f8b-efa8-4710-b088-78b825b0454a
function f(du, u, p, t)
	du[1] = dfx(u...)
	du[2] = dfy(u...)
	du[1], du[2]
end

# ╔═╡ e3b32bba-b8c8-48ba-8918-c73f1f1e9db9
begin
	g(du, u, p, t) = begin
	  du[1] = 1.25
	  du[2] = 1.25
	end
	sde_prob= SDEProblem(f, g, [0., 0.], (0.0, 1000.0))
	sde_sol = solve(sde_prob, SRIW1())
end

# ╔═╡ 67d5512b-fe45-47d7-9b18-c847a8010577
pstoch_1 = plot(sde_sol,title="Stochastic Simulation", labels=[L"x" L"y"])

# ╔═╡ a1d79d06-afdd-4097-94ac-716779173e6c
pstoch_2 = plot(sde_sol[1000:end], vars = [(1,2)], label="", xlabel=L"x", ylabel = L"y", title="Time Evolution State Plot")

# ╔═╡ eef51429-8458-421a-8366-35577019e204
# pstoch = plot(pstoch_1, pstoch_2, layout=(1, 2), size=(900, 350))

# ╔═╡ 10c9d45f-d8ab-4acd-88ba-8a80a747e30f
# savefig(pstoch, "ch3_four_attractor_stoch.pdf")

# ╔═╡ 9e084100-e5d1-488c-8eaf-769711af1d95
md"_Helper function to tip towards different quadrants for each run of the simulation_"

# ╔═╡ dc5e6e4a-93d6-47c3-80ef-8d22d3483e99
function get_u0(i)
	offset = 1.0
	new_u0 = [0.0, 0.0]
	if i % 4 == 0
		new_u0 .+= [offset, offset]
	elseif i % 4 == 1
		new_u0 .+= [-1* offset, offset]
	elseif i % 4 == 2
		new_u0 .+= [-1* offset, -1* offset]
	else
		new_u0 .+= [offset,-1* offset]
	end
	new_u0
end

# ╔═╡ 3481f158-573b-4ca4-9f2f-fa0e4b68edb4
md"_Add smaller noise term for longer time simulation._

Note that this is equivalent to modifying `D` in the Fokker-Plank Equations and yeilds different probability distributions."

# ╔═╡ 25b8d3e0-4402-4e84-91a0-c5d1c4684ac4
begin
	g2(du, u, p, t) = begin
	  du[1] = 0.5
	  du[2] = 0.5
	end
end

# ╔═╡ 1870a27b-683b-463a-b5cc-9ac073110091
md"_Helper to compute the 2D histograms for unnomalized probability_"

# ╔═╡ c1b075dc-c4dc-4bd9-bbd9-bbb0119fc54a
begin
	nbins = 257
	bin_min = -10.
	bin_max = 10.
	xbins = range(bin_min, bin_max, length=nbins)
	ybins = range(bin_min, bin_max, length=nbins)
	xbins

	function bucket_idx(val, vec)
		idx = 1
		while val > vec[idx]
			idx += 1
		end
		idx - 1
	end

	function althist(x, y, dt, xedges, yedges)
	   counts = spzeros(length(xedges)-1, length(yedges)-1)

		for i=1:length(x) - 1
			r = bucket_idx(x[i], yedges)
			c = bucket_idx(y[i], xedges)
			counts[r,c] += dt[i]
		end

 		counts
	end

	drop_num = 2000
end

# ╔═╡ 351e030b-0d6c-42ea-8c5b-9cad2af5c12c
unnorm = spzeros(length(xbins)-1, length(ybins)-1)

# ╔═╡ 8b509897-8b02-4af7-a0bd-990f2cb3df87
md"_Rerun the following cell to add more data points to the simulation._"

# ╔═╡ 75bcc971-1a89-4dd4-a5ba-a1397f5db274
begin
	for i in 1:4*100
		u00 = get_u0(i)
		sde_prob_long = SDEProblem(f, g2, u00, (0.0, 1e3))
		sde_sol_long= solve(sde_prob_long, SRIW1())
		unnorm .+= althist(sde_sol_long[1, drop_num:end], sde_sol_long[2, drop_num:end], diff(sde_sol_long.t[drop_num:end]), xbins, ybins)
	end
	unnorm
end

# ╔═╡ 86019cdf-c7de-4495-ba61-de0852f2b496
md"_Normalized and visualize the probability and resulting Quasi-potential._"

# ╔═╡ 13347146-e598-4d25-9d92-4b047a9de492
prob = unnorm ./ sum(unnorm)

# ╔═╡ 6c523287-b8db-4ef7-a08a-f61bf2639278
begin
	plotly()
	p = surface(- log.(prob))
	gr()
	p
end

# ╔═╡ bc84c949-6b26-4e02-84bf-a2093508a0dc
begin
	plotly()
	pp = surface(xbins, ybins,prob,c = :grayC)
	gr()
	pp
end

# ╔═╡ 6af8e6ef-2444-4c88-ad81-a645b8971527
begin
	xplot = range(bin_min, bin_max, length=nbins-1)
	yplot = range(bin_min, bin_max, length=nbins-1)
end

# ╔═╡ b3a3b34b-ffff-4b35-952e-d844b5830ce7
 begin
	pp1 = surface(xplot, yplot,-log.(prob), c = :thermal)
	pp2 =  surface(xplot, yplot, prob, c = :grayC)
	ps = plot(pp1, pp2, layout =(2, 1), size=(500, 800))
end

# ╔═╡ 4e4f16b1-7c8a-4435-91f9-6a0d4ff54b22
# savefig(ps, "ch3_4a_ad_hoc.pdf")

# ╔═╡ c37e269f-ac80-4bd1-81d0-e5a476e85272
md"_Helper function to generate -∇ ² to solve the Helmhotz equation_"

# ╔═╡ 164f3c77-5e95-4feb-8783-54c381f4b184
begin
	import DiffEqOperators
	const DiffEqOp = DiffEqOperators

	eye(N, M) = sparse(I, N, M)
	eye(M) = eye(M, M)
	diff1(M) = [ [1.0 zeros(1, M - 1)]; diagm(1 => ones(M - 1)) - eye(M) ]
	sdiff1(M) = sparse(diff1(M))
	flatshape(X) = reshape(X, length(X))

	# make the discrete -Laplacian in 2d, with Dirichlet boundaries
	function laplacian2d(Nx, Ny, Lx, Ly)
		dx = Lx / (Nx + 1)
		dy = Ly / (Ny + 1)
		Dx = sdiff1(Nx) / dx
		Dy = sdiff1(Ny) / dy
		Ax = Dx' * Dx
		Ay = Dy' * Dy
		return Dx, Dy, kron(eye(Ny), Ax) + kron(Ay, eye(Nx))
	end
end

# ╔═╡ c0960c8a-76d0-466a-889b-2179d02518a7
# fg =  plot(p1, p2, p3, layout=(3,1), size=(750, 2250))

# ╔═╡ eacf4f60-956d-4408-b67d-58465a0411dc
# savefig(fg, "ch3_four_attractor.pdf")

# ╔═╡ 23bd055e-599b-407a-9918-d9880d257db9
md"### Appendix: Helper decomposition functions"

# ╔═╡ 3fd9492a-d75e-4603-bafc-4e48fd422385
## We are trying to solve the following equation
## ∇²Uᴴ (x⃗) = -∇ ⋅ F(x⃗)
## We are starting in 2 dimensions
function helmholtz(prob::DiscreteProblem, minval::Int64, maxval::Int64, nsample::Int64)
    dx = (maxval - minval) / (nsample + 1)
    dy = (maxval - minval) / (nsample + 1)
    xs = range(minval, maxval, length=nsample)
    ys = range(minval, maxval, length=nsample)
    ∂x(x, y) = prob.f.f([0.0, 0.0], [x, y], prob.p, nothing)[1]
    ∂y(x, y) = prob.f.f([0.0, 0.0], [x, y], prob.p, nothing)[2]
    # Let's first construct F = [f1, f2]
    f1 = [∂x(x, y) for x in xs, y in ys]
    f2 = [∂y(x, y) for x in xs, y in ys]
    _, _, Δ = laplacian2d(nsample, nsample, maxval - minval, maxval - minval)

    Dx = DiffEqOp.CenteredDifference{1}(1, 2, dx, nsample)
    Dy = DiffEqOp.CenteredDifference{2}(1, 2, dy, nsample)

    bcx = DiffEqOp.Dirichlet0BC{1}(eltype(f1), size(f1))
    bcy = DiffEqOp.Dirichlet0BC{2}(eltype(f2), size(f2))

    rhs =  Dx * bcx * f1 + Dy * bcy *  f2

    u = Δ \ flatshape(rhs)
    xs, ys, reshape(u, size(rhs)...)
end

# ╔═╡ 751ac213-1312-4f0e-bdd8-fa2a10c4554f
begin
	nsample = 512
	dprob = DiscreteProblem(f, [0.0,0.0], (0, 100.), nothing)
	x, y, u_h = helmholtz(dprob, -3, 3, nsample)
end

# ╔═╡ 8101d876-4c69-43e2-8492-59a799707151
begin
	gr()
	fg = surface(x, y, u_h, colorbar=:none,  c=:viridis, aspect_ratio=:equal, xlabel=L"x", ylabel=L"y", zlabel=L"U^H")
end

# ╔═╡ b6ce865e-e1d0-47cd-bbfc-e144459d92a2
p3 = heatmap(x, y, u_h, c=:viridis)

# ╔═╡ 75902190-09c8-41aa-b3ab-ceeaee4ac821

function make_inplace(f::Function)
    function inplace_f(out, x)
        out .= f(x)
    end
    return inplace_f
end

# ╔═╡ b57411c3-a92d-4ce6-82e0-ad0cc4b56f3f
function normal(prob::DiscreteProblem, minval::Int64, maxval::Int64, nsample::Int64, u0::Matrix{Float64})
    dx = (maxval - minval) / (nsample + 1)
    dy = (maxval - minval) / (nsample + 1)

    xs = range(minval, maxval, length=nsample)
    ys = range(minval, maxval, length=nsample)
    ∂x(x, y) = prob.f.f([0.0, 0.0], [x, y], prob.p, nothing)[1]
    ∂y(x, y) = prob.f.f([0.0, 0.0], [x, y], prob.p, nothing)[2]

    f1 = [∂x(x, y) for x in xs, y in ys]
    f2 = [∂y(x, y) for x in xs, y in ys]

    Dx = DiffEqOp.UpwindDifference{1}(1, 2, dx, nsample, 1)
    Dy = DiffEqOp.UpwindDifference{2}(1, 2, dy, nsample, 1)

    bcx = DiffEqOp.Dirichlet0BC{1}(eltype(u0), size(u0))
    bcy = DiffEqOp.Dirichlet0BC{2}(eltype(u0), size(u0))

    function find_my_zero(u)
        dudx = Dx * bcx * u
        dudy = Dy * bcy * u
        return dot(dudx, f1 .+ dudx) + dot(dudy, f2 .+ dudy)
    end

    fmz! = make_inplace(find_my_zero)

    sol = nlsolve(fmz!, u0, show_trace=true, iterations=3000)
    sol.zero
end

# ╔═╡ Cell order:
# ╟─b91fd324-adf3-11eb-36e5-59bf4de5e0f9
# ╠═d4260fcc-784e-4341-ae84-c7e8ab288c4b
# ╠═82272c06-3861-4aed-a08d-bed2e84570fc
# ╟─41f97746-5818-410f-b3cb-e59aa9a5bc99
# ╠═5a0a71b5-0b18-42be-b567-c01feb0d9af6
# ╠═f56451d1-6f67-494b-82b0-2369558d6819
# ╟─a342ae72-b363-4142-929c-78e682871d81
# ╠═7cb95f1f-8a19-484c-a33f-a13348dce332
# ╟─7d1e052e-c9b6-4fa3-b4aa-980e60e53961
# ╠═601e7f8b-efa8-4710-b088-78b825b0454a
# ╠═e3b32bba-b8c8-48ba-8918-c73f1f1e9db9
# ╠═67d5512b-fe45-47d7-9b18-c847a8010577
# ╠═a1d79d06-afdd-4097-94ac-716779173e6c
# ╠═eef51429-8458-421a-8366-35577019e204
# ╠═10c9d45f-d8ab-4acd-88ba-8a80a747e30f
# ╟─9e084100-e5d1-488c-8eaf-769711af1d95
# ╠═dc5e6e4a-93d6-47c3-80ef-8d22d3483e99
# ╟─3481f158-573b-4ca4-9f2f-fa0e4b68edb4
# ╠═25b8d3e0-4402-4e84-91a0-c5d1c4684ac4
# ╟─1870a27b-683b-463a-b5cc-9ac073110091
# ╠═c1b075dc-c4dc-4bd9-bbd9-bbb0119fc54a
# ╠═351e030b-0d6c-42ea-8c5b-9cad2af5c12c
# ╟─8b509897-8b02-4af7-a0bd-990f2cb3df87
# ╠═75bcc971-1a89-4dd4-a5ba-a1397f5db274
# ╟─86019cdf-c7de-4495-ba61-de0852f2b496
# ╠═13347146-e598-4d25-9d92-4b047a9de492
# ╠═6c523287-b8db-4ef7-a08a-f61bf2639278
# ╠═bc84c949-6b26-4e02-84bf-a2093508a0dc
# ╠═6af8e6ef-2444-4c88-ad81-a645b8971527
# ╠═b3a3b34b-ffff-4b35-952e-d844b5830ce7
# ╠═4e4f16b1-7c8a-4435-91f9-6a0d4ff54b22
# ╟─c37e269f-ac80-4bd1-81d0-e5a476e85272
# ╠═164f3c77-5e95-4feb-8783-54c381f4b184
# ╠═751ac213-1312-4f0e-bdd8-fa2a10c4554f
# ╠═8101d876-4c69-43e2-8492-59a799707151
# ╠═b6ce865e-e1d0-47cd-bbfc-e144459d92a2
# ╠═c0960c8a-76d0-466a-889b-2179d02518a7
# ╠═eacf4f60-956d-4408-b67d-58465a0411dc
# ╟─23bd055e-599b-407a-9918-d9880d257db9
# ╟─3fd9492a-d75e-4603-bafc-4e48fd422385
# ╟─75902190-09c8-41aa-b3ab-ceeaee4ac821
# ╟─b57411c3-a92d-4ce6-82e0-ad0cc4b56f3f


using Test

function AND(x1, x2)
    x = [x1, x2]
    w = [0.5, 0.5]
    b = -0.7
    tmp = sum(w.*x) + b
    return tmp > 0
end

function NAND(x1, x2)
    x = [x1, x2]
    w = [-0.5, -0.5]
    b = 0.7
    tmp = sum(w.*x) + b
    return tmp > 0
end

function OR(x1, x2)
    x = [x1, x2]
    w = [0.5, 0.5]
    b = -0.2
    tmp = sum(w.*x) + b
    return tmp > 0
end

function XOR(x1, x2)
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y
end

function main()
    @testset "Test" begin
        @testset "AND" begin
            @test AND(0, 0) == 0
            @test AND(0, 1) == 0
            @test AND(1, 0) == 0
            @test AND(1, 1) == 1
        end
        @testset "NAND" begin
            @test NAND(0, 0) == 1
            @test NAND(0, 1) == 1
            @test NAND(1, 0) == 1
            @test NAND(1, 1) == 0
        end
        @testset "OR" begin
            @test OR(0, 0) == 0
            @test OR(0, 1) == 1
            @test OR(1, 0) == 1
            @test OR(1, 1) == 1
        end
        @testset "XOR" begin
            @test XOR(0, 0) == 0
            @test XOR(0, 1) == 1
            @test XOR(1, 0) == 1
            @test XOR(1, 1) == 0
        end
    end
end

main()


function part1(input)
    containers = sort(parse.(Int, eachline(input)), rev = true)
    return combinations(containers, 150)
end

function part2(input)
    containers = sort(parse.(Int, eachline(input)), rev = true)
    return last(combinations2(containers, 150))
end

function combinations(containers, n, i = 1,
                      cache = Dict{Tuple{Int, Int}, Int }())
    n < 0 && return 0
    n == 0 && return 1
    i > length(containers) && return 0
    if !haskey(cache, (n, i))
        c = sum(combinations(containers, n - containers[j], j + 1, cache)
                for j = i:length(containers))
        cache[(n, i)] = c
    end
    return cache[(n, i)]
end

function combinations2(containers, n, i = 1,
                       cache = Dict{Tuple{Int, Int}, Tuple{Int, Int}}())
    n < 0 && return (0, 0)
    n == 0 && return (0, 1)
    i > length(containers) && return (0, 0)
    if !haskey(cache, (n, i))
        min_cups, num_combinations = typemax(Int), 0
        for j = i:length(containers)
            mc, nc = combinations2(containers, n - containers[j], j + 1, cache)
            nc == 0 && continue
            if mc < min_cups
                min_cups = mc
                num_combinations = nc
            elseif mc == min_cups
                num_combinations += nc
            end
        end
        if num_combinations == 0
            cache[(n, i)] = (0, 0)
        else
            cache[(n, i)] = (min_cups + 1, num_combinations)
        end
    end
    return cache[(n, i)]
end


maxdiff(x::Real, y::Real) = abs(x - y)

function maxdiff{T <: Number}(x::Array{T}, y::Array{T})
    m = -Inf
    for i in 1:length(x)
        d = abs(x[i] - y[i])
        if d > m
            m = d
        end
    end
    return m
end


using DitherPunk

@test DitherPunk.bayer_matrix(1) == [
    0 8 2 10
    12 4 14 6
    3 11 1 9
    15 7 13 5
]

@test DitherPunk.bayer_matrix(2) == [
    0 32 8 40 2 34 10 42
    48 16 56 24 50 18 58 26
    12 44 4 36 14 46 6 38
    60 28 52 20 62 30 54 22
    3 35 11 43 1 33 9 41
    51 19 59 27 49 17 57 25
    15 47 7 39 13 45 5 37
    63 31 55 23 61 29 53 21
]


function knothash(arr::Array{Int, 1}, list::UnitRange=0:255; pos::Int=0, skip::Int=0, iterations::Int=1)
    n = length(arr)
    for iter in 1:iterations
        for i in 1:n

            L = arr[i]
            if L > length(list)
                continue
            end
            list = circshift(list, -pos)
            reverse!(list, 1, L)
            list = circshift(list, pos)
            pos += L + skip
            skip += 1

        end
    end

    return list
end

"Convert a string to bytes"
function string2bytes(s::String)
    Int.(collect(s))
end

function puzzle10(path::String=joinpath(@__DIR__, "..", "data/10.txt"))
    input = readline(path)
    lengths = parse.(Int, split(input, ","))
    list = knothash(lengths)
    partone = list[1] * list[2]

    stringofbytes = string2bytes(input)
    append!(stringofbytes, [17, 31, 73, 47, 23])
    list = knothash(stringofbytes, iterations=64)
    sparsehash = reshape(list, 16, 16)
    densehash = [reduce(xor, sparsehash[:, i]) for i in 1:size(sparsehash, 2)]
    parttwo = reduce(*, lpad.(string.(densehash, base=16), 2, "0"))

    return [partone, parttwo]

end


# Show cancellation errors in stresslet when evaluated directly

push!(LOAD_PATH, string(pwd(),"/src"))
using PyPlot
using ModifiedStokesSolver

for ifprime = [false, true]
    N = 100
    z = linspace(eps(), 3, N)
    E = zeros(N, 6)
    for i=1:N
        Tp = ModifiedStokesSolver.Ti_split_pow(z[i], ifprime)
        Td = ModifiedStokesSolver.Ti_split_direct(z[i], ifprime)
        for j=1:6
            E[i, j] = abs(Tp[j]-Td[j]) / maximum(abs.(Tp))
        end
    end

    figure(ifprime ? 1 : 2)
    clf()
    semilogy(z, E[:,1], label="T1S")
    semilogy(z, E[:,2], label="T1L")
    semilogy(z, E[:,3], label="T2S")
    semilogy(z, E[:,4], label="T2L")
    semilogy(z, E[:,5], label="T3S")
    semilogy(z, E[:,6], label="T3L")
    ylim(1e-16, 1)

    if ifprime
        stem(ModifiedStokesSolver.POWERSERIES_ZMAX_PRIME*[1.0, 1.0], [1e-16, 1], label="cutoff")
    else
        stem(ModifiedStokesSolver.POWERSERIES_ZMAX*[1.0, 1.0], [1e-16, 1], label="cutoff")
    end
    title("ifprime=$ifprime")

    legend()
    grid("on")
end
show()


using ChaosTools, DynamicalSystemsBase, Test

println("\nTesting period estimation...")
Δt = 0.005

function test_algs(vs, ts, trueperiod, atol; methods = [
            :autocorrelation, :periodogram,
            :multitaper, :lombscargle, :zerocrossing
    ])

    for alg in methods
        @testset "$alg" begin
            Sys.WORD_SIZE == 32 && alg == :multitaper && continue
            @test estimate_period(vs, alg, ts) ≈ trueperiod atol = atol
        end
    end
end

@testset "sin(t)" begin
    tsin = 0:Δt:22π
    vsin = sin.(tsin)

    test_algs(vsin, tsin, 2π, 11.53Δt)
end

@testset "sin(2t)" begin

    tsin = 0:Δt:22π
    vsin = sin.(2 .* tsin)

    test_algs(vsin, tsin, π, 3Δt)
end

@testset "Roessler" begin

    Δt = 0.1
    ds = Systems.roessler(ones(3))
    T = 5000.0
    tr = trajectory(ds, T; Ttr = 100.0, Δt)
    v = tr[:, 1]
    t = 0:Δt:T

    test_algs(v, t, 6, 1.5)
end

@testset "Modified FitzHugh-Nagumo" begin

    function FHN(u, p, t)
        e, b, g = p
        v, w = u
        dv = min(max(-2 - v, v), 2 - v) - w
        dw = e*(v - g*w + b)
        return SVector(dv, dw)
    end

    g  = 0.8
    e = 0.04
    b = 0
    p0 = [e, b, g]

    fhn = ContinuousDynamicalSystem(FHN, SVector(-2, -0.6667), p0)
    T = 1000.0
    v = trajectory(fhn, T; Δt)[:, 1]
    real_p = 91

    test_algs(v, 0:Δt:T, real_p, 0.8)
end


# Generating new terms in a sequence

function generate!(S::Sequence,n::Int)
    s,a = sequence(S),generator(S)
    i = length(s)
    i ≤ n && resize!(s,n)
    for k = 1+i:n s[k] = a[k] end
end

function generate!(S::Cumsum,n::Int)
    s,a = sequence(S),generator(S)
    i = length(s)
    i ≤ n && resize!(s,n)
    for k = 1+i:n s[k] = a[k]+s[k-1] end
end

function generate!(S::Cumprod,n::Int)
    s,a = sequence(S),generator(S)
    i = length(s)
    i ≤ n && resize!(s,n)
    for k = 1+i:n s[k] = a[k]*s[k-1] end
end

function generate!(S::Difference{T,D,1},n::Int) where {T,D}
    s,a = sequence(S),generator(S)
    i = length(s)
    i ≤ n && resize!(s,n)
    for k = 1+i:n s[k] = a[k+1]-a[k] end
end

function generate!(S::Difference{T,D,2},n::Int) where {T,D}
    s,a = sequence(S),generator(S)
    i = length(s)
    i ≤ n && resize!(s,n)
    for k = 1+i:n s[k] = a[k+2]-2a[k+1]+a[k] end
end

function generate!(S::Shift{T,D,O},n::Int) where {T,D,O}
    s,a = sequence(S),generator(S)
    i = length(s)
    i ≤ n && resize!(s,n)
    for k = 1+i:n s[k] = a[k+O] end
end

# Aitken's Δ² process

function generate!(S::Aitken,n::Int)
    s,a = sequence(S),generator(S)
    i = length(s)
    i ≤ n && resize!(s,n)
    for k = 1+i:n
        s[k] = a[k+2]-(a[k+2]-a[k+1])^2/(a[k+2]-2a[k+1]+a[k])
    end
end

# Wynn's ϵ- and ρ-algorithms

for trans in (:ϵ,:ρ)
    rec = Meta.parse(string(trans)*"recurrence!")
    @eval begin
        function generate!(S::$trans,n::Int)
            data,s = sequence(S),generator(S)
            aux = auxiliary(S)
            i = length(data)
            i ≤ n && resize!(data,n);resize!(aux,n)
            for k = 1+i:n
                aux[k] = s[k]
                $rec(k,aux,data)
            end
        end
    end
end

function ϵrecurrence!(k::Int,aux::Vector{T},data::Vector{T}) where T
    aux2 = zero(T)
    for j = k:-1:2
        aux1 = aux2
        aux2 = aux[j-1]
        diff = aux[j]-aux2
        aux[j-1] = aux1 + one(T)/diff
    end
    data[k] = isodd(k) ? aux[1] : aux[2]
end

function ρrecurrence!(k::Int,aux::Vector{T},data::Vector{T}) where T
    aux2 = zero(T)
    for j = k:-1:2
        aux1 = aux2
        aux2 = aux[j-1]
        diff = aux[j]-aux2
        aux[j-1] = aux1 + j/diff
    end
    data[k] = isodd(k) ? aux[1] : aux[2]
end

# Drummond, Levin, and Weniger transformations

for trans in (:Drummond,:Levin,:Weniger)
    rec = Meta.parse(string(trans)*"recurrence!")
    @eval begin
        function generate!(S::$trans{T},n::Int) where T
            ℓ,s = sequence(S),generator(S)
            N,D = numerator(S),denominator(S)
            i = length(ℓ)
            i ≤ n && resize!(ℓ,n);resize!(N,n);resize!(D,n)
            for k = 1+i:n
                ω = remainder(S,k)
                N[k],D[k] = s[k]/ω,one(T)/ω
                $rec(k,ω,N,D,ℓ)
            end
        end
    end
end

function Drummondrecurrence!(k::Int,ω::T,N::Vector{T},D::Vector{T},ℓ::Vector{T}) where T
    for j = 1:k-1
        N[k-j] = N[k-j+1]-N[k-j]
        D[k-j] = D[k-j+1]-D[k-j]
    end
    ℓ[k] = N[1]/D[1]
end

function Levinrecurrence!(k::Int,ω::T,N::Vector{T},D::Vector{T},ℓ::Vector{T}) where T
    kp1 = k+one(T)
    kdkp1 = k/kp1
    kdkp1jm2 = 1/kdkp1
    for j = 1:k-1
        cst = -(kp1-j)*kdkp1jm2/kp1
        N[k-j] = muladd(cst,N[k-j],N[k-j+1])
        D[k-j] = muladd(cst,D[k-j],D[k-j+1])
        kdkp1jm2 *= kdkp1
    end
    ℓ[k] = N[1]/D[1]
end

function Wenigerrecurrence!(k::Int,ω::T,N::Vector{T},D::Vector{T},ℓ::Vector{T}) where T
    km1 = k-one(T)
    for j = 1:k-1
        kjm1 = km1+j
        kjm2 = kjm1-1
        cst = -k*(km1/kjm1)/kjm2
        N[k-j] = muladd(cst,N[k-j],N[k-j+1])
        D[k-j] = muladd(cst,D[k-j],D[k-j+1])
    end
    ℓ[k] = N[1]/D[1]
end


#push!(LOAD_PATH, "/home/caseykneale/Desktop/ChemometricsTools/ChemometricsTools.jl/");
using ChemometricsTools
using Plots

#View the data in the package space
ChemometricsToolsDatasets()
#Load the data from package-space: remove the time column while we're at it
DF = ChemometricsToolsDataset("actuator.csv");
actuator = convert(Matrix,  DF)[:,2:end];
#Split into a training set based on normal behaviour
(Train, Test) = SplitByProportion(actuator, 0.40)

#Plot the data
plot(Train, legend = false, title = "Train Set - Raw Data", ylabel = "Measurements", xlabel = "time")
#alot of DC offsets let's column center each variable.
clean = Center(Train);
pca = PCA(clean(Train); Factors = 5);
cumsum( ExplainedVariance( pca ) )
#Old Hotelling Interface
#(Lambdas, limits) = Hotelling(clean(Train), pca; Quantile = 0.015, Variance = 0.95)
T = Hotelling(clean(Train), pca; Quantile = 0.015, Variance = 0.95)
scatter(T(clean(Train)), title = "Train - T2 vs Time", xlabel = "time", ylabel = "T2", label = "T2");
hline!([T.UpperLimit], label = "Control Limit")

#Old Q Interface
#(qs, limits) = Q(clean(Train), pca; Quantile = 1.0 - 1e-16, Variance = 0.99)
q_residuals = Q(clean(Train), pca; Quantile = 1.0 - 1e-16, Variance = 0.99)
scatter(q_residuals(Train), title = "Train - Q vs Time", xlabel = "time", ylabel = "Q", label = "Q");
hline!([T.UpperLimit], label = "Control Limit")

#Let's apply the control limits obtained from the train set to the test set...
scatter(T(clean(Test)), title = "Test - T2 vs Time", xlabel = "time", ylabel = "T2", label = "T2");
hline!([T.UpperLimit], label = "Control Limit");
vline!([120], label = "Known Fault")

scatter(q_residuals(clean(Test)), title = "Test - Q vs Time", xlabel = "time", ylabel = "Q", label = "Q");
hline!([q_residuals.UpperLimit], label = "Control Limit");
vline!([120], label = "Known Fault")

#Hotellings T2 is slower to detect the failure then the Q plot. Not a surprise but good to see.

#Let's try an EWMA on a single variable...
plot(Train[:,3], title = "Variable #1", xlabel = "time", ylabel = "Magnitude")
#Use the "Burn in" constructor for EWMA
EWM = EWMA(Train[:,3], 0.1)
(UpperEWMA, LowerEWMA) = Limits(EWM; k = 3)

#Let's get to predicting already...
ewmascore = []
for s in 1:length( Test[:,3] )
    push!( ewmascore, EWM( Test[s,3] ; train = false) )
end
plot(ewmascore, label = "EWMA", title = "EWMA Fault detection", ylabel = "EWMA", xlabel = "time");
hline!([UpperEWMA], label = "Upper Limit");
hline!([LowerEWMA], label = "Lower Limit");
vline!([120], label = "Known Fault")

score = OneClassJKNN( Train, Test; J = 1, K = 1,  DistanceType = "euclidean" );
scatter(log10.(score));
hline!([1.0], label = "Threshold of 1");
vline!([120], label = "Known Fault")


using ManifoldsBase
using Test

struct ErrorTestManifold <: AbstractManifold{ℝ} end

function ManifoldsBase.check_point(::ErrorTestManifold, x)
    if any(u -> u < 0, x)
        return DomainError(x, "<0")
    end
    return nothing
end
function ManifoldsBase.check_vector(M::ErrorTestManifold, x, v)
    mpe = check_point(M, x)
    mpe === nothing || return mpe
    if any(u -> u < 0, v)
        return DomainError(v, "<0")
    end
    return nothing
end

@testset "Domain errors" begin
    M = ErrorTestManifold()
    @test isa(check_point(M, [-1, 1]), DomainError)
    @test check_point(M, [1, 1]) === nothing
    @test !is_point(M, [-1, 1])
    @test is_point(M, [1, 1])
    @test_throws DomainError is_point(M, [-1, 1], true)

    @test isa(check_vector(M, [1, 1], [-1, 1]), DomainError)
    @test check_vector(M, [1, 1], [1, 1]) === nothing
    @test !is_vector(M, [1, 1], [-1, 1])
    @test is_vector(M, [1, 1], [1, 1])
    @test_throws DomainError is_vector(M, [1, 1], [-1, 1], true)
end


#=
Tools for solving the standard optimal savings / income fluctuation
problem for an infinitely lived consumer facing an exogenous income
process that evolves according to a Markov chain.

@author : Spencer Lyon <spencer.lyon@nyu.edu>

@date: 2014-08-18

References
----------

http://quant-econ.net/jl/ifp.html

=#
# using PyCall
# @pyimport scipy.optimize as opt
# brentq = opt.brentq

"""
Income fluctuation problem

##### Fields

- `u::Function` : Utility `function`
- `du::Function` : Marginal utility `function`
- `r::Real` : Strictly positive interest rate
- `R::Real` : The interest rate plus 1 (strictly greater than 1)
- `bet::Real` : Discount rate in (0, 1)
- `b::Real` :  The borrowing constraint
- `Pi::Matrix` : Transition matrix for `z`
- `z_vals::Vector` : Levels of productivity
- `asset_grid::AbstractVector` : Grid of asset values
"""
type ConsumerProblem
    u::Function
    du::Function
    r::Real
    R::Real
    bet::Real
    b::Real
    Pi::Matrix
    z_vals::Vector
    asset_grid::AbstractVector
end

"Marginal utility for log utility function"
default_du{T <: Real}(x::T) = 1.0 / x

"""
Constructor with default values for `ConsumerProblem`

##### Arguments

- `r::Real(0.01)` : Strictly positive interest rate
- `bet::Real(0.96)` : Discount rate in (0, 1)
- `Pi::Matrix{Float64}([0.6 0.4; 0.05 0.95])` : Transition matrix for `z`
- `z_vals::Vector{Float64}([0.5, 1.0])` : Levels of productivity
- `b::Real(0.0)` : Borrowing constraint
- `grid_max::Real(16)` : Maximum in grid for asset holdings
- `grid_size::Int(50)` : Number of points in grid for asset holdings
- `u::Function(log)` : Utility `function`
- `du::Function(x->1/x)` : Marginal utility `function`

##### Notes

$(____kwarg_note)

"""
function ConsumerProblem(r=0.01, bet=0.96, Pi=[0.6 0.4; 0.05 0.95],
                         z_vals=[0.5, 1.0], b=0.0, grid_max=16, grid_size=50,
                         u=log, du=default_du)
    R = 1 + r
    asset_grid = linspace_range(-b, grid_max, grid_size)

    ConsumerProblem(u, du, r, R, bet, b, Pi, z_vals, asset_grid)
end

# make kwarg version
function ConsumerProblem(;r=0.01, beta=0.96, Pi=[0.6 0.4; 0.05 0.95],
                         z_vals=[0.5, 1.0], b=0.0, grid_max=16, grid_size=50,
                         u=log, du=x -> 1./x)
    ConsumerProblem(r, beta, Pi, z_vals, b, grid_max, grid_size, u, du)
end

"""
$(____bellman_main_docstring).

##### Arguments

- `cp::ConsumerProblem` : Instance of `ConsumerProblem`
- `v::Matrix`: Current guess for the value function
- `out::Matrix` : Storage for output
- `;ret_policy::Bool(false)`: Toggles return of value or policy functions

##### Returns

None, `out` is updated in place. If `ret_policy == true` out is filled with the
policy function, otherwise the value function is stored in `out`.

"""
function bellman_operator!(cp::ConsumerProblem, V::Matrix, out::Matrix;
                           ret_policy::Bool=false)
    # simplify names, set up arrays
    R, Pi, bet, u, b = cp.R, cp.Pi, cp.bet, cp.u, cp.b
    asset_grid, z_vals = cp.asset_grid, cp.z_vals

    new_V = similar(V)
    new_c = similar(V)

    z_idx = 1:length(z_vals)

    # Linear interpolation of V along the asset grid
    vf(a, i_z) = CoordInterpGrid(asset_grid, V[:, i_z], BCnearest,
                                 InterpLinear)[a]

    # compute lower_bound for optimization
    opt_lb = minimum(z_vals) - 1e-5

    # solve for RHS of Bellman equation
    for (i_z, z) in enumerate(z_vals)
        for (i_a, a) in enumerate(asset_grid)

            function obj(c)
                y = sum([vf(R*a+z-c, j) * Pi[i_z, j] for j=z_idx])
                return -u(c)  - bet * y
            end
            res = optimize(obj, opt_lb, R.*a.+z.+b)
            c_star = res.minimum
            if ret_policy
                out[i_a, i_z] = c_star
            else
               out[i_a, i_z] = - obj(c_star)
            end
        end
    end
end

function bellman_operator(cp::ConsumerProblem, V::Matrix; ret_policy=false)
    out = similar(V)
    bellman_operator!(cp, V, out, ret_policy=ret_policy)
    return out
end

"""
$(____greedy_main_docstring).

##### Arguments

- `cp::CareerWorkerProblem` : Instance of `CareerWorkerProblem`
- `v::Matrix`: Current guess for the value function
- `out::Matrix` : Storage for output

##### Returns

None, `out` is updated in place to hold the policy function

"""
function get_greedy!(cp::ConsumerProblem, V::Matrix, out::Matrix)
    bellman_operator!(cp, V, out, ret_policy=true)
end

function get_greedy(cp::ConsumerProblem, V::Matrix)
    bellman_operator(cp, V, ret_policy=true)
end

"""
The approximate Coleman operator.

Iteration with this operator corresponds to policy function
iteration. Computes and returns the updated consumption policy
c.  The array c is replaced with a function cf that implements
univariate linear interpolation over the asset grid for each
possible value of z.

##### Arguments

- `cp::CareerWorkerProblem` : Instance of `CareerWorkerProblem`
- `c::Matrix`: Current guess for the policy function
- `out::Matrix` : Storage for output

##### Returns

None, `out` is updated in place to hold the policy function

"""
function coleman_operator!(cp::ConsumerProblem, c::Matrix, out::Matrix)
    # simplify names, set up arrays
    R, Pi, bet, du, b = cp.R, cp.Pi, cp.bet, cp.du, cp.b
    asset_grid, z_vals = cp.asset_grid, cp.z_vals
    z_size = length(z_vals)
    gam = R * bet
    vals = Array(Float64, z_size)

    # linear interpolation to get consumption function. Updates vals inplace
    function cf!(a, vals)
        for i=1:z_size
            vals[i] = CoordInterpGrid(asset_grid, c[:, i], BCnearest,
                                      InterpLinear)[a]
        end
        Void
    end

    # compute lower_bound for optimization
    opt_lb = minimum(z_vals) - 1e-2
    for (i_z, z) in enumerate(z_vals)
        for (i_a, a) in enumerate(asset_grid)
            function h(t)
                cf!(R*a+z-t, vals)  # update vals
                expectation = dot(du(vals), vec(Pi[i_z, :]))
                return abs(du(t) - max(gam * expectation, du(R*a+z+b)))
            end
            opt_ub = R*a + z + b  # addresses issue #8 on github
            res = optimize(h, min(opt_lb, opt_ub - 1e-2), opt_ub, method=:brent)
            out[i_a, i_z] = res.minimum
        end
    end
    return out
end

"""
Apply the Coleman operator for a given model and initial value

See the specific methods of the mutating version of this function for more
details on arguments
"""
function coleman_operator(cp::ConsumerProblem, c::Matrix)
    out = similar(c)
    coleman_operator!(cp, c, out)
    return out
end

function init_values(cp::ConsumerProblem)
    # simplify names, set up arrays
    R, bet, u, b = cp.R, cp.bet, cp.u, cp.b
    asset_grid, z_vals = cp.asset_grid, cp.z_vals
    shape = length(asset_grid), length(z_vals)
    V, c = Array(Float64, shape...), Array(Float64, shape...)

    # Populate V and c
    for (i_z, z) in enumerate(z_vals)
        for (i_a, a) in enumerate(asset_grid)
            c_max = R*a + z + b
            c[i_a, i_z] = c_max
            V[i_a, i_z] = u(c_max) ./ (1 - bet)
        end
    end

    return V, c
end


using ApproxFun, Base.Test

ef = Fun(exp,Interval())

@test ef == -(-ef)
@test ef == (ef-1) + 1

ef = Fun(exp)

@test ef == -(-ef)
@test ef == (ef-1) + 1

cf = Fun(cos)

ecf = Fun(x->cos(x).*exp(x))
eocf = Fun(x->cos(x)./exp(x))

@test_approx_eq ef(.5) exp(.5)
@test_approx_eq ecf(.123456) cos(.123456).*exp(.123456)

r=2.*rand(100) .- 1

@test maximum(abs(ef(r)-exp(r)))<100eps()
@test maximum(abs(ecf(r)-cos(r).*exp(r)))<100eps()

@test norm((ecf-cf.*ef).coefficients)<100eps()

@test maximum(abs((eocf-cf./ef).coefficients))<1000eps()

@test norm(((ef/3).*(3/ef)-1).coefficients)<1000eps()

## Diff and cumsum

@test norm((ef - ef').coefficients)<10E-11

@test norm((ef - cumsum(ef)').coefficients) < 20eps()
@test norm((cf - cumsum(cf)').coefficients) < 20eps()

@test_approx_eq sum(ef)  2.3504023872876028

@test_approx_eq norm(ef)  1.90443178083307

##Check other domains

ef = Fun(exp,[1,2])
cf = Fun(cos,[1,2])

ecf = Fun(x->cos(x).*exp(x),[1,2])
eocf = Fun(x->cos(x)./exp(x),[1,2])

r=rand(100) .+ 1
x=1.5

@test_approx_eq ef(x) exp(x)

@test maximum(abs(ef(r)-exp(r)))<100eps()
@test maximum(abs(ecf(r)-cos(r).*exp(r)))<100eps()

@test norm((ecf-cf.*ef).coefficients)<100eps()

@test maximum(abs((eocf-cf./ef).coefficients))<1000eps()

@test norm(((ef/3).*(3/ef)-1).coefficients)<1000eps()

## Diff and cumsum

@test norm((ef - ef').coefficients)<10E-11

@test norm((ef - cumsum(ef)').coefficients) < 10eps()
@test norm((cf - cumsum(cf)').coefficients) < 10eps()

@test_approx_eq sum(ef) 4.670774270471604

@test_approx_eq norm(ef) 4.858451087240335

##Roots

f=Fun(x->sin(10(x-.1)))
@test norm(f(roots(f)))< 1000eps()

## ALiasing

f=Fun(x->cos(50acos(x)))
@test norm(f.coefficients-eye(ncoefficients(f))[:,51])<100eps()

## Int values

@test_approx_eq Fun(x->2,10)(.1) 2
@test_approx_eq Fun(x->2)(.1) 2

@test_approx_eq Fun(Float64[],Chebyshev)([0.,1.]) [0.,0.]
@test_approx_eq Fun([],Chebyshev)(0.) 0.
@test_approx_eq Fun(x->[1.,0.])(0.) [1.,0.]


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.10.3
#   kernelspec:
#     display_name: Julia 1.6.3
#     language: julia
#     name: julia-1.6
# ---

# %%
using BenchmarkTools
using Plots
gr(fmt=:png)
using CUDA

# %%
plotjulia(j) = heatmap(j; c=:gist_earth,
    size=(540, 540), colorbar=false, ticks=false, frame=false)

plotmandelbrot(m) = heatmap(m; c=reverse(cgrad(:jet1)),
    size=(540, 540), colorbar=false, ticks=false, frame=false)

function julia(z, c, maxiters=2^10, threshold_abs2=Inf)
    for i in Base.OneTo(maxiters)
        z = z * z + c
        abs2(z) ≥ threshold_abs2 && return i
    end
    maxiters + oneunit(maxiters)
end

mandelbrot(c, maxiters=2^10, threshold_abs2=Inf) =
    julia(zero(c), c, maxiters, threshold_abs2)

# %%
n = 2^9
x = range(-1.5, 1.5; length=n)
y = range(-1.5, 1.5; length=n)
z = complex.(x', y)
c = -0.380 + 0.605im

@show typeof(z)
@time j = julia.(z, c, 2^8)
@time j = julia.(z, c, 2^8)
@show typeof(j)
plotjulia(j)

# %%
n = 2^9
x = range(-1.5, 1.5; length=n)
y = range(-1.5, 1.5; length=n)
z = complex.(x', y)
c = -0.380 + 0.605im

c32 = ComplexF32(c)
z_cuda32 = cu(z)

@show typeof(z_cuda32)
@time j_cuda32 = julia.(z_cuda32, c32, Int32(2^8), Inf32)
@time j_cuda32 = julia.(z_cuda32, c32, Int32(2^8), Inf32)
@show typeof(j_cuda32)
@time cj_cuda32 = collect(j_cuda32)
plotjulia(cj_cuda32)

# %%
n = 2^9
x = range(-1.5, 1.5; length=n)
y = range(-1.5, 1.5; length=n)
z = complex.(x', y)
c = -0.380 + 0.605im

z_cuda = CuMatrix{ComplexF64}(z)

@show typeof(z_cuda)
@time j_cuda = julia.(z_cuda, c, 2^8)
@time j_cuda = julia.(z_cuda, c, 2^8)
@show typeof(j_cuda)
@time cj_cuda = collect(j_cuda)
plotjulia(cj_cuda)

# %%
n = 2^9
x = range(-1.6, 0.5; length=n)
y = range(-1.1, 1.1; length=n)
c = complex.(x', y)

@show typeof(c)
@time m = mandelbrot.(c, 2^6)
@time m = mandelbrot.(c, 2^6)
@show typeof(m)
plotmandelbrot(m)

# %%
n = 2^9
x = range(-1.6, 0.5; length=n)
y = range(-1.1, 1.1; length=n)
c = complex.(x', y)
c_cuda = CuMatrix{ComplexF64}(c)

@show typeof(c_cuda)
@time m_cuda = mandelbrot.(c_cuda, 2^6)
@time m_cuda = mandelbrot.(c_cuda, 2^6)
@show typeof(m_cuda)
@time cm_cuda = collect(m_cuda)
@show typeof(cm_cuda)
plotmandelbrot(cm_cuda)

# %%
n = 2^9
x = range(-0.714689, -0.714679; length=n)
y = range( 0.299872,  0.299882; length=n)
c = complex.(x', y)

@show typeof(c)
@time m = mandelbrot.(c, 2^12)
@time m = mandelbrot.(c, 2^12)
@show typeof(m)
plotmandelbrot(m)

# %%
n = 2^9
x = range(-0.714689, -0.714679; length=n)
y = range( 0.299872,  0.299882; length=n)
c = complex.(x', y)
c_cuda = CuMatrix{ComplexF64}(c)

@show typeof(c_cuda)
@time m_cuda = mandelbrot.(c_cuda, 2^12)
@time m_cuda = mandelbrot.(c_cuda, 2^12)
@show typeof(m_cuda)
@time cm_cuda = collect(m_cuda)
@show typeof(cm_cuda)
plotmandelbrot(cm_cuda)

# %%


using Sundials, Test, ModelingToolkit, LinearAlgebra, IncompleteLU
import AlgebraicMultigrid

const N = 32
const xyd_brusselator = range(0,stop=1,length=N)
brusselator_f(x, y, t) = (((x-0.3)^2 + (y-0.6)^2) <= 0.1^2) * (t >= 1.1) * 5.
limit(a, N) = a == N+1 ? 1 : a == 0 ? N : a

function brusselator_2d_loop(du, u, p, t)
  A, B, alpha, dx = p
  alpha = alpha/dx^2
  @inbounds for I in CartesianIndices((N, N))
    i, j = Tuple(I)
    x, y = xyd_brusselator[I[1]], xyd_brusselator[I[2]]
    ip1, im1, jp1, jm1 = limit(i+1, N), limit(i-1, N), limit(j+1, N), limit(j-1, N)
    du[i,j,1] = alpha*(u[im1,j,1] + u[ip1,j,1] + u[i,jp1,1] + u[i,jm1,1] - 4u[i,j,1]) +
                B + u[i,j,1]^2*u[i,j,2] - (A + 1)*u[i,j,1] + brusselator_f(x, y, t)
    du[i,j,2] = alpha*(u[im1,j,2] + u[ip1,j,2] + u[i,jp1,2] + u[i,jm1,2] - 4u[i,j,2]) +
                A*u[i,j,1] - u[i,j,1]^2*u[i,j,2]
    end
end
p = (3.4, 1., 10., step(xyd_brusselator))

function init_brusselator_2d(xyd)
  N = length(xyd)
  u = zeros(N, N, 2)
  for I in CartesianIndices((N, N))
    x = xyd[I[1]]
    y = xyd[I[2]]
    u[I,1] = 22*(y*(1-y))^(3/2)
    u[I,2] = 27*(x*(1-x))^(3/2)
  end
  u
end
u0 = init_brusselator_2d(xyd_brusselator)
prob_ode_brusselator_2d = ODEProblem(brusselator_2d_loop,
                                     u0,(0.,11.5),p)

prob_ode_brusselator_2d_mtk = ODEProblem(modelingtoolkitize(prob_ode_brusselator_2d),[],(0.0,11.5),jac=true,sparse=true);

u0 = prob_ode_brusselator_2d_mtk.u0
p  = prob_ode_brusselator_2d_mtk.p
const jaccache = prob_ode_brusselator_2d_mtk.f.jac(u0,p,0.0)
const W = I - 1.0*jaccache

prectmp = ilu(W, τ = 50.0)
const preccache = Ref(prectmp)

function psetupilu(p, t, u, du, jok, jcurPtr, gamma)
  if jok
    prob_ode_brusselator_2d_mtk.f.jac(jaccache,u,p,t)
    jcurPtr[] = true

    # W = I - gamma*J
    @. W = -gamma*jaccache
    idxs = diagind(W)
    @. @view(W[idxs]) = @view(W[idxs]) + 1

    # Build preconditioner on W
    preccache[] = ilu(W, τ = 5.0)
  end
end

function precilu(z,r,p,t,y,fy,gamma,delta,lr)
  ldiv!(z,preccache[],r)
end

prectmp2 = AlgebraicMultigrid.aspreconditioner(AlgebraicMultigrid.ruge_stuben(W, presmoother = AlgebraicMultigrid.Jacobi(rand(size(W,1))), postsmoother = AlgebraicMultigrid.Jacobi(rand(size(W,1)))))
const preccache2 = Ref(prectmp2)
function psetupamg(p, t, u, du, jok, jcurPtr, gamma)
  if jok
    prob_ode_brusselator_2d_mtk.f.jac(jaccache,u,p,t)
    jcurPtr[] = true

    # W = I - gamma*J
    @. W = -gamma*jaccache
    idxs = diagind(W)
    @. @view(W[idxs]) = @view(W[idxs]) + 1

    # Build preconditioner on W
    preccache2[] = AlgebraicMultigrid.aspreconditioner(AlgebraicMultigrid.ruge_stuben(W, presmoother = AlgebraicMultigrid.Jacobi(rand(size(W,1))), postsmoother = AlgebraicMultigrid.Jacobi(rand(size(W,1)))))
  end
end

function precamg(z,r,p,t,y,fy,gamma,delta,lr)
  ldiv!(z,preccache2[],r)
end

sol1 = solve(prob_ode_brusselator_2d,CVODE_BDF(linear_solver=:GMRES),save_everystep=false);
sol2 = solve(prob_ode_brusselator_2d,CVODE_BDF(linear_solver=:GMRES,prec=precilu,psetup=psetupilu,prec_side=1),save_everystep=false);
sol3 = solve(prob_ode_brusselator_2d,CVODE_BDF(linear_solver=:GMRES,prec=precamg,psetup=psetupamg,prec_side=1),save_everystep=false);
@test sol1.destats.nf > sol2.destats.nf
@test sol1.destats.nf > sol3.destats.nf


############
## TlsIID ##
############

type TlsIID <: AbstrUnivarModel
    ν::Float64
    μ::Float64
    a::Float64
    function TlsIID(ν::Float64, μ::Float64, a::Float64)
        if a <= 0
            error("scaling parameter a must be larger than 0")
        end
        if ν <= 0
            error("ν parameter must be larger than 0")
        end
        if ν <= 2
            warn("ν parameter is smaller than 2")
        end
        return new(ν, μ, a)
    end
end

function TlsIID(params::Array{Float64, 1})
    return TlsIID(params...)
end

import Base.Multimedia.display
function display(mod::TlsIID)
    nDigits = 3
    println("\nIID, constant univariate t-location scale distribution:")
    println("    ν: $(round(mod.ν, nDigits))")
    println("    μ: $(round(mod.μ, nDigits))")
    println("    a: $(round(mod.a, nDigits))")
end

function description(mod::TlsIID)
    return "IID, Student's t location scale distributed"
end

## getParams
##----------

function getParams(mod::TlsIID)
    return (mod.ν, mod.μ, mod.a)
end

## simulate
##---------

function simulate(mod::TlsIID, nObs::Int)
    ## simulate from TlsIID model
    t = TDist(mod.ν)
    return rand(t, nObs)*mod.a + mod.μ
end

## estimate
##---------

function estimate(dt::Type{TlsIID}, data::Array{Float64, 1})
    tlsFit = fit(TLSDist, data)
    return TlsIID(getParams(tlsFit)...)
end

## resimulate
##-----------

@doc doc"""
Fit model and resimulate data from it.
"""->
function resimulate(data::AbstractTimenum,
                    dt::Type{Econometrics.TlsIID},
                    nPaths::Int = 1)
    ## get returns without NAs
    pureRets = dropna(data.vals[1])
    nRets = size(pureRets, 1)

    ## fit model to pure returns
    modFit = estimate(dt, pureRets)

    return resimulate(data, modFit, nPaths)
end


module test_julia_complex

using Test

# https://en.wikipedia.org/wiki/Conjugate_transpose#Example
A = [1     -2-im  5
     1+im     im  4-2im]

@test transpose(A) == [ 1      1+im
                       -2-im     im
                        5     4-2im]

@test A' == adjoint(A) == [ 1      1-im
                           -2+im    -im
                            5     4+2im]

@test conj(A) == [1     -2+im  5
                  1-im    -im  4+2im]

end # module test_julia_complex


"""
	drawcircle!(img,i,j,r[,trange])
Modifies the image to superimpose a circle of specified center and radius. If `trange` is given, it's a 2-vector defining the range of angles to be used, measured ccw from "straight down" in the usual image visualization (i.e., vertically flipped).
"""
function drawcircle!(img,i,j,r,trange::AbstractVector=[-π,π];usecolor=RGB(1,0,0.8))
	t = π/2 .+ LinRange(trange[1],trange[2],5size(img,2))
	valid = i -> checkbounds(Bool,img,i)
	for r in 0.99r:1.01r
		idx = CartesianIndex.(zip(round.(Int,i.+r*sin.(t)),round.(Int,j.+r*cos.(t))))
		idx = filter(valid,idx)
		img[idx] .= usecolor
	end
	return img
end

"""
	makemovie(files,result,size;numframes=Inf)
Make a movie to show the detected cornea on each image named in the string vector `files`, using the detection results in `result` and the given `size` as [height,width]. If a value is given for `numframes`, frames will be skipped to bring the total frame count to be no greater than that value. Returns a vector of images.
"""
function makemovie(folder::AbstractVector{String},result,sz;numframes=Inf)
	imgstack = []
	circ = sz[1].*[ result.cenrow result.cencol result.radius ]
	N = length(folder)
	select = 1:max(1,ceil(Int,(N-1)/numframes)):N
	@showprogress for k in select
		img = imresize(RGB.(load(folder[k])),sz...)
		drawcircle!(img,circ[k,:]...)
		push!(imgstack,img)
	end
	return imgstack
end


function test_uniform_grid_type(T, N, L)
  grid = UniformGrid(T, N, L)
  T == typeof(grid.L) && T == eltype(grid.zc) && T == eltype(grid.zf)
end

function test_uniform_grid_spacing(T, N, L)
  Δ = T(L/N)
  grid = UniformGrid(T, N, L)
  Δ == grid.Δc && Δ == grid.Δf
end

function test_uniform_grid_limits_zc_left(T, N, L)
  Δ = L/N
  grid = UniformGrid(T, N, L)
  grid.zc[1] ≈ -L+0.5Δ
end

function test_uniform_grid_limits_zc_right(T, N, L)
  Δ = L/N
  grid = UniformGrid(T, N, L)
  grid.zc[N] ≈ -0.5Δ
end

function test_uniform_grid_limits_zf_left(T, N, L)
  Δ = L/N
  grid = UniformGrid(T, N, L)
  grid.zf[1] ≈ -L
end

function test_uniform_grid_limits_zf_right(T, N, L)
  Δ = L/N
  grid = UniformGrid(T, N, L)
  grid.zf[N+1] ≈ 0
end

@testset "Grids" begin
    nz, Lz = 3, 4.2
    for T in (Float64, Float32, Float16)
        @test test_uniform_grid_type(T, nz, Lz)
        @test test_uniform_grid_spacing(T, nz, Lz)
        @test test_uniform_grid_limits_zc_right(T, nz, Lz)
        @test test_uniform_grid_limits_zf_right(T, nz, Lz)
        @test test_uniform_grid_limits_zc_left(T, nz, Lz)
        @test test_uniform_grid_limits_zf_left(T, nz, Lz)
    end
end