#
# Linear fit
#

struct Linear
  a :: Float64
  b :: Float64
  R :: Float64
  x :: Vector{Float64}
  y :: Vector{Float64}
  ypred :: Vector{Float64}
  residues :: Vector{Float64}
end

"""
`fitlinear(x,y)`

Obtains the linear fit: ``y = a*x + b``

Optional lower and upper bounds for a, and constant b can be provided using, for example:

```
fitlinear(x,y, l=lower(a=0.), b=3)
```

# Examples
```jldoctest
julia> x = sort(rand(10)) ; y = sort(rand(10));

julia> fit = fitlinear(x,y)

------------------- Linear Fit -------------

Equation: y = ax + b

With: a = 1.0448783208110997
      b = 0.18817627115683894

Pearson correlation coefficient, R = 0.8818586822210751
Average absolute residue = 0.14274752107157443

Predicted Y: ypred = [0.1987357699444139, 0.32264343301109627...
residues = [0.1613987313816987, 0.22309410865095275...

--------------------------------------------

```
"""
function fitlinear(X :: AbstractArray{<:Real}, Y :: AbstractArray{<:Real};
                   l :: lower = lower(), u :: upper = upper(), b = nothing,
                   options :: Options = Options() )
  # Check data
  X, Y = checkdata(X,Y,options)
  # Set bounds
  vars = [ VarType(:a,Number,1),
           VarType(:b,Nothing,1) ]
  lower, upper = setbounds(vars,l,u)
  if b == nothing
    # Set model
    @. model(x,p) = p[1]*x + p[2]
    # Initial point
    p0 = Vector{Float64}(undef,2)
    initP!(p0,options,lower,upper)
    # Fit
    fit = curve_fit(model, X, Y, p0, lower=lower, upper=upper)
    # Analyze results and return
    R = pearson(X,Y,model,fit)
    x, y, ypred = finexy(X,length(X),model,fit)
    return Linear(fit.param...,R,x,y,ypred,fit.resid)
  else
    lower = [lower[1]]; upper = [upper[1]];
    # Set model
    @. model_const(x,p) = p[1]*x + b
    # Initial point
    p0 = Vector{Float64}(undef,1)
    initP!(p0,options,lower,upper)
    # Fit
    fit = curve_fit(model_const, X, Y, p0, lower=lower, upper=upper)
    # Analyze results and return
    R = pearson(X,Y,model_const,fit)
    x, y, ypred = finexy(X,length(X),model_const,fit)
    return Linear(fit.param...,b,R,x,y,ypred,fit.resid)
  end
end

function Base.show( io :: IO, fit :: Linear )
  println("")
  println(" ------------------- Linear Fit ------------- ")
  println("")
  println(" Equation: y = ax + b ")
  println("")
  println(" With: a = ", fit.a)
  println("       b = ", fit.b)
  println("")
  println(" Pearson correlation coefficient, R = ", fit.R)
  println(" Average square residue = ", mean(fit.residues.^2))
  println("")
  println(" Predicted Y: ypred = [",fit.ypred[1],", ",fit.ypred[2],"...")
  println(" residues = [", fit.residues[1],", ",fit.residues[2],"...")
  println("")
  println(" -------------------------------------------- ")
end

export fitlinear


### ADAPTIVE GAUSS-LOBATTO FROM GANDER AND GAUTSCHI (2000)

### constants from section 4 of the paper
const α = sqrt(2/3)
const β = 1/sqrt(5)

const X1 = 0.94288241569547971905635175843185720232
const X2 = 0.64185334234578130578123554132903188354
const X3 = 0.23638319966214988028222377349205292599

const A = 0.015827191973480183087169986733305510591
const B = 0.094273840218850045531282505077108171960
const C = 0.155071987336585396253963597980210298680
const D = 0.18882157396018245442000533937297167125
const E = 0.199773405226858526792068022066048840246
const F = 0.22492646533333952701601768799639508076
const G = 0.24261107190140773379964095790325635233

"""
    adaptlob(f,a,b; tol::Real=1e-10, trace::Bool=false)

Numerically integrate `f` over the closed real interval `[a,b]` to within tolerance `tol` using the adaptive Gauss-Lobatto method from Gander and Gautschi (2000).

Passing `trace=true` prints out information about where the function is being sampled.

`f` must be defined at `a` and `b`.

```
using QuadGG
adaptlob(sqrt,0,1)
```
"""
function adaptlob(f,a,b; tol::Real=1e-10, trace::Bool=false)
    m = (a+b)/2
    h = (b-a)/2

    x = (a,m-X1*h,m-α*h,m-X2*h,m-β*h,m-X3*h,m,m+X3*h,m+β*h,m+X2*h,m+α*h,m+X1*h,b)
    y = f.(x)

    fa,fb = y[1],y[end]

    _i2 = (h/6) * (y[1] + y[13] + 5*(y[5]+y[9]))
    _i1 = (h/1470) * (77*(y[1]+y[13]) + 432*(y[3]+y[11]) + 625 * (y[2]+y[12]) + 672*y[7])

    _is = h * (A*(y[1]+y[13]) + B*(y[2]+y[12]) + C*(y[3]+y[11]) + D*(y[4]+y[10]) + E*(y[5]+y[9]) + F*(y[6]+y[8]) + G*y[7])

    sgn = sign(_is)
    sgn = ifelse(iszero(sgn),1,sgn)
    erri1 = abs(_i1 - _is)
    erri2 = abs(_i2 - _is)

    R = ifelse(!iszero(erri2), erri1/erri2, 1)

    if 0 < R < 1
        tol = tol/R
    end

    _is_new = sgn * abs(_is) * tol / eps()

    if iszero(_is_new)
        _is_new = b-a
    end

    flag = WarnFlag(false)
    Q = adaptlobstp(f,a,b,fa,fb,_is_new,trace,flag)
    return Q
end

function adaptlobstp(f,a,b,fa,fb,_is,trace,flag)
    m = (a+b)/2
    h = (b-a)/2

    mll = m-α*h
    ml = m-β*h
    mr = m+β*h
    mrr = m+α*h

    fmll = f(mll)
    fml = f(ml)
    fm = f(m)
    fmr = f(mr)
    fmrr = f(mrr)

    i2=(h/6)*(fa+fb+5*(fml+fmr))
    i1=(h/1470)*(77*(fa+fb)+432*(fmll+fmrr)+625*(fml+fmr)+672*fm)

    if ≈(_is+i1-i2, _is, atol=1e-16) || mll <= a || b <= mrr || !isfinite(_is)
        if (m <= a || b <= m) && !flag.nowarn
            @warn "Interval contains no more machine numbers. Required tolerance may not be met."
            flag.nowarn = true
        end
        Q = i1
        trace && @printf "%20.15f    %20.15f    %20.15f\n" a b-a Q
    else
        Q = adaptlobstp(f,a,mll,fa,fmll,_is,trace,flag) +
            adaptlobstp(f,mll,ml,fmll,fml,_is,trace,flag) +
            adaptlobstp(f,ml,m,fml,fm,_is,trace,flag) +
            adaptlobstp(f,m,mr,fm,fmr,_is,trace,flag) +
            adaptlobstp(f,mr,mrr,fmr,fmrr,_is,trace,flag) +
            adaptlobstp(f,mrr,b,fmrr,fb,_is,trace,flag)
    end
    return Q
end


##############################################################################
#
# REFERENCES: Forbes et al. "Statistical Distributions"
#
##############################################################################

immutable BetaPrime <: ContinuousUnivariateDistribution
    alpha::Float64
    beta::Float64
    function BetaPrime(a::Float64, b::Float64)
        (a > zero(a) && b > zero(b)) || error("Alpha and beta must be positive")
        new(float64(a), float64(b))
    end
end

BetaPrime() = BetaPrime(1.0, 1.0)

@distr_support BetaPrime 0.0 Inf

function mean(d::BetaPrime)
    d.beta > 1.0 ? d.alpha / (d.beta - 1.0) : NaN
end

mode(d::BetaPrime) = d.alpha > 1.0 ? (d.alpha - 1.0) / (d.beta + 1.0) : 0.0

function pdf(d::BetaPrime, x::Float64)
    α, β = d.alpha, d.beta
    (x^(α - 1.0) * (1.0 + x)^(-(α + β))) / beta(α, β)
end

cdf(d::BetaPrime, x::Float64) = cdf(Beta(d.alpha, d.beta), x / (one(x) + x))
ccdf(d::BetaPrime, x::Float64) = ccdf(Beta(d.alpha, d.beta), x / (one(x) + x))
logcdf(d::BetaPrime, x::Float64) = logcdf(Beta(d.alpha, d.beta), x / (one(x) + x))
logccdf(d::BetaPrime, x::Float64) = logccdf(Beta(d.alpha, d.beta), x / (one(x) + x))

quantile(d::BetaPrime, p::Float64) = (x = quantile(Beta(d.alpha,d.beta),p); x / (1.0-x))
cquantile(d::BetaPrime, p::Float64) = (x = cquantile(Beta(d.alpha,d.beta),p); x / (1.0-x))
invlogcdf(d::BetaPrime, p::Float64) = (x = invlogcdf(Beta(d.alpha,d.beta),p); x / (1.0-x))
invlogccdf(d::BetaPrime, p::Float64) = (x = invlogccdf(Beta(d.alpha,d.beta),p); x / (1.0-x))

function rand(d::BetaPrime)
    x = rand(Gamma(d.alpha, 1))
    y = rand(Gamma(d.beta, 1))
    x/y
end

function skewness(d::BetaPrime)
    α, β = d.alpha, d.beta
    β > 3.0 ? (2.0 * (2.0 * α + β - 1))/(β - 3.0) * sqrt((β - 2.0)/(α * (α + β - 1.0))) : NaN
end

function var(d::BetaPrime)
    α, β = d.alpha, d.beta
    β > 2.0 ? (α * (α + β - 1.0)) / ((β - 2.0) * (β - 1.0)^2) : NaN
end


module Mathes

import Statistics: mean
import LinearAlgebra: svd, diagm, diag

"""
    pca(data)
perform PCA using SVD
inputs:
    - data: M x N matrix of input data. (M dimensions, N trials)
outputs:
    - PC: each column is a principle component
    - V: M x 1 matrix of variances
"""
function pca(data::Array{T,2}) where T
    X = data .- mean(data, dims=2)
    Y = X' ./ sqrt(T(size(X,2)-1))
    U,S,PC = svd(Y)
    S = diagm(0=>S)
    V = S .* S

    # find the least variance vector
    indexList = sortperm(diag(V); rev=true)

    PCs = map(x->PC[:,x], indexList)
    return PCs, diag(V)[indexList]
end

end # end of module


#=
The first two consecutive numbers to have two distinct prime factors are:

14 = 2 × 7
15 = 3 × 5

The first three consecutive numbers to have three distinct prime factors are:

644 = 2² × 7 × 23
645 = 3 × 5 × 43
646 = 2 × 17 × 19.

Find the first four consecutive integers to have four distinct prime factors. What is the first of these numbers?
=#

using Primes

function calc()
  num = 4
  arr = Int64[]
  i = 7
  while length(arr) < num
    if (length(keys(factor(i))) == num) && (length(arr) == 0 || last(arr) == i-1)
      push!(arr, i)
    else
      arr = Int64[]
    end
    i += 1
  end
  arr[1]
end
@time println(calc())


#=

@authors : Spencer Lyon, John Stachurski

=#

using Optim

"""
The approximate Bellman operator, which computes and returns the
updated value function Tw on the grid points.  An array to store
the new set of values Tw is optionally supplied (to avoid having to
allocate new arrays at each iteration).  If supplied, any existing data in
Tw will be overwritten.

#### Arguments

`w` : Vector
      The value of the input function on different grid points
`grid` : Vector
         The set of grid points
`β` : AbstractFloat
         The discount factor
`u` : Function
      The utility function
`f` : Function
      The production function
`shocks` : Vector
           An array of draws from the shock, for Monte Carlo integration (to
           compute expectations).
`Tw` : Vector, optional (default=similar(w))
       Array to write output values to
`compute_policy` : Bool, optional (default=false)
                   Whether or not to compute policy function

"""
function bellman_operator(w::Vector,
                          grid::Vector,
                          β::AbstractFloat,
                          u::Function,
                          f::Function,
                          shocks::Vector,
                          Tw::Vector = similar(w);
                          compute_policy::Bool = false)

    # === Apply linear interpolation to w === #
    w_func = LinInterp(grid, w)

    if compute_policy
        σ = similar(w)
    end

    # == set Tw[i] = max_c { u(c) + β E w(f(y  - c) z)} == #
    for (i, y) in enumerate(grid)
        objective(c) = - u(c) - β * mean(w_func.(f(y - c) .* shocks))
        res = optimize(objective, 1e-10, y)

        if compute_policy
            σ[i] = res.minimizer
        end
        Tw[i] = - res.minimum
    end

    if compute_policy
        return Tw, σ
    else
        return Tw
    end
end


export softmax, softmax!, ∇softmax, ∇softmax!,
       logsoftmax, logsoftmax!, ∇logsoftmax, ∇logsoftmax!

"""
    softmax(x; dims=1)

[Softmax](https://en.wikipedia.org/wiki/Softmax_function) turns input array `x`
into probability distributions that sum to 1 along the dimensions specified by `dims`.
It is semantically equivalent to the following:

    softmax(x; dims=1) = exp.(x) ./ sum(exp.(x), dims=dims)

with additional manipulations enhancing numerical stability.

For a matrix input `x` it will by default (`dims=1`) treat it as a batch of vectors,
with each column independent. Keyword `dims=2` will instead treat rows independently,
etc...
```julia-repl
julia> softmax([1, 2, 3])
3-element Array{Float64,1}:
  0.0900306
  0.244728
  0.665241
```

See also [`logsoftmax`](@ref).
"""
function softmax(xs::AbstractArray; dims=1)
    max_ = maximum(xs, dims=dims)
    exp_ = exp.(xs .- max_)
    exp_ ./ sum(exp_, dims=dims)
end

function softmax!(out::AbstractVecOrMat{T}, xs::AbstractVecOrMat{T}) where {T}
    @inbounds for j = 1:size(xs, 2)
        # First, store column-wise maximum in the last element of `out`
        out[end, j] = xs[end, j]
        @inbounds for i = 1:(size(xs, 1) - 1)
            out[end, j] = max(out[end, j], xs[i, j])
        end

        # Subtract the column-wise maximums to normalize, take exp()
        # out .= exp(xs .- out[end, :])
        @inbounds for i = 1:size(out, 1)
            out[i, j] = exp(xs[i, j] - out[end, j])
        end

        # Normalize by sum of the entire thing
        # out ./= sum(out, 1)
        s = T(0)
        @inbounds for i = 1:size(out, 1)
            s += out[i, j]
        end
        @inbounds for i = 1:size(out, 1)
            out[i, j] /= s
        end
    end
    return out
end

function ∇softmax!(out::AbstractVecOrMat, Δ::AbstractVecOrMat, xs::AbstractVecOrMat)
    sf = softmax(xs)
    out .= sf .* (Δ .- sum(Δ .* sf, dims = 1))
end
function ∇softmax(Δ, xs; dims=1)
    sf = softmax(xs, dims=dims)
    sf .* (Δ .- sum(Δ .* sf, dims=dims))
end
∇softmax!(Δ, xs) = ∇softmax!(Δ, Δ, xs)

"""
    logsoftmax(x; dims=1)

Computes the log of softmax in a more numerically stable
way than directly taking `log.(softmax(xs))`. Commonly used in
computing cross entropy loss.

It is semantically equivalent to the following:

    logsoftmax(x; dims=1) = x .- log.(sum(exp.(x), dims=dims))

See also [`softmax`](@ref).
"""
function logsoftmax(xs::AbstractArray; dims=1)
    max_ = maximum(xs, dims=dims)
    exp_ = exp.(xs .- max_)
    log_ = log.(sum(exp_, dims=dims))
    (xs .- max_) .- log_
end

function logsoftmax!(out::AbstractVecOrMat, xs::AbstractVecOrMat)
    for j = 1:size(xs, 2)
        @inbounds begin
            xi_max = xs[1, j]
            for i = 1:size(out, 1)
                xi_max = max(xi_max, xs[i, j])
            end
            s = zero(eltype(out))
            for i = 1:size(out, 1)
                s += exp(xs[i, j] - xi_max)
            end
            for i = 1:size(out, 1)
                out[i, j] = xs[i, j] - log(s) - xi_max
            end
        end
    end
    return out
end

∇logsoftmax(Δ, xs; dims=1) = Δ .- sum(Δ, dims=dims) .* softmax(xs, dims=dims)
∇logsoftmax!(Δ, xs) = ∇softmax!(Δ, Δ, xs)


"""
    assortativity(g)

Return the [assortativity coefficient](https://en.wikipedia.org/wiki/Assortativity)
of graph `g`, defined as the Pearson correlation of excess degree between
the end vertices of all the edges of the graph.

The excess degree is equal to the degree of linked vertices minus one,
i.e. discounting the edge that links the pair.
In directed graphs, the paired values are the out-degree of source vertices
and the in-degree of destination vertices.

# Examples
```jldoctest
julia> using LightGraphs

julia> assortativity(star_graph(4))
-1.0
```
"""
function assortativity(g::AbstractGraph{T}) where T
    P = promote_type(Int64, T) # at least Int64 to reduce risk of overflow
    nue  = ne(g)
    sjk = sj = sk = sjs = sks = zero(P)
    for d in edges(g)
        j = P(outdegree(g, src(d)) - 1)
        k = P(indegree(g, dst(d)) - 1)
        sjk += j*k
        sj  += j
        sk  += k
        sjs += j^2
        sks += k^2
    end
    return assortativity_coefficient(g, sjk, sj, sk, sjs, sks, nue)
end

#=
assortativity coefficient for directed graphs:
see equation (21) in M. E. J. Newman: Mixing patterns in networks, Phys. Rev. E 67, 026126 (2003),
http://arxiv.org/abs/cond-mat/0209450
=#
@traitfn function assortativity_coefficient(g::::IsDirected, sjk, sj, sk, sjs, sks, nue)
    return (sjk - sj*sk/nue) / sqrt((sjs - sj^2/nue)*(sks - sk^2/nue))
end

#=
assortativity coefficient for undirected graphs:
see equation (4) in M. E. J. Newman: Assortative mixing in networks, Phys. Rev. Lett. 89, 208701 (2002),
http://arxiv.org/abs/cond-mat/0205405/
=#
@traitfn function assortativity_coefficient(g::::(!IsDirected), sjk, sj, sk, sjs, sks, nue)
    return (sjk/nue - ((sj + sk)/(2*nue))^2) / ((sjs + sks)/(2*nue) - ((sj + sk)/(2*nue))^2)
end


using IntroLinearAlgebra
using Test

# write your own tests here
@test rowswitch([1 2 3 4; 5 6 7 8],1,2) == [5 6 7 8; 1 2 3 4]

@test rowscale([1 2 3; 4 5 6],2,-3//2) == [1 2 3; -6 -15//2 -9]

@test rowadd([0.3 0.6; π 2π],2,1,-π/0.3) == [0.3 0.6; 0.0 0.0]

@test rref([1 2 3 4 5; 6 7 8 9 10]) == [1 0 -1 -2 -3; 0 1 2 3 4]

@test length(rref(randn(8,8))) == 64

A = [1//1 2 3 4;
        5 6 7 8]

zerooutbelow!(A,1,1)

@test A == [1 2 3 4; 0 -4 -8 -12]


using Revise

include("Interface.jl")

module Attractors

using ..Interface: @ODE

export lorenz, TSUCS1, TSUCS2, yuwang, aizawa,
       rucklidge, genesio_tesi, finance, shimizu_morioka,
       noose_hoover, liu_chen, arneodo, bouali,
       burke_shaw, chen_celikovsky, chen_lee, dequan_li,
       hadley, halvorsen, newton_leipnik

lorenz = @ODE quote
    dx = σ * (y - x)
    dy = x * (ρ - z) - y
    dz = x * y - β * z

    σ = 10
    ρ = 28
    β = 8/3
end

TSUCS1 = @ODE quote
    dx = α * (y - x) + δ * x * z
    dy = ζ * y - x * z
    dz = β * z + x * y - ϵ * x * x

    α = 40
    β = 0.833
    δ = 0.5
    ϵ = 0.65
    ζ = 20
end

TSUCS2 = @ODE quote
    dx = α * (y - x) + δ * x * z
    dy = (ς * x) - (x * z) + ζ * y
    dz = (β * z) + (x * y) - (ϵ * x * x)

    α = 40
    δ = 0.16
    ς = 55
    ζ = 20
    β = 1.833
    ϵ = 0.65
end

yuwang = @ODE quote
    dx = α * (y - x)
    dy = β * x - σ * x * z
    dz = exp(x * y) - δ * z

    α = 10
    β = 40
    σ = 2
    δ = 2.5
end

aizawa = @ODE quote
    dx = (z - β) * x - δ * y
    dy = δ * x + (z - β) * y
    dz = σ + α * z - (z^3)/3 - (x^2 + y^2)*(1 + ϵ * z) + ζ * z * x^3

    α = 0.95
    β = 0.7
    σ = 0.6
    δ = 3.5
    ϵ = 0.25
    ζ = 0.1
end

lorenz_mod_2 = @ODE quote
    dx = -α*x + y^2 - z^2 + α*σ
    dy = x*(y-β*z) + δ
    dz = -z + x*(β * y + z)

    α = 0.9
    β = 5
    σ = 9.9
    δ = 1
end

thomas = @ODE quote
    dx = -β * x + sin(y)
    dy = -β * y + sin(z)
    dz = -β * z + sin(x)

    β = 0.19
end

rucklidge = @ODE quote
    dx = -κ*x + α*y - y* z
    dy = x
    dz = -z + y^2

    κ = 2
    α = 6.7
end

genesio_tesi = @ODE quote
    dx = y
    dy = z
    dz = -σ * x - β * y - α * z + x^2

    α = 0.44
    β = 1.1
    σ = 1.0
end

finance = @ODE quote
    dx = ((1/β) - α)*x + z + x*y
    dy = -β*y - x^2
    dz = -x - σ*z

    α = 0.001
    β = 0.2
    σ = 1.1
end

shimizu_morioka = @ODE quote
    dx = y
    dy = (1-z)*x - α*y
    dz = x^2 - β*z

    α = 0.75
    β = 0.45
end

noose_hoover = @ODE quote
    dx = y
    dy = -x + y*z
    dz = α - y^2

    α = 1.5
end

liu_chen = @ODE quote
    dx = α*y + β*x + σ*y*z
    dy = δ*y - z + ϵ*x*z
    dz = ζ*z + η*x*y

    α = 2.4
    β = -3.78
    σ = 14
    δ = -11
    ϵ = 4
    ζ = 5.58
    η = 1
end

arneodo = @ODE quote
    dx = y
    dy = z
    dz = -α*x - β*y - z + σ*x^3

    α = -5.5
    β = 3.5
    σ = -1.0
end

bouali = @ODE quote
    dx = x*(4-y) + α*z
    dy = -y*(1-x^2)
    dz = -x*(1.5 - σ*z) - 0.05*z

    α = 0.3
    σ = 1.0
end

burke_shaw = @ODE quote
    dx = -σ*(x+y)
    dy = -y -σ*x*z
    dz = σ*x*y + ν

    σ = 10
    ν = 4.272
end

chen_celikovsky = @ODE quote
    dx = α*(y-x)
    dy = -x*z + σ*y
    dz = x*y - β*z

    α = 36
    β = 3
    σ = 20
end

chen_lee = @ODE quote
    dx = α*x - y*z
    dy = β*y + x*z
    dz = σ*z + x*(y/3)

    α = 5
    β = -10
    σ = -0.38
end

dequan_li = TSUCS2

hadley = @ODE quote
    dx = -y^2 -z^2 -α*x + α*ζ
    dy = x*y - β*x*z - y + η
    dz = β*x*y + x*z - z

    α = 0.2
    β = 4
    ζ = 8
    η = 1
end

halvorsen = @ODE quote
    dx = -α*x -4*y -4*z - y^2
    dy = -α*y - 4*z - 4*x - z^2
    dz = -α*z - 4*x - 4*y - x^2

    α = 1.4
end

newton_leipnik = @ODE quote
    dx = -α*x + y + 10*y*z
    dy = -x - 0.4*y + 5*x*z
    dz = β*z -5*x*y

    α = 0.4
    β = 0.175
end

end


function grav_potential_cartesian(u)
    x,y,z = u
    return -1.0/sqrt(x^2 + y^2 + z^2)
end

function grav_potential_cylindrical(u)
    r,θ,z = u
    return -1.0/sqrt(r^2 + z^2)
end

function grav_potential_spherical(u)
    r,θ,ϕ = u
    return -1.0/r
end

function grav_field(potential::Function, u::Coordinate)
    return -grad(potential, u)
end

# Scalar Field Functions
function scalar_field(u::Coordinate)
    return u[3]*u[1]^2 + sin(u[1])*u[2]*u[3]^2
end

function grad_scalar_field_cartesian(u::Coordinate)
    x,y,z = u
    return [2*x*z + cos(x)*y*z^2, sin(x)*z^2, x^2 + 2*sin(x)*y*z]
end

function laplacian_scalar_field_cartesian(u::Coordinate)
    x,y,z = u
    return (2*z - sin(x)*y*z^2) + (0.0) + (2*sin(x)*y)
end

function grad_scalar_field_cylindrical(u::Coordinate)
    r,θ,z = u
    return [2*r*z + cos(r)*θ*z^2, (sin(r)*z^2)/r, r^2 + 2*sin(r)*θ*z]
end

function laplacian_scalar_field_cylindrical(u::Coordinate)
    r,θ,z = u
    return (4*z + (θ*z^2)*(cos(r)/r - sin(r))) + 2*sin(r)*θ
end

function grad_scalar_field_spherical(u::Coordinate)
    r,θ,ϕ = u
    return [2*r*ϕ + cos(r)*θ*ϕ^2, (sin(r)*ϕ^2)/(r*sin(ϕ)), (r^2 + 2*sin(r)*θ*ϕ)/r]
end

function laplacian_scalar_field_spherical(u::Coordinate)
    r,θ,ϕ = u
    return (6*ϕ + (θ*ϕ^2)*(2*cos(r)/r - sin(r))) + (cot(ϕ) + (2*θ*sin(r)/(r^2))*(cot(ϕ)*ϕ + 1))
end

#Vector Field Functions
function vector_field(u)
    V = [u[2]*u[1]^2, u[1]*u[2]^2, u[1]*u[2]*u[3]]
    return ContravariantVector(V, u, unit_basis=true)
end

function div_vector_field_cartesian(u)
    x,y,z = u
    return 5*x*y
end

function curl_vector_field_cartesian(u)
    x,y,z = u
    return [x*z, -y*z, y^2 - x^2]
end

function vector_laplacian_cartesian(u)
    x,y,z = u
    return [2*y , 2*x, 0.0]
end

function div_vector_field_cylindrical(u)
    r,θ,z = u
    return 4*θ*r + 2*θ
end

function curl_vector_field_cylindrical(u)
    r,θ,z = u
    return [z, -θ*z, 2*θ^2 - r]
end

function vector_laplacian_cylindrical(u)
    r,θ,z = u
    return [ θ*(3 - 4/r), 2*(1 + 1/r), θ*z/r]
end

function div_vector_field_spherical(u)
    r,θ,ϕ = u
    return 4*θ*r + (2*θ)/sin(ϕ) + θ*(1 + ϕ*cot(ϕ))
end

function curl_vector_field_spherical(u)
    r,θ,ϕ = u
    return [cot(ϕ)*θ^2 - ϕ/sin(ϕ), 2*θ*ϕ, (r/sin(ϕ)) - 2*θ^2]
end

function vector_laplacian_spherical(u)
    r,θ,ϕ = u
    return [4*θ - (2*θ/r)*(1 + 2/sin(ϕ) + ϕ*cot(ϕ)),
            (2*θ^2)/r + (2-θ^2)/(r*sin(ϕ)^2) + 2*(1+ ϕ*cot(ϕ)/r)/sin(ϕ),
            (θ*ϕ/r)*(2 - 1/sin(ϕ)^2) + (θ*cot(ϕ)/r)*(1 - 4/sin(ϕ))]
end

function Φ1(c)
    tan(prod(c))
end

function Φ2(c)
    c[1]*sin(c[2]/c[3])^2
end

function differential_identities(c)

    grad_fg1 = grad(x->Φ1(x)*Φ2(x), c)
    grad_fg2 = Φ1(c)*grad(Φ2,c) + grad(Φ1,c)*Φ2(c)
    @test grad_fg1 ≈ grad_fg2

    div_fA1 = div(x->Φ1(x)*vector_field(x), c)
    div_fA2 = Φ1(c)*div(vector_field,c) + dot(vector_field(c),grad(Φ1,c))
    @test div_fA1 ≈ div_fA2

    curl_fA1 = curl(x->Φ1(x)*vector_field(x), c)
    curl_fA2 = Φ1(c)*curl(vector_field,c) + cross(grad(Φ1,c),vector_field(c))
    @test curl_fA1 ≈ curl_fA2

end

function random_cartesian()
    CartesianCoordinate(3*randn(3))
end

function random_cylindrical()
    x = [0.0, 0.0, -3.0] + [3.0,2pi,6.0].*rand(3)
    CylindricalCoordinate(x)
end

function random_spherical()
    x = [0.0, 0.0, 0.0] + [3.0,2pi,pi].*rand(3)
    SphericalCoordinate(x)
end

const ubc = unit_basis_components

@testset "Differential Operations" begin
    x = CartesianCoordinate([2.0,-1.0,3.0])
    cyl = CylindricalCoordinate([sqrt(x[1]^2 + x[2]^2), atan(x[2],x[1]), x[3]])
    sph = SphericalCoordinate([norm(x), atan(x[2],x[1]), acos(x[3]/norm(x))])

    f = norm(grav_field(grav_potential_cartesian, x))
    @test f ≈ norm(grav_field(grav_potential_cylindrical, cyl))
    @test f ≈ norm(grav_field(grav_potential_spherical, sph))

    x = random_cartesian()
    cyl = random_cylindrical()
    sph = random_spherical()
    println("Testing Gradient...")
    @testset "Gradient" begin
        @test ubc(grad(scalar_field, x)) ≈ grad_scalar_field_cartesian(x)
        @test ubc(grad(scalar_field, cyl)) ≈ grad_scalar_field_cylindrical(cyl)
        @test ubc(grad(scalar_field, sph)) ≈ grad_scalar_field_spherical(sph)
    end

    println("Testing Laplacian...")
    @testset "Laplacian" begin
        @test laplacian(scalar_field, x) ≈ laplacian_scalar_field_cartesian(x)
        @test laplacian(scalar_field, cyl) ≈ laplacian_scalar_field_cylindrical(cyl)
        @test laplacian(scalar_field, sph) ≈ laplacian_scalar_field_spherical(sph)
    end

    println("Testing Divergence...")
    @testset "Divergence" begin
        @test div(vector_field, x) ≈ div_vector_field_cartesian(x)
        @test div(vector_field, cyl) ≈ div_vector_field_cylindrical(cyl)
        @test div(vector_field, sph) ≈ div_vector_field_spherical(sph)
    end

    println("Testing Curl...")
    @testset "Curl" begin
        @test ubc(curl(vector_field, x)) ≈ curl_vector_field_cartesian(x)
        @test ubc(curl(vector_field, cyl)) ≈ curl_vector_field_cylindrical(cyl)
        @test ubc(curl(vector_field, sph)) ≈ curl_vector_field_spherical(sph)
    end

    println("Testing Vector Laplacian...")
    @testset "Vector Laplacian" begin
        @test ubc(vector_laplacian(vector_field, x)) ≈ vector_laplacian_cartesian(x)
        @test ubc(vector_laplacian(vector_field, cyl)) ≈ vector_laplacian_cylindrical(cyl)
        @test ubc(vector_laplacian(vector_field, sph)) ≈ vector_laplacian_spherical(sph)
    end

    println("Testing Differential Identities...")
    @testset "Differential Identities" begin
        @testset "Cartesian" begin
            differential_identities(x)
        end
        @testset "Cylindrical" begin
            differential_identities(cyl)
        end
        @testset "Spherical" begin
            differential_identities(sph)
        end
    end
end


# double factorial
factorial2(n) = prod(n:-2:1)

dist2(dx,dy,dz) = dx*dx + dy*dy + dz*dz

triangle(i::Int64) = div(i*(i+1),2)
triangle(i::Int64,j::Int64) = i < j ? triangle(j-1)+i : triangle(i-1)+j

iindex(i::Int64,j::Int64,k::Int64,l::Int64) = triangle(triangle(i,j),triangle(k,l))

trace2(A,B) = sum(A.*B)


# Matern 3/2 isotropic covariance function

@doc """
# Description
Constructor for the isotropic Matern 3/2 kernel (covariance)

k(x,x') = σ²(1+√3*d/ℓ)exp(-√3*d/ℓ), where d = |x-x'|
# Arguments:
* `ll::Float64`: Log of the length scale ℓ
* `lσ::Float64`: Log of the signal standard deviation σ
""" ->
type Mat32Iso <: Isotropic
    ℓ::Float64       # Length scale
    σ2::Float64      # Signal std
    Mat32Iso(ll::Float64, lσ::Float64) = new(exp(ll),exp(2*lσ))
end

function set_params!(mat::Mat32Iso, hyp::Vector{Float64})
    length(hyp) == 2 || throw(ArgumentError("Matern 3/2 only has two parameters"))
    mat.ℓ, mat.σ2 = exp(hyp[1]), exp(2.0*hyp[2])
end

get_params(mat::Mat32Iso) = Float64[log(mat.ℓ), log(mat.σ2)/2.0]
get_param_names(mat::Mat32Iso) = [:ll, :lσ]
num_params(mat::Mat32Iso) = 2

metric(mat::Mat32Iso) = Euclidean()
cov(mat::Mat32Iso, r::Float64) = mat.σ2*(1+sqrt(3)*r/mat.ℓ)*exp(-sqrt(3)*r/mat.ℓ)

function grad_kern(mat::Mat32Iso, x::Vector{Float64}, y::Vector{Float64})
    r = distance(mat,x,y)
    exp_r = exp(-sqrt(3)*r/mat.ℓ)

    g1= mat.σ2*(sqrt(3)*r/mat.ℓ)^2*exp_r       #dK_d(log ℓ)
    g2 = 2.0*mat.σ2*(1+sqrt(3)*r/mat.ℓ)*exp_r  #dK_d(log σ)
    return [g1,g2]
end

function grad_stack!(stack::AbstractArray, mat::Mat32Iso, X::Matrix{Float64}, data::IsotropicData)
    nobsv = size(X,2)
    R = distance(mat, X, data)
    exp_R = exp(-sqrt(3)*R/mat.ℓ)

    for i in 1:nobsv, j in 1:i
        @inbounds stack[i,j,1] = mat.σ2*(sqrt(3)*R[i,j]/mat.ℓ)^2*exp_R[i,j]       # dK_dℓ
        @inbounds stack[j,i,1] = stack[i,j,1]
        @inbounds stack[i,j,2] = 2.0 *mat.σ2*(1+sqrt(3)*R[i,j]/mat.ℓ)*exp_R[i,j]  # dK_dσ
        @inbounds stack[j,i,2] = stack[i,j,2]
    end

    return stack
end


export Companion

struct Companion{T} <: AbstractArray{T, 2}
    c :: Vector{T}
end
#From polynomial

using Polynomials
#Generate companion matrix from a polynomial

function Companion(P::Poly{T}) where T
   n = length(P)
   c = Array{T}(undef, n-1)
   d=P.a[n]
   for i=1:n-1
       c[i]=P.a[i]/d
   end
   Companion(c)
end

#Basic property computations
size(C::Companion, r::Int) = (r==1 || r==2) ? length(C.c) :
    throw(ArgumentError("Companion is of rank 2"))

function size(C::Companion)
    n = length(C.c)
    n, n
end

#XXX Inefficient but works
# getindex(C::Companion, i, j) = getindex(Matrix(C), i, j)
# isassigned(C::Companion, i, j) = isassigned(Matrix(C), i, j)
getindex(C::Companion{T}, i::Int, j::Int) where T = (j==length(C.c)) ? -C.c[i] : (i==j+1 ? one(T) : zero(T) )
isassigned(C::Companion, i::Int, j::Int) = (j==length(C.c)) ? isassigned(C.c,i) : true

function Matrix(C::Companion{T}) where T
    M = zeros(T, size(C)...)
    M[:,end]=-C.c
    for i=1:size(C,1)-1
    	M[i+1, i] = one(T)
	end
    M
end

#Linear algebra stuff
function mul!(C::Companion{T}, b::Vector{T}) where T
	x = b[end]
	y = -C.c[1]*x
	b[2:end] = b[1:end-1]-C.c[2:end]*x
	b[1] = y
    b
end
*(C::Companion{T}, b::Vector{T}) where T = mul!(C, copy(b))

function mul!(A::Matrix{T}, C::Companion{T}) where T
	v = Array{T}(undef, size(A,1))
	for i=1:size(A,1)
		v[i] =dot(vec(A[i,:]),-C.c)
	end
	for i=1:size(A,1), j=1:size(A,2)-1
		A[i,j] = A[i,j+1]
	end
	A[:,end] = v
	A
end
*(A::Matrix{T}, C::Companion{T}) where T = mul!(copy(A), C)

function inv(C::Companion{T}) where T
	M = zeros(T, size(C)...)
    for i=1:size(C,1)-1
    	M[i, i+1] = one(T)
	end
	d = M[end, 1] = -one(T)/C.c[1]
	M[1:end-1, 1] = d*C.c[2:end]
    M
end


using StanModels, Distributions

# Define the Stan language model

m8_5s = "
data{
  int N;
  vector[N] y;
}
parameters{
  real sigma;
  real a1;
  real a2;
}
model{
  real mu;
  a1 ~ normal(0, 10);
  a2 ~ normal(0, 10);
  mu = a1 + a2;
  y ~ normal( mu , sigma );
}
";

# Define the Stanmodel and set the output format to :mcmcchains.

sm = SampleModel("m8.5s", m8_5s);

# Input data for cmdstan

m8_5_data = Dict("N" => 100, "y" => rand(Normal(0, 1), 100));

# Sample using cmdstan

(sample_file, log_file) = stan_sample(sm, data=m8_5_data);

rethinking = "
       mean   sd   5.5% 94.5% n_eff Rhat
a1    -0.08 7.15 -11.34 11.25  1680    1
a2    -0.05 7.15 -11.37 11.19  1682    1
sigma  0.90 0.07   0.81  1.02  2186    1
";

# Describe the draws
if !(sample_file == nothing)
  chn = read_samples(sm)
  describe(chn)
end


# Machinery for complex plane mappings

"zr = rotate_and_scale(za, zb, z)"
function rotate_and_scale(za, zb, z)
    # computes
    # zr = M(z)
    # where
    # M(za) = -1
    # M(zb) = 1
    c_map = ((za+zb)/(za-zb), 2/(zb-za))
    zr = c_map[1] + z*c_map[2]
    return zr
end

function z_edges(grid, i)
    za = grid.edges[1,i] + 1im*grid.edges[2,i]
    zb = grid.edges[3,i] + 1im*grid.edges[4,i]
    return za, zb
end

function map_panels(grid::DiscreteCurve)
    L = legendre_matrix(grid.panelorder)
    ffit(z) = L*z
    coeffs = Array{Complex{Float64}}(grid.panelorder, grid.numpanels)
    for i=1:grid.numpanels
        za, zb = z_edges(grid, i)
        idx = (1:grid.panelorder) + grid.panelorder*(i-1)
        zpanel = grid.points[1,idx] + 1im*grid.points[2,idx]
        # First rescale [za,zb] to [-1,1], which helps Newton search later on
        zscaled = rotate_and_scale(za, zb, zpanel)
        c = ffit(zscaled)
        coeffs[:,i] = c
    end
    return coeffs
end

function invert_map(grid, coeffs, panel_idx, z; fall_back_to_initial::Bool=true)
    # Step 1: Rotate and scale
    za, zb = z_edges(grid, panel_idx)
    zr = rotate_and_scale(za, zb, z)
    # Step 2: Get the right expansion and solve with Newton
    c = coeffs[:, panel_idx]
    c = c[1 : min(length(c), 16)]     # Limit to 16 modes
    t0 = zr
    maxiter = 30
    converged = true
    t, relres, iter = newton_legendre(c, zr, t0, maxiter, 1e-13) # Try to hit 1e-13
    if iter == maxiter && relres > 1e-10 # ...but consider things converged if only 1e-10
        info("Newton did not converge, relres=", @sprintf("%.2e", relres), ". t=$t, t0=$t0")
        converged = false
        if fall_back_to_initial
            t = t0 # Fall back to initial guess
        end
    end
    tlocal = t
    # Step 3: Rescale t back to global parametrization
    ta = grid.t_edges[1, panel_idx]
    tb = grid.t_edges[2, panel_idx]
    tglobal = ta + (t+1)/2*(tb-ta)
    return tlocal, tglobal, converged
end


# A palindromic number reads the same both ways. The largest palindrome made
# from the product of two 2-digit numbers is 9009 = 91 * 99.

# Find the largest palindrome made from the product of two 3-digit numbers.

function largest_palindrome(digits)
    limit = 10^digits-1
    largest = 0
    for i in limit:-1:1
        for j in i:-1:1
            num = i*j
            str = string(num, pad=digits*2)
            if (num > 10^(digits*2-1)) & (num > largest) & (reverse(str[1:digits]) == str[digits+1:end])
                largest = num
            end
        end
    end
    #return sort(list)[end]
    return largest
end

largest_palindrome(2)

largest_palindrome(3)


export
    laguerre_horner,
    laguerre

function laguerre_horner(n::Integer, α::Integer, x::Real)
    # by Horner's method
    laguerre_l = 1
    bin = 1
    for i in n:-1:1
        bin *= (α + i) / (n + 1 - i)
        laguerre_l = bin - x * laguerre_l / i
    end

    return laguerre_l
end

laguerre_horner(n::Integer, α::Integer) = x->laguerre_horner(n, α, x)

function laguerre(n::Integer, α::Integer, x::T) where {T<:Real}
    if n == 0
        return one(T)
    elseif n == 1
        return one(T) + α - x
    else
        L_prev = one(T)
        L_k = one(T) + α - x
        for k = 1:(n-1)
            L_next = ((2k + one(T) + α - x) * L_k - (k+α) * L_prev) / (k+one(T))
            L_prev, L_k = L_k, L_next
        end
        return L_k
    end
end

laguerre(n::Integer, α::Integer) = x->laguerre(n, α, x)

function laguerre(m::Integer, n::Integer, x::Real, p::Real)
    if n ≥ m
        return laguerre(m, n - m, abs2(z(x, p)))
    else
        return laguerre(n, m - n, abs2(z(x, p)))
    end
end

function laguerre(m::Vector{<:Integer}, n::Vector{<:Integer}, x::Vector{<:Real}, p::Vector{<:Real})
    x = reshape(x, 1, 1, length(x))
    p = reshape(p, 1, 1, 1, length(p))
    if n ≥ m
        return laguerre.(m, n' .- m, x, p)
    else
        return laguerre.(n', m .- n', x, p)
    end
end

function laguerre(m::Vector{<:Integer}, n::Vector{<:Integer})
    laguerre_xp(x::Vector{<:Real}, p::Vector{<:Real}) = laguerre(m, n, x, p)
    return laguerre_xp
end

function laguerre(x::Vector{<:Real}, p::Vector{<:Real})
    x = reshape(x, 1, 1, length(x))
    p = reshape(p, 1, 1, 1, length(p))
    function laguerre_mn(m::Vector{<:Integer}, n::Vector{<:Integer})
        if n ≥ m
            return laguerre.(m, n' .- m, x, p)
        else
            return laguerre.(n', m .- n', x, p)
        end
    end
    return laguerre_mn
end


### A Pluto.jl notebook ###
# v0.12.4

using Markdown
using InteractiveUtils

# ╔═╡ 7022d960-2901-11eb-2528-87791813b415
using OffsetArrays

# ╔═╡ afa4a77e-28fc-11eb-1ab0-bbba1b653e46
data = reshape(rand(1:9,36),6,6)

# ╔═╡ 0019e726-28fd-11eb-0e86-31ec28b3c1a9
I = CartesianIndices(data)

# ╔═╡ 15548ecc-28fd-11eb-1206-476c5f44efdf
[ data[i] for i∈I]

# ╔═╡ 5fb6e7b6-2901-11eb-0e94-aba290fd0bae
A = OffsetArray(zeros(Int,8,8), 0:7 ,0:7)

# ╔═╡ 87c260a2-2901-11eb-1060-b1e4b6b5b02b
for i ∈ I
	A[i] = data[i]  # copy data
end

# ╔═╡ a5c7693a-2901-11eb-1083-0da8138a73c2
A

# ╔═╡ b6fde83c-2901-11eb-0e3b-4b3766579cc8
neighborhood = CartesianIndices((-1:1, -1:1))

# ╔═╡ babe3c24-2901-11eb-2d30-51256eb97e11
[ A[i.+neighborhood] for i ∈ I]

# ╔═╡ e6bd9dea-2901-11eb-1100-ad10705f41cc
stencil =  [ 0  -1   0
            -1   4  -1
            0  -1   0]

# ╔═╡ fe4f6df0-2901-11eb-1945-27e3f041ed1f
[  sum(A[i.+neighborhood].*stencil) for i ∈ I]

# ╔═╡ 77c06ce6-2902-11eb-30a7-51f210dbd723
begin
 B = copy(A)

 B[0,:] = B[6,:]  ## periodic
 B[7,:] = B[1,:]
 B[:,0] = B[:,6]
 B[:,7] = B[:,1]

 #B[0,:] = B[1,:]  ## zero derivative
 #B[7,:] = B[6,:]
 #B[:,0] = B[:,1]
 #B[:,7] = B[:,6]

 B
end

# ╔═╡ 4f342744-2902-11eb-1401-55e770d9d751

for i∈I
	B[i] = sum(A[i.+neighborhood].*stencil)
end

# ╔═╡ 6223e374-2902-11eb-3bb2-4d2d0d352801
B

# ╔═╡ Cell order:
# ╠═afa4a77e-28fc-11eb-1ab0-bbba1b653e46
# ╠═0019e726-28fd-11eb-0e86-31ec28b3c1a9
# ╠═15548ecc-28fd-11eb-1206-476c5f44efdf
# ╠═7022d960-2901-11eb-2528-87791813b415
# ╠═5fb6e7b6-2901-11eb-0e94-aba290fd0bae
# ╠═87c260a2-2901-11eb-1060-b1e4b6b5b02b
# ╠═a5c7693a-2901-11eb-1083-0da8138a73c2
# ╠═b6fde83c-2901-11eb-0e3b-4b3766579cc8
# ╠═babe3c24-2901-11eb-2d30-51256eb97e11
# ╠═e6bd9dea-2901-11eb-1100-ad10705f41cc
# ╠═fe4f6df0-2901-11eb-1945-27e3f041ed1f
# ╠═77c06ce6-2902-11eb-30a7-51f210dbd723
# ╠═4f342744-2902-11eb-1401-55e770d9d751
# ╠═6223e374-2902-11eb-3bb2-4d2d0d352801


"""
    heun(f,a,b,y0,M)

berisi program untuk mencari solusi persamaan differensial `y'=f(t,y)`
dengan masalah nilai awal `y(a) = y0` pada interval `[a, b]`. Program ini secara default
berisi 5 masukan, yaitu fungsi `f(t,y)`, titik ujung interval penyelesaian `[a,b]`, nilai awal
`y0`, dan jumlah sub-interval `M`.

# Examples
```jldoctest
julia> heun((t,y)->(t-y)/2,0,3,1,6)
7×2 Array{Float64,2}:
 0.0  1.0
 0.5  0.84375
 1.0  0.831055
 1.5  0.930511
 2.0  1.11759
 2.5  1.37311
 3.0  1.68212
```
return solusi masalah nilai awal `sol`.
"""
function heun(f,a,b,y0,M)
    M = Int(M)
    h = (b-a)/M;
    T = a:h:b;
    Y = Array{Float64}(undef,length(T),1)
    P = Array{Float64}(undef,length(T),1)
    Y[1] = y0;
    # Mulai langkah iterasi Heun
    for k = 1:M
        #% Rumus iterasi Heun
        P[k+1]=Y[k]+h*f(T[k],Y[k]);
        Y[k+1]=Y[k]+h/2*(f(T[k],Y[k])+f(T[k+1],P[k+1]));
    end
    sol = [T Y];
end


# Source: Lanshammar, H. (1982).
# On precision limits for derivatives numerically calculated from noisy data
# Journal of Biomechanics 15(6), 459-470

"""Define parameters for Lanshammar's polynomial filter"""
function lpparams(n, p; window=epanwin, dt::Real=1.)
    U = zeros(2n+1,p+1)
    for k=0:p
      U[:,k+1] = (-n:n).^k
    end
    w = window(2*n+1)
    W = diagm(w./maximum(w))
    V = U'*W*U
    f = map(factorial, 0:p) ./ (-dt).^(0:p)
    Dict(:h => diagm(f)*(V\eye(p+1))*U'*W, :p=>p)
end

"""Apply Lanshammar polynomial filter"""
function lpfit(x::Matrix; h::VecOrMat=[1], p::Integer=0);
y = zeros(size(x)..., p+1)
for k=1:p+1
  y[:,:,k] = filt(collect(h[k,:]),1,x)
end
y
end

function lpfit(x::Vector; h::VecOrMat=[1], p::Integer=0);
y = zeros(length(x), p+1)
for k=1:p+1
  y[:,k] = filt(collect(h[k,:]),1,x)
end
y
end

"""Table of optimal bandwidths"""
optband =
    [NaN  .275  NaN; # p=1
     .53  .275  .33; # p=2
     .53  .600  .33; # p=3
     .82  .600  .70; # p=4
     .82  .920  .70; # p=5
    1.15  .920 1.02; # p=6
    1.15 1.250 1.02; # p=7
    1.48 1.250 1.35; # p=8
    1.48   NaN 1.35] # p=9

nmin =
    [0 1 0; # p=1
     1 1 1; # p=2
     1 2 1; # p=3
     2 2 2; # p=4
     2 3 2; # p=5
     3 3 3; # p=6
     3 4 3; # p=7
     4 4 4; # p=8
     4 0 4] # p=9

function optbandn(p,k,w)
    !(1 <= p <= size(optband)[1]) &&
        error("polynomial order must be between 1 and $(size(optband)[1])")
    !(0 <= k <= size(optband)[2]) &&
        error("the target order of the derivative must be between 0 and $(size(optband)[2])")
    isnan(optband[p,k+1]) && error("value undefined for p==$p, k==$k")
    max(ceil(Integer, optband[p,k+1]/w), nmin[p,k+1])
end


# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

function _eqpole(l::T, b::T, southpole::Bool) where {T<:AbstractFloat}
    sgn = southpole ? -1 : 1
    l = deg2rad(sgn*l)
    b = deg2rad(sgn*b)
    r = 18 * sqrt(2 * (1 - sin(b))) * 3.53553391
    return r .* sincos(l)
end

"""
    eqpole(l, b[; southpole = false]) -> x, y

### Purpose ###

Convert right ascension \$l\$ and declination \$b\$ to coordinate \$(x, y)\$
using an equal-area polar projection.

### Explanation ###

The output \$x\$ and \$y\$ coordinates are scaled to be in the range \$[-90, 90]\$
and to go from equator to pole to equator.  Output map points can be
centered on the north pole or south pole.

### Arguments ###

* `l`: longitude, scalar or vector, in degrees
* `b`: latitude, same number of elements as right ascension, in degrees
* `southpole` (optional boolean keyword): keyword to indicate that the plot is
  to be centered on the south pole instead of the north pole.  Default is
  `false`.

### Output ###

The 2-tuple \$(x, y)\$:

* ``x`` coordinate, same number of elements as right ascension, normalized to be
  in the range ``[-90, 90]``.
* ``y`` coordinate, same number of elements as declination, normalized to be
  in the range ``[-90, 90]``.

### Example ###

```jldoctest
julia> using AstroLib

julia> eqpole(100, 35, southpole=true)
(-111.18287262822456, -19.604540237028665)

julia> eqpole(80, 19)
(72.78853915267848, 12.83458333897169)
```

### Notes ###

Code of this function is based on IDL Astronomy User's Library.
"""
eqpole(l::Real, b::Real; southpole::Bool=false) =
    _eqpole(promote(float(l), float(b))..., southpole)

function eqpole(l::AbstractArray{L}, b::AbstractArray{<:Real};
                southpole::Bool=false) where {L<:Real}
    @assert length(l) == length(b)
    typel = float(L)
    x = similar(l, typel)
    y = similar(l, typel)
    for i in eachindex(l)
        x[i], y[i] = eqpole(l[i], b[i], southpole=southpole)
    end
    return x, y
end


using Unitful
t = 1.0u"s"

t2 = 1.02u"s"
t+t2

t*t2

sqrt(t)

t + sqrt(t)

using DifferentialEquations
f = (y,p,t) -> 0.5*y
u0 = 1.5u"N"
prob = ODEProblem(f,u0,(0.0u"s",1.0u"s"))
sol = solve(prob,Tsit5())

f = (y,p,t) -> 0.5*y/3.0u"s"
prob = ODEProblem(f,u0,(0.0u"s",1.0u"s"))
sol = solve(prob,Tsit5())

print(sol[:])

using Plots
gr()
plot(ustrip(sol.t),ustrip(sol[:]),lw=3)

using SciMLTutorials
SciMLTutorials.tutorial_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file])


# Robertson chemical reaction:
#
# Adapted from
# http://www.unige.ch/~hairer/testset/testset.html
# reference solution from
# http://www.dm.uniba.it/~testset/problems/rober.php
#
# See also:
# http://octave.sourceforge.net/odepkg/function/odepkg_testsuite_robertson.html
#
# Note that this runs over large tspan = [0,1e11] with the first step being < 1!

export rober

rober = let
    tcname = :rober
    T = Float64 # the datatype used
    Tarr = Matrix
    dof = 3 # degrees of freedom
    dae=0
    # stiffness of system, one of the three constants
    stiffness = [nonstiff, mildlystiff, stiff][3]
    function fn!(t,y,dydt)
        # the ode function
        dydt[1] = -0.04*y[1] + 1.0e4*y[2]*y[3]
        dydt[3] = 3.0e7 *y[2]*y[2]
        dydt[2] = -dydt[1]-dydt[3]
        nothing
    end
    # initializes storage for y:
    fn!( ; T_::Type=T, dof_=dof) = zeros(T_,dof_)

    function jac!(t,y,dfdy)
        # the Jacobian of f
        prod1 = 1.0e4*y[2]
        prod2 = 1.0e4*y[3]
        prod3 = 6.0e7*y[2]
        dfdy[1,1] = -0.04
        dfdy[1,2] = prod2
        dfdy[1,3] = prod1
        dfdy[2,1] = 0.04
        dfdy[2,2] = -prod2-prod3
        dfdy[2,3] = -prod1
        dfdy[3,1] = 0.0
        dfdy[3,2] = prod3
        dfdy[3,3] = 0.0
        return nothing
    end
    jac!( ; T_::Type=T, dof_=dof) = zeros(T_,dof_,dof_)  # if the problem is large better return an appropriate sparse matrix

    # mass matrix M(t,y) dydt = f(t,y)
    mass! = nothing

    ic = T[1,0,0] # vector of initial conditions
    tspan = T[0, 1e11] # integration interval
    refsol = T[0.2083340149701255e-07,
               0.8333360770334713e-13,
               0.9999999791665050] # reference solution at tspan[2]
    refsolinds = trues(dof)
    scd_absinds = falses(dof)

    tc = TestCaseExplicit{tcname, T, Tarr}(
                             stiffness,
                             dae,
                             dof,
                             fn!,
                             jac!,
                             mass!,
                             ic,
                             tspan,
                             refsol,
                             refsolinds,
                             scd_absinds)

    tc_all[tcname] = tc
    tc_stiff[tcname] = tc
end


"""
    GeneralizedGaussian( μ, α, β )

The "Generalized Normal" or "Generalized Gaussian" with shape parameter 'β',
scale 'α', and location 'μ' has probability density function

```math
f(x; \\mu \\alpha \\beta ) = \\frac{\\beta}{2\\alpha\\Gamma(1/\\beta)}
\\text{exp}\\left( -|x - \\mu|/\\alpha \\right),

\\alpha = \\sigma \\sqrt{ \\frac{\\Gamma(1/\\beta)}{\\Gamma(3/\\beta)} }

```
where `β = 1` incorporates the Laplacian distribution, `β = 2` is the Normal distribution,
and as `β → ∞`, the distribution approaches Uniform on `[μ-α, μ+α]`.

```julia
GeneralizedGaussian(m, a, b)    # Generalized Gaussian with mu, alpha, and beta.
params(d)                       # Get the parameters (m,a,b)
shape(d)                        # returns β shape
scale(d)                        # returns the scale parameter, α
```

External Links
* [Generalized Normal Distribution on Wikipedia](https://en.wikipedia.org/wiki/Generalized_normal_distribution)
* [Reference implementation paper](https://www.researchgate.net/publication/254282790_Simulation_of_the_p-generalized_Gaussian_distribution)

References
[1] Nadarajah, Saralees (September 2005). "A generalized normal distribution".
    Journal of Applied Statistics. 32 (7): 685&ndash, 694.
[2]  Gonzalez-Farias, G., Molina, J. A. D., & Rodríguez-Dagnino, R. M. (2009).
    Efficiency of the approximated shape parameter estimator in the generalized
    Gaussian distribution. IEEE Transactions on Vehicular Technology, 58(8),
    4214-4223.
"""
struct GeneralizedGaussian{T<:Real}
    μ::T
    α::T
    β::T
    function GeneralizedGaussian{T}(μ::T, α::T, β::T) where T
        α > zero(α) || error("standard deviation must be positive.")
        β > zero(β) || error("β must be greater than zero.")
        new{T}(μ, α, β)
    end
end

GeneralizedGaussian(μ::T, α::T, β::T) where {T<:Real} = GeneralizedGaussian{T}(μ, α, β)
GeneralizedGaussian(μ::Real, α::Real, β::Real) = GeneralizedGaussian(promote(μ, α, β)...)
GeneralizedGaussian(μ::Integer, σ::Integer, β::Integer) = GeneralizedGaussian(Float64(μ), Float64(σ), Float64(β))

"""
    GeneralizedGaussian(β)

Builds a default distribution with shape `β` and `μ=0, α=1`.
"""
GeneralizedGaussian(β::Real) = GeneralizedGaussian(zero(β), one(β), β)

"""
    GeneralizedGaussian()

Builds the Normal distribution case, where `μ=0, α=√2, β=2`, or N(0, 1).
"""
GeneralizedGaussian() = GeneralizedGaussian(0.0, √2, 2)

### Conversion
function convert(::Type{GeneralizedGaussian{T}}, μ::Real, σ::Real, β::Real) where T <: Real
    GeneralizedGaussian(T(μ), T(α), T(β))
end
function convert(::Type{GeneralizedGaussian{T}}, d::GeneralizedGaussian{S}) where {T <: Real, S <: Real}
    GeneralizedGaussian(T(d.μ), T(d.α), T(d.β))
end

### Parameters
params(d::GeneralizedGaussian) = (d.μ, d.α, d.β)
location(d::GeneralizedGaussian) = d.μ
scale(d::GeneralizedGaussian) = d.α
shape(d::GeneralizedGaussian) = d.β

var(d::GeneralizedGaussian) = (d.α^2) * (gamma(3.0 * inv(d.β)) / gamma(inv(d.β)))
std(d::GeneralizedGaussian) = (d.α) * sqrt(gamma(3.0 * inv(d.β)) / gamma(inv(d.β)))

mean(d::GeneralizedGaussian) = d.μ
median(d::GeneralizedGaussian) = d.μ
mode(d::GeneralizedGaussian) = d.μ

### Statistics
skewness(d::GeneralizedGaussian{T}) where T = zero(T)
kurtosis(d::GeneralizedGaussian) = gamma(5.0 * inv(d.β)) * gamma(inv(d.β)) / (gamma(3.0 * inv(d.β))^2) - 3.0
entropy(d::GeneralizedGaussian) = inv(d.β) - log( d.β / (2.0 * d.α * gamma(inv(d.β))))

### Evaluation
function pdf(d::GeneralizedGaussian{T}, x::Real) where T<:Real
    if x == -Inf || x == Inf
        return zero(T)
    else
        A = d.β / (2.0 * d.α * gamma(1. / d.β) )
        return A * exp( -(abs(x - d.μ) / d.α)^d.β )
    end
end
logpdf(d::GeneralizedGaussian, x::Real) = log(pdf(d, x))

"""
    cdf(d, x)
Calculates the CDF of the distribution. To determine the CDF, the incomplete
gamma function is required. The CDF  of the Gamma distribution provides this,
with the necessary 1/Γ(a) normalization.
"""
function cdf(d::GeneralizedGaussian{T}, x::Real) where T<:Real
    if x == -Inf || x == Inf
        return zero(T)
    else
        v = cdf(Gamma(inv(d.β), 1), (abs(x - μ) / d.α)^d.β) * inv(2)
        return typeof(v)(1/2) + sign(x - μ) * v
    end
end

"""
    rand(rng, d)
Extract a sample from the Generalized Gaussian distribution `d`. The sampling
procedure is implemented from from [2].
"""
function rand(rng::AbstractRNG, d::GeneralizedGaussian)
    # utilizing the sampler from the Gamma distribution.
    g = Gamma(inv(d.β), 1)
    # random variable with value -1 or 1 with probability (1/2).
    b = 2.0 * rand(Bernoulli()) - 1
    return d.μ + inv(sqrt(d.α)) * rand(rng, g)^inv(d.β) * b
end

# multi-sample case
function rand(rng::AbstractRNG, d::GeneralizedGaussian, dims::Dims)
    out = Array{eltype(params(d))}(undef, dims)
    @inbounds for i in eachindex(out)
        out[i] = rand(rng, d)
    end
    out
end

# various function signatures for sampling the distribution.
rand(d::GeneralizedGaussian) = rand(GLOBAL_RNG, d)
rand(d::GeneralizedGaussian, dims::Dims) = rand(GLOBAL_RNG, d, dims)
rand(d::GeneralizedGaussian, dims::Int) = rand(GLOBAL_RNG, d, (dims))
rand(rng::AbstractRNG, d::GeneralizedGaussian, dim::Int, dims::Int...) = rand(rng, d, (dim, dims...))
rand(d::GeneralizedGaussian, dims::Int...) = rand(GLOBAL_RNG, d, dims...)

function Zn(X::AbstractArray, β::Real)
    n = size(X,1)
    S1 = 1/n * sum(abs.(X).^(2β))
    S2 = 1/n * sum(abs.(X).^β)
    return (S1/(S2^2)) - (β + 1)
end

function Zn′(X::AbstractArray, β::Real)
    n = size(X,1)
    S1 = sum(abs.(X).^β) # sum
    S2 = sum(abs.(X).^(2β))
    L1 = sum(abs.(X).^β .* log.(abs.(X)))    # log
    L2 = sum(abs.(X).^(2β) .* log.(abs.(X)))
    num1 = (2/n * L2) * (1/n * S1)^2
    denom = (1/n * S1)^4
    num2 = (1/n * L1) * (1/n * S2) * (2/n * S1)
    return (num1/denom) - (num2/denom) - 1
end

"""
    β = gcmsearch(X::AbstractArray, βi::Real)

Globally convergent method for β-parameter estimation using Newton-Raphson
iterative search from [1].

[1] Song, Kai-Sheng. "A globally convergent and consistent method
    for estimating the shape parameter of a generalized Gaussian distribution."
    IEEE Transactions on Information Theory 52.2 (2006): 510-527.
"""
function gcmsearch(X::AbstractArray{T}, βi::Real) where T<:AbstractFloat
    ϵ = √eps(T)
    N_MAX = 1_000
    βp = copy(βi)
    num, denom = Zn(X, βp), Zn′(X, βp)
    βn = βp - (num/denom)
    βp = copy(βn)
    for _ = 2:N_MAX
        num, denom = Zn(X, βp), Zn′(X, βp)
        βn = βp - (num/denom)
        if (abs(βn - βp) < ϵ) # check if converged?
            break
        else
            βp = copy(βn) # prep for next iteration.
        end
    end
    βn
end


## Julia program for Calculation of Gamma function
## author: weiya <szcfweiya@gmail.com>
## date: 2018-08-21
##
## refer to "Coefficients for the Lanczos Approximation to the Gamma Function"
##   https://mrob.com/pub/ries/lanczos-gamma.html

const LG_g = 5.0;
const LG_N = 6;
const lct = [
    1.000000000190015,
    76.18009172947146,
    -86.50532032941677,
    24.01409824083091,
    -1.231739572450155,
    0.1208650973866179e-2,
    -0.5395239384953e-5];
const ln_sqrt_2_pi = 0.91893853320467274178;
const g_pi = 3.14159265358979323846;

function lanczos_ln_gamma(z)
    if z < 0.5
        #Use Euler's reflection formula:
        #Gamma(z) = Pi / [Sin[Pi*z] * Gamma[1-z]];
        return log(g_pi / sin(g_pi * z)) - lanczos_ln_gamma(1.0 - z);
    end
    z = z - 1.0;
    base = z + LG_g + 0.5;  # Base of the Lanczos exponential
    sum = 0;
    ## We start with the terms that have the smallest coefficients and largest denominator.
    for i = 2:LG_N+1
      sum += lct[i] / (z + i - 1);
    end
    sum += lct[1];
    ## This printf is just for debugging
#    @printf("ls2p %7g  l(b^e) %7g   -b %7g  l(s) %7g\n", ln_sqrt_2_pi,
#              log(base)*(z+0.5), -base, log(sum));
    ## Gamma[z] = Sqrt(2*Pi) * sum * base^[z + 0.5] / E^base
    return ((ln_sqrt_2_pi + log(sum)) - base) + log(base)*(z+0.5);
end

function lanczos_gamma(z)
    return(exp(lanczos_ln_gamma(z)));
end

## example
lanczos_gamma(1)


_rescale(x, m, M) = (x .- minimum(x))./(maximum(x)-minimum(x)).*(M-m).+m

"""
    rescale!(layer::TI, template::TJ) where {TI <: SimpleSDMLayer, TJ <: SimpleSDMLayer}

Changes the values of the layer given as its first argument, so that it has the
same *range* as the values of the layer given as its second argument.
Modification is done in-place.
"""
function rescale!(layer::TI, template::TJ) where {TI <: SimpleSDMLayer, TJ <: SimpleSDMLayer}
    return rescale!(layer, extrema(template))
end

"""
    rescale!(layer::TI, t::Tuple{T,T}) where {TI <: SimpleSDMLayer, T <: Number}

Changes the values of the layer given as its first argument, so that it has the
same *range* as the values given as a tuple of values. Modification is done
in-place.
"""
function rescale!(layer::TI, t::Tuple{T,T}) where {TI <: SimpleSDMLayer, T <: Number}
    occ = findall(!isnothing, layer.grid)
    layer.grid[occ] .= _rescale(layer.grid[occ], t...)
end

"""
    rescale(layer::TI, template::TJ) where {TI <: SimpleSDMLayer, TJ <: SimpleSDMLayer}

Copying version of `rescale!`.
"""
function rescale(layer::TI, template::TJ) where {TI <: SimpleSDMLayer, TJ <: SimpleSDMLayer}
    l = copy(layer)
    return rescale!(l, extrema(template))
end

"""
    rescale(layer::TI, t::Tuple{T,T}) where {TI <: SimpleSDMLayer, T <: Number}

Copying version of `rescale!`.
"""
function rescale(layer::TI, t::Tuple{T,T}) where {TI <: SimpleSDMLayer, T <: Number}
    l = copy(layer)
    return rescale!(l, t)
end

"""
    rescale!(layer::T, p::Vector{Real}) where {T <: SimpleSDMLayer}

Rescale the values of a `layer` so that they match with the quantiles given in
`p`. Internally, this uses the `Statistics.quantile` function.
"""
function rescale!(layer::T, p::Vector{TI}) where {T <: SimpleSDMLayer, TI <: AbstractFloat}
    q = reverse!(quantile(layer, p))
    occupied = findall(!isnothing, layer.grid)
    v = collect(layer)
    for i in 1:length(q)
        layer.grid[occupied[findall(x -> x <= q[i], v)]] .= reverse(p)[i]
    end
    return layer
end

"""
    rescale(layer::T, p::Vector{Real}) where {T <: SimpleSDMLayer}

Copying version of `rescale!`.
"""
function rescale(layer::T, p::Vector{TI}) where {T <: SimpleSDMLayer, TI <: AbstractFloat}
    l = copy(layer)
    return rescale!(l, p)
end


using NiLang, NiLang.AD
using LinearAlgebra

function _iqr(A)
    @assert size(A, 1) == size(A, 2)
    N = size(A, 1)
    Q = zero(A)
    R = zero(A)
    for col = 1:N
        ri = A[:,col]
        for precol = 1:col-1
            ni = Q[:,precol]'*ri
            R[precol,col] = ni
            ri = ri - ni*Q[:,precol]
        end
        R[col, col] = norm(ri)
        Q[:,col] = ri/R[col,col]
    end
    return Q, R
end

@i function iqr(Q, R, A::AbstractMatrix{T}) where T
    @anc anc_norm = zero(T)
    @anc anc_dot = zeros(T, size(A,2))
    @anc ri = zeros(T, size(A,1))
    for col = 1:size(A, 1)
        ri .+= identity.(A[:,col])
        for precol = 1:col-1
            idot(anc_dot[precol], Q[:,precol], ri)
            R[precol,col] += identity(anc_dot[precol])
            for row = 1:size(Q,1)
                ri[row] -= anc_dot[precol] * Q[row, precol]
            end
        end
        inorm2(anc_norm, ri)

        R[col, col] += anc_norm^0.5
        for row = 1:size(Q,1)
            Q[row,col] += ri[row] / R[col, col]
        end

        ~begin
            ri .+= identity.(A[:,col])
            for precol = 1:col-1
                idot(anc_dot[precol], Q[:,precol], ri)
                for row = 1:size(Q,1)
                    ri[row] -= anc_dot[precol] * Q[row, precol]
                end
            end
            inorm2(anc_norm, ri)
        end
    end
end

using Test, Random
@testset "test qr" begin
    Random.seed!(2)
    A = randn(4,4)
    Q, R = _iqr(A)
    @test Q * R ≈ A
    @test Q'*Q ≈ I
    R[abs.(R).<1e-10] .= 0.0
    @test istriu(R)

    q = zero(A)
    r = zero(A)
    @instr iqr(q, r, A)
    @test q ≈ Q
    @test r ≈ R
    @test check_inv(iqr, (q, r, A))

    @i function test1(out, q, r, A)
        iqr(q, r, A)
        out += identity(q[1,2])
    end
    @i function test2(out, q, r, A)
        iqr(q, r, A)
        out += identity(r[1,2])
    end
    @test check_grad(test1, (Loss(0.0), q, r, A); atol=0.05, verbose=true)
    @test check_grad(test2, (Loss(0.0), q, r, A); atol=0.05, verbose=true)
end


@testset "Utils" begin

    @testset "Test soft_theta" begin
        x = -1:0.1:1
        @test eltype(MicroscopyTools.soft_theta.(Float32.(x))) == Float32
        @test MicroscopyTools.soft_theta.(x, Ref(13.1)) ≈ 1 ./ (1 .+exp.(-2 .*13.1 .*x))
        @test MicroscopyTools.soft_theta_pw(0.01) == 1.0
        @test typeof(MicroscopyTools.soft_theta_pw(0.01f0)) == Float32
        @test MicroscopyTools.soft_theta_pw(-0.01) == 0.0
        @test MicroscopyTools.soft_theta_pw(-0.0) ≈ 0.5
        @test MicroscopyTools.soft_theta_pw(1/3.0, 1.0) ≈ 0.75
    end

    @testset "Test soft_delta" begin

        T = Float64
        f(x, k) = one(T) / (k * √(T(π))) * exp(-abs2(x / k))

        x = -1:0.1:1
        @test eltype(MicroscopyTools.soft_delta.(Float32.(x))) == Float32
        @test MicroscopyTools.soft_delta.(x, Ref(13.1)) ≈ f.(x, 13.1)

        @test typeof(MicroscopyTools.soft_delta_pw(0.01f0)) == Float32
        @test MicroscopyTools.soft_delta_pw(0.01f0) == 0.0
        @test MicroscopyTools.soft_delta_pw(-0.01) == 0.0
        @test MicroscopyTools.soft_delta_pw(-0.0) ≈ 1.0
        @test MicroscopyTools.soft_delta_pw(1/3.0, 1.0) ≈ 0.75
    end

end


function z2gk(A::Float64, B::Float64, g::Float64, k::Float64, z::Float64, c=0.8)
    temp = exp(-g*z)
    A + B*(1.0+c*(1.0-temp)/(1.0+temp))*(1.0+z^2)^k*z
end

function rgk(pars)
    (A,B,g,k) = pars
    z = randn()
    z2gk(A, B, g, k, z)
end

##Simulates U(0,1) order statistics specified in "orderstats" from n total sims
##orderstats should be in ascending order
##See Ripley "Stochastic Simulation" pg 98
function unif_os(orderstats::Array{Int,1}, n::Int)
    p = size(orderstats)[1]
    w = Array{Float64}(p+1)
    w[1] = rand(Gamma(orderstats[1]))
    for i in 2:p
        w[i] = rand(Gamma(orderstats[i] - orderstats[i-1]))
    end
    w[p+1] = rand(Gamma(n + 1 - orderstats[p]))
    wsums = cumsum(w)
    wsums[1:p] / wsums[p+1]
end

##Efficiently simulates g&k order statistics specified in "orderstats" from n total sims
##orderstats should be in ascending order
function rgk_os(pars::Array{Float64,1}, orderstats::Array{Int,1}, n::Int)
    (A,B,g,k) = pars
    u = unif_os(orderstats, n)
    z = quantile.(Normal(), u)
    map((x)->z2gk(A,B,g,k,x), z)
end


function zeck(n)
    n <= 0 && return 0
    fib = [2,1]; while fib[1] < n unshift!(fib,sum(fib[1:2])) end
    dig = Int[]; for f in fib f <= n ? (push!(dig,1); n = n-f;) : push!(dig,0) end
    return dig[1] == 0 ? dig[2:end] : dig
end


# Algorithm 5.1
# Euler's method

function euler(f, a, b, N, α)
	result = Tuple{Float64, Float64}[]
	h = (b - a)/N
	t = α
	ω = α
	push!(result, (t, ω))
	for i ∈ 1:N
		ω = ω + h*f(t, ω)
		t = a + i*h
		push!(result, (t, ω))
	end
	return result
end

f(t, y) = y -t^2 + 1
println(euler(f, 0, 2, 10, 0.5))


"""
    cheb(xmin, xmax, N)

Return a Chebyshev-Lobatto grid, together with its first and second
differentiation matrices.

# Arguments
* `xmin::Real`: rightmost grid point
* `xmax::Real`: leftmost grid point
* `N::Integer`: total number of grid points
"""
function cheb(xmin::T, xmax::T, N::Integer) where {T<:Real}
    x, D, D2 = cheb(N)
    x = 0.5 * (xmax + xmin .+ (xmax - xmin) * x)
    D  ./= 0.5 * (xmax - xmin)
    D2 ./= 0.25 * (xmax - xmin)^2
    x, D, D2
end

# taken from Trefethen (2000), "Spectral Methods in MatLab"

"When omitting grid limits, default to [-1,1] interval"
function cheb(N::Integer)
    @assert(N > 1, "number of points should be greater than 1...")
    M = N - 1
    x = -cos.(pi*(0:M)/M)

    c  = [2; ones(M-1, 1); 2] .* (-1).^(0:M)
    X  = repeat(x, 1, N)
    dX = X - X'

    D = (c * (1 ./ c)') ./ (dX + Matrix(I, N, N))  # off-diagonal entries
    D = D - diagm(0 => sum(D', dims=1)[:])         # diagonal entries

    x, D, D*D
end

"""
    fourier(xsize, N)

Return the first and second derivative matrices on a Fourier grid

# Arguments
* `xsize::Real`: size of (periodic) domain
* `N::Integer`: total number of grid points
"""
function fourier(xsize::T, N::Integer) where {T<:Real}
    D, D2 = fourier(N)
    pi_ = T(pi)
    D  ./= xsize / (2*pi_)
    D2 ./= xsize^2 / (4*pi_^2)
    D, D2
end

# taken from Trefethen (2000), "Spectral Methods in MatLab"

"When omitting `xsize`, default to 2π"
function fourier(N::Integer)
    @assert(mod(N,2)==0, "number of points needs to be even")
    h = 2*pi/N

    column = [0; 0.5*(-1).^(1:N-1) .* cot.((1:N-1)*h/2)]
    tmp = [circshift(column, i) for i in 0:N-1]
    D   = hcat(tmp...)

    column2 = [-pi^2/(3*h^2) - 1/6; -0.5*(-1).^(1:N-1) ./ sin.((1:N-1)*h/2).^2]
    tmp = [circshift(column2, i) for i in 0:N-1]
    D2  = hcat(tmp...)

    D, D2
end

struct ChebInterpolator{T,A,FT<:FFTW.r2rFFTWPlan}
    xmin     :: T
    xmax     :: T
    c        :: A
    fft_plan :: FT
end
"""
    ChebInterpolator(xmin, xmax, N)

Build an interpolator to act on functions defined on a Chebyshev-Lobatto grid.
Uses `FFTW`.

# Arguments
* `xmin::Real`: rightmost grid point
* `xmax::Real`: leftmost grid point
* `N::Integer`: total number of grid points
"""
function ChebInterpolator(xmin::T, xmax::T, N::Int) where {T<:Real}
    M  = N - 1
    x  = Vector{T}(undef, N)

    # use only one thread for the FFTW! by default it allocates a bunch of them,
    # and it hurts performance (by a huge amount!) when using this inside loops.
    # it's much better to thread the loops themselves, which is what we do.
    FFTW.set_num_threads(1)

    # Create the FFT plan for the DCT-I
    fft_plan = FFTW.plan_r2r(x, FFTW.REDFT00)

    c  = M * [2; ones(Int,M-1); 2] .* (-1).^(0:M)

    ChebInterpolator{T,typeof(c),typeof(fft_plan)}(xmin, xmax, c, fft_plan)
end
"""
    ChebInterpolator(xp::Vector)

Build it directly from a `Vector` with the grid points
"""
ChebInterpolator(xp::Vector) = ChebInterpolator(xp[1], xp[end], length(xp))

"""
# Examples

```
julia> xx, = Jecco.cheb(0.0, 2.0, 16);

julia> f = xx.^2;

julia> interp = Jecco.ChebInterpolator(xx);

julia> f_interp = interp(f);

julia> f_interp(0.2)
0.03999999999999987
```
"""
function (interp::ChebInterpolator)(fp)
    # compute the DCT-I of the coefficients
    fft_fp = interp.fft_plan * fp

    # compute the spectral coefficients
    spec_coeff = fft_fp ./ interp.c

    function (x0::T) where {T<:Real}
        @assert interp.xmin <= x0 <= interp.xmax
        X = (2 * x0 - (interp.xmin + interp.xmax)) / (interp.xmax - interp.xmin)
        sum_l = zero(T)
        @fastmath @inbounds for i in LinearIndices(spec_coeff)
            sum_l += spec_coeff[i] * cos( (i-1)*acos(X) )
        end
        sum_l
    end
end


"""
    ExponentiatedKernel()

The exponentiated kernel is a Mercer kernel given by:
```
    κ(x,y) = exp(xᵀy)
```
"""
struct ExponentiatedKernel <: SimpleKernel end

kappa(κ::ExponentiatedKernel, xᵀy::Real) = exp(xᵀy)

metric(::ExponentiatedKernel) = DotProduct()

iskroncompatible(::ExponentiatedKernel) = true

Base.show(io::IO, ::ExponentiatedKernel) = print(io, "Exponentiated Kernel")


# Logistic loss function

export LogisticLoss

"""
**Logistic loss**

    LogisticLoss(y, μ=1)

Returns the function
```math
f(x) = μ⋅∑_i log(1+exp(-y_i⋅x_i))
```
where `y` is an array and `μ` is a positive parameter.
"""
struct LogisticLoss{T <: AbstractArray, R <: Real} <: ProximableFunction
    y::T
    mu::R
    function LogisticLoss{T, R}(y::T, mu::R) where {T, R}
        if mu <= R(0)
            error("parameter mu must be positive")
        end
        new(y, mu)
    end
end

LogisticLoss(y::T, mu::R=1) where {R, T <: AbstractArray} = LogisticLoss{T, R}(y, mu)

is_separable(f::LogisticLoss) = true
is_convex(f::LogisticLoss) = true
is_smooth(f::LogisticLoss) = true
is_prox_accurate(f::LogisticLoss) = false

# f(x)  =  mu log(1 + exp(-y x))

function (f::LogisticLoss)(x::AbstractArray{R}) where R
    val = R(0)
    for k in eachindex(x)
        expyx = exp(f.y[k] * x[k])
        val += log(R(1) + R(1) / expyx)
    end
    return f.mu * val
end

# f'(x) = -mu y exp(-y x) / (1 + exp(-y x))
#       = -mu y / (1 + exp(y x))
#
# Lipschitz constant of gradient: (mu y)

function gradient!(g::AbstractArray{R}, f::LogisticLoss, x::AbstractArray{R}) where R
    val = R(0)
    for k in eachindex(x)
        expyx = exp(f.y[k] * x[k])
        g[k] = -f.mu * f.y[k] / (R(1) + expyx)
        val += log(R(1) + R(1) / expyx)
    end
    return f.mu * val
end

# Computing proximal operator:
# z = prox(f, x, gamma)
# <==> f'(z) + (z - x)/gamma = 0
# <==> (z - x)/gamma - mu y / (1 + exp(y z)) = 0
# <==> z - x - mu gamma y / (1 + exp(y z)) = 0
#
# Indicating the above condition as F(z) = 0, then
# ==> F'(z) = 1 - (mu gamma y^2 exp(y z))/(1+exp(y z))^2
#
# Newton's method (no damping) to compute z reads:
# z_{k+1} = z_k - F(z_k)/F'(z_k)
#
# To ensure convergence of Newton's method a damping is required.
# The damping coefficient could be computed by backtracking.
#
# Alternatively we can use gradient methods with constant step size.

function prox!(z::AbstractArray{R}, f::LogisticLoss, x::AbstractArray{R}, gamma::R=R(1)) where R
    c = R(1) / gamma # convexity modulus
    L = maximum(abs, f.mu .* f.y) + c # Lipschitz constants
    z .= x
    expyz = similar(z)
    Fz = similar(z)
    F1z = similar(z)
    for k = 1:20
        expyz .= exp.(f.y .* z)
        Fz .= z .- x .- f.mu * gamma * (f.y ./ (1 .+ expyz))
        z .-= Fz ./ L
    end
    expyz .= exp.(f.y .* z)
    val = R(0)
    for k in eachindex(expyz)
        val += log(R(1) + R(1)/expyz[k])
    end
    return f.mu * val
end


mutable struct AffineMean{T<:Real,V<:AbstractVector{<:Real}} <: PriorMean{T}
    w::V
    b::T
    nDim::Int
    opt::Optimizer
end

"""
**AffineMean**
```julia
    AffineMean(A::V,b::V;opt::Optimizer=Adam(α=0.01))
    AffineMean(dims::Int,features::Int;opt::Optimizer=Adam(α=0.01))
```
Construct an affine operation on `X` : `μ₀(X) = X*w + b` where `w` is a vector and `b` a scalar
Optionally give an optimizer `opt` (`Adam(α=0.01)` by default)
"""
function AffineMean(w::V,b::T,;opt::Optimizer=Adam(α=0.01)) where {V<:AbstractVector{<:Real},T<:Real}
    AffineMean{eltype(w),V}(w,b,length(w),opt)
end

function AffineMean(dims::Int;opt::Optimizer=Adam(α=0.01))
    AffineMean{Float64,Vector{Float64}}(randn(dims),0.0,dims,opt)
end

function update!(μ::AffineMean{T},grad::AbstractVector{T},X::AbstractMatrix) where {T<:Real}
    Δ = vcat(X'*grad,sum(grad))
    Δ = update(μ.opt,Δ)
    μ.w .+= Δ[1:μ.nDim]
    μ.b += Δ[end]
end

function (μ::AffineMean{T})(x::AbstractMatrix) where {T<:Real}
    @assert μ.nDim == size(x,2) "Number of dimensions do not match"
    return x*μ.w .+ μ.b
end


using AdFem
using PyPlot

function g_func(x, y)
    -2*x*(1-x) - 2*y*(1-y) + 2*(1-2x)*y*(1-y) + 3*(1-2y)*x*(1-x)
end

m = 30
n = 30
h = 1/n

xy = fem_nodes(m, n, h)
x, y = xy[:,1], xy[:,2]
u = @. (x*(1-x)*y*(1-y))

A = constant(compute_fem_laplace_matrix1(m, n, h))
u0 = 2ones(2(m+1)*(n+1))
v0 = 3ones(2(m+1)*(n+1))
u0 = fem_to_gauss_points(u0, m, n, h)
v0 = fem_to_gauss_points(v0, m, n, h)
B = compute_fem_advection_matrix1(constant(u0), constant(v0), m, n, h)
L = -A+B
g = eval_f_on_gauss_pts(g_func, m, n, h)
rhs = compute_fem_source_term1(g, m, n, h)
bd = bcnode("all", m, n, h)
L, _ = fem_impose_Dirichlet_boundary_condition1(L, bd, m, n, h)
rhs[bd] .= 0.0
sol = L\rhs

sess = Session(); init(sess)
S = run(sess, sol)
figure(figsize=(10,4))
subplot(121)
visualize_scalar_on_fem_points(S, m, n, h)
title("computed solution")
subplot(122)
visualize_scalar_on_fem_points(u, m, n, h)
title("exact solution")

# subplot(133)
# visualize_scalar_on_fem_points(S-u, m, n, h)
# title("difference between computed and exact")


# X.F. Yang's Double Cosine kernel (2013)
function doublecosine(r, h)
    # Kernel constant
    k = 2
    if abs(r) < 2h
        # Normalizing factor
        σ = 1/(6k * h)

        # Checking sign
        sign = r == 0 ? 1 : r/abs(r)

        # Kernel computation
        W = (4cos(π/(2h) * r) + cos(π/h * r) + 3) * σ

        # Derivatives computation
        ∂x_W = (4π/(2h) * sin(π/(2h) * r) + π/h * sin(π/h * r)) * -sign * σ

        return (W,∂x_W)
    else
        return (0,0)
    end
    res
end

# J.J. Monaghan's Cubic Spline kernel (1992)
function cubicspline(r, h)
    if abs(r) < 2h
        # Reduced radius
        q = r/h

        # Normalizing factor
        σ = 2/(3h)

        # Checking sign
        sign = r == 0 ? 1 : r/abs(r)

        if abs(r) < h
            # Kernel computation
            W = (1 - 1.5q^2 + 0.75q^3) * σ

            # Derivatives computation
            ∂x_W = (2q - 1.5q^2) * sign/h^2

        else #if h <abs(r) < 2h
            # Kernel computation
            W = (2-q)^3/4 * σ

            # Derivatives computation
            ∂x_W = (q-2)^2/2 * sign/h^2
        end
        return (W,∂x_W)
    else
        return (0,0)
    end
    res
end


using Test, FinancialMonteCarlo;
@show "MertonProcess"
S0 = 100.0;
K = 100.0;
r = 0.02;
T = 1.0;
d = 0.01;
D = 90.0;

Nsim = 10000;
Nstep = 30;
sigma = 0.2;
lam = 5.0;
mu1 = 0.03;
sigma1 = 0.02;
mc = MonteCarloConfiguration(Nsim, Nstep);
mc1 = MonteCarloConfiguration(Nsim, Nstep, FinancialMonteCarlo.AntitheticMC());
toll = 0.8;

rfCurve = ZeroRate(r);

FwdData = Forward(T)
EUData = EuropeanOption(T, K)
AMData = AmericanOption(T, K)
BarrierData = BarrierOptionDownOut(T, K, D)
AsianData = AsianFloatingStrikeOption(T)
Model = MertonProcess(sigma, lam, mu1, sigma1, Underlying(S0, d));

@show FwdPrice = pricer(Model, rfCurve, mc, FwdData);
@show EuPrice = pricer(Model, rfCurve, mc, EUData);
@show AmPrice = pricer(Model, rfCurve, mc, AMData);
@show BarrierPrice = pricer(Model, rfCurve, mc, BarrierData);
@show AsianPrice = pricer(Model, rfCurve, mc, AsianData);

@test abs(FwdPrice - 99.1188767166039) < toll
@test abs(EuPrice - 9.084327245917533) < toll
@test abs(BarrierPrice - 7.880881290426765) < toll
@test abs(AsianPrice - 5.129020349580892) < toll

@show FwdPrice = pricer(Model, rfCurve, mc1, FwdData);
@show EuPrice = pricer(Model, rfCurve, mc1, EUData);
@show AmPrice = pricer(Model, rfCurve, mc1, AMData);
@show BarrierPrice = pricer(Model, rfCurve, mc1, BarrierData);
@show AsianPrice = pricer(Model, rfCurve, mc1, AsianData);
tollanti = 0.6;
@test abs(FwdPrice - 99.1188767166039) < tollanti
@test abs(EuPrice - 9.084327245917533) < tollanti
@test abs(BarrierPrice - 7.880881290426765) < tollanti
@test abs(AsianPrice - 5.129020349580892) < tollanti

@show "Test Merton Parameters"

@test_throws(ErrorException, MertonProcess(-sigma, lam, mu1, sigma1, Underlying(S0, d)))
@test_throws(ErrorException, MertonProcess(sigma, lam, mu1, -sigma1, Underlying(S0, d)))
@test_throws(ErrorException, MertonProcess(sigma, -lam, mu1, sigma1, Underlying(S0, d)))


function qap(n, W, D, ::Val{:JuMP})
    model = JuMP.Model(CBLS.Optimizer)

    @variable(model, 1 ≤ X[1:n] ≤ n, Int)
    @constraint(model, X in AllDifferent())

    Σwd = p -> sum(sum(W[p[i], p[j]] * D[i, j] for j in 1:n) for i in 1:n)

    @objective(model, Min, ScalarFunction(Σwd))

    return model, X
end

"""
    qap(n, weigths, distances; modeler = :JuMP)

Modelize an instance of the Quadractic Assignment Problem with
- `n`: number of both facilities and locations
- `weights`: `Matrix` of the weights of each pair of facilities
- `distances`: `Matrix` of distances between locations
- `modeler`: Default to `:JuMP`. No other modeler available for now.

# From Wikipedia
There are a set of `n` facilities and a set of `n` locations. For each pair of locations, a distance is specified and for each pair of facilities a `weight` or flow is specified (e.g., the amount of supplies transported between the two facilities). The problem is to assign all facilities to different locations with the goal of minimizing the sum of the `distances` multiplied by the corresponding flows.
"""
qap(n, weigths, distances; modeler = :JuMP) = qap(n, weigths, distances, Val(modeler))


using LinearAlgebra

"""
    normal_hessenberg_matrix(Float64, vals::AbstractVector)

Generate a normal hessenberg matrix with eigenvalues `vals`
"""
function normal_hessenberg_matrix(T::Type{<:Number}, vals::AbstractVector)
    n = length(vals)
    Q, R = qr(randn(T, n, n))
    A = Q * Diagonal(vals) * Q'
    return triu(hessenberg!(A).factors, -1)
end

function normal_hessenberg_matrix(T::Type{<:Real}, vals::AbstractVector{<:Complex})
    n = length(vals)
    Q, R = qr(randn(T, n, n))
    D = zeros(T, n, n)
    i = 1
    while i ≤ n
        if imag(vals[i]) != 0
            D[i+0,i+0] = real(vals[i])
            D[i+1,i+0] = imag(vals[i])
            D[i+0,i+1] = -imag(vals[i])
            D[i+1,i+1] = real(vals[i])
            i += 2
        else
            D[i] = real(vals[i])
            i += 1
        end
    end
    return triu(hessenberg!(Q * D * Q').factors, -1)
end

"""
    realimag(1 + 3im) → (1, 3)

Split imaginary number into a tuple of real and imaginary part
"""
realimag(x) = (real(x), imag(x))

"""
    is_hessenberg(H) → bool

Test whether the sub-subdiagonals of H are zero.
"""
is_hessenberg(H) = norm(tril(H, -2)) == 0


using LinearAlgebra
using SparseArrays
using Plots

"This routine solves the heat equation using Forward Euler in time and finite differences
in space. Note that this is unstable unless dt ~ O(h^2)."

m = 100 # number of points
T = .01
dt = .0005

u0(x) = 0.0 # initial condition
f(x) = 5*(Float64((x > -.5) && (x <= 0.0)) - Float64((x < .5) && (x >= 0.0)))
f(x,t) = f(x)*exp(-t)
α(t) = 1.0
β(t) = pi

# define spatial grid
x = LinRange(-1,1,m+2)
xint = x[2:end-1]
h = x[2]-x[1]
A = (1/h^2) * spdiagm(0=>2*ones(m),-1=>-ones(m-1),1=>-ones(m-1))

function F(t)
    b = f.(xint,t) # look for f(x,t)
    b[1] += α(t)/h^2
    b[end] += β(t)/h^2
    return b
end

u = u0.(xint)
Nsteps = ceil(Int,T/dt)
dt = T / Nsteps

interval = 1
@gif for k = 1:Nsteps
    tk = k*dt
    u .= u + dt * (F(tk) - A*u)
    if k % interval==0
        plot(xint,u,linewidth=2,label="Solution",ylims=(-1.0,3.0))
        println("on timestep $k out of $Nsteps.")
    end
end every interval


@doc raw"""
    GammaRationalKernel([α [,β [,γ]]])

The gamma-rational kernel is a generalization of the rational-quadratic kernel with an
additional shape parameter:

```math
\kappa(\mathbf{x},\mathbf{y})
= \left(1 +\alpha ||\mathbf{x},\mathbf{y}||^{\gamma}\right)^{-\beta}
\qquad \alpha > 0, \; \beta > 0, \; 0 < \gamma \leq 1
```

where ``\alpha`` is a scaling parameter and ``\beta`` and ``\gamma`` are shape parameters.
"""
struct GammaRationalKernel{T<:AbstractFloat} <: MercerKernel{T}
    alpha::HyperParameter{T}
    beta::HyperParameter{T}
    gamma::HyperParameter{T}
    GammaRationalKernel{T}(α::Real, β::Real, γ::Real) where {T<:AbstractFloat} = new{T}(
        HyperParameter(convert(T,α), interval(OpenBound(zero(T)), nothing)),
        HyperParameter(convert(T,β), interval(OpenBound(zero(T)), nothing)),
        HyperParameter(convert(T,γ), interval(OpenBound(zero(T)), ClosedBound(one(T))))
    )
end
function GammaRationalKernel(
        α::T1 = 1.0,
        β::T2 = one(T1),
        γ::T3 = one(floattype(T1,T2))
    ) where {T1<:Real,T2<:Real,T3<:Real}
    GammaRationalKernel{floattype(T1,T2,T3)}(α,β,γ)
end

@inline gammarationalkernel(z::T, α::T, β::T, γ::T) where {T<:AbstractFloat} = (1 + α*(z^γ))^(-β)

@inline basefunction(::GammaRationalKernel) = SquaredEuclidean()
@inline function kappa(κ::GammaRationalKernel{T}, z::T) where {T}
    gammarationalkernel(z, getvalue(κ.alpha), getvalue(κ.beta), getvalue(κ.gamma))
end


#' ---
#' title: Experiment Design
#' ---

#' **Originally Contributed by**: Arpit Bhatia, Chris Coey

#' This tutorial covers experiment design examples (D-optimal, A-optimal, and E-optimal)
#' from section 7.5 of the book Convex Optimization by Boyd and Vandenberghe[[1]](#c1)

#' ## Relaxed Experiment Design Problem

#' The basic experiment design problem is as follows.
#' Given the menu of possible choices for experiments, $v_{1}, \ldots, v_{p}$,
#' and the total number $m$ of experiments to be carried out, choose the numbers of each type of experiment,
#' $i . e ., m_{1}, \ldots, m_{p}$ to make the error covariance $E$ small (in some sense).
#' The variables $m_{1}, \ldots, m_{p}$ must, of course, be integers and sum to $m,$ the given total number of experiments.
#' This leads to the optimization problem

#' $$
#' \begin{array}{cl}{\operatorname{minimize}\left(\mathrm{w.r.t.} \mathbf{S}_{+}^{n}\right)} & {E=\left(\sum_{j=1}^{p} m_{j} v_{j} v_{j}^{T}\right)^{-1}} \\ {\text { subject to }} & {m_{i} \geq 0, \quad m_{1}+\cdots+m_{p}=m} \\ {} & {m_{i} \in \mathbf{Z}}\end{array}
#' $$

#' The basic experiment design problem can be a hard combinatorial problem when $m,$ the total number of experiments,
#' is comparable to $n$ , since in this case the $m_{i}$ are all small integers.
#' In the case when $m$ is large compared to $n$ , however, a good approximate solution can be found by ignoring,
#' or relaxing, the constraint that the $m_{i}$ are integers.
#' Let $\lambda_{i}=m_{i} / m,$ which is the fraction of the total number of experiments for which
#' $a_{j}=v_{i},$ or the relative frequency of experiment $i$.
#' We can express the error covariance in terms of $\lambda_{i}$ as

#' $$
#' E=\frac{1}{m}\left(\sum_{i=1}^{p} \lambda_{i} v_{i} v_{i}^{T}\right)^{-1}
#' $$

#' The vector $\lambda \in \mathbf{R}^{p}$ satisfies $\lambda \succeq 0,
#' \mathbf{1}^{T} \lambda=1,$ and also, each $\lambda_{i}$ is an integer multiple of $1 / m$.
#' By ignoring this last constraint, we arrive at the problem

#' $$
#' \begin{array}{ll}{\operatorname{minimize}\left(\mathrm{w.r.t.} \mathbf{S}_{+}^{n}\right)} & {E=(1 / m)\left(\sum_{i=1}^{p} \lambda_{i} v_{i} v_{i}^{T}\right)^{-1}} \\ {\text { subject to }} & {\lambda \succeq 0, \quad \mathbf{1}^{T} \lambda=1}\end{array}
#' $$

#' ## Types of Experiment Design Problems

#' Several scalarizations have been proposed for the experiment design problem,
#' which is a vector optimization problem over the positive semidefinite cone.

using JuMP
using SCS
using LinearAlgebra

q = 4 # dimension of estimate space
p = 8 # number of experimental vectors
nmax = 3 # upper bound on lambda
n = 12

V = randn(q, p)

eye = Matrix{Float64}(I, q, q);

#' ### A-optimal design

#' In A-optimal experiment design, we minimize tr $E$, the trace of the covariance matrix.
#' This objective is simply the mean of the norm of the error squared:

#' $$
#' \mathbf{E}\|e\|_{2}^{2}=\mathbf{E} \operatorname{tr}\left(e e^{T}\right)=\operatorname{tr} E
#' $$

#' The A-optimal experiment design problem in SDP form is

#' $$
#' \begin{array}{ll}{\operatorname{minimize}} & {\mathbf{1}^{T} u} \\ {\text { subject to }} & {\left[\begin{array}{cc}{\sum_{i=1}^{p} \lambda_{i} v_{i} v_{i}^{T}} & {e_{k}} \\ {e_{k}^{T}} & {u_{k}}\end{array}\right] \succeq 0, \quad k=1, \ldots, n} \\ {} & {\lambda \succeq 0, \quad \mathbf{1}^{T} \lambda=1}\end{array}
#' $$

aOpt = Model(with_optimizer(SCS.Optimizer, verbose = 0))
@variable(aOpt, np[1:p], lower_bound = 0, upper_bound = nmax)
@variable(aOpt, u[1:q], lower_bound = 0)

@constraint(aOpt, sum(np) <= n)
for i = 1:q
    @SDconstraint(aOpt, [V * diagm(0 => np ./ n) * V' eye[:, i]; eye[i, :]' u[i]] >= 0)
end

@objective(aOpt, Min, sum(u))

optimize!(aOpt)

@show objective_value(aOpt);
@show value.(np);

#' ### E-optimal design

#' In $E$ -optimal design, we minimize the norm of the error covariance matrix, i.e. the maximum eigenvalue of $E$.
#' Since the diameter (twice the longest semi-axis) of the confidence ellipsoid $\mathcal{E}$
#' is proportional to $\|E\|_{2}^{1 / 2}$,
#' minimizing $\|E\|_{2}$ can be interpreted geometrically as minimizing the diameter of the confidence ellipsoid.
#' E-optimal design can also be interpreted as minimizing the maximum variance of $q^{T} e$,
#' over all $q$ with $\|q\|_{2}=1$.
#' The E-optimal experiment design problem in SDP form is

#' $$
#' \begin{array}{cl}{\operatorname{maximize}} & {t} \\ {\text { subject to }} & {\sum_{i=1}^{p} \lambda_{i} v_{i} v_{i}^{T} \succeq t I} \\ {} & {\lambda \succeq 0, \quad \mathbf{1}^{T} \lambda=1}\end{array}
#' $$

eOpt = Model(with_optimizer(SCS.Optimizer, verbose = 0))
@variable(eOpt, np[1:p], lower_bound = 0, upper_bound = nmax)
@variable(eOpt, t)

@SDconstraint(eOpt, V * diagm(0 => np ./ n) * V' - (t .* eye) >= 0)
@constraint(eOpt, sum(np) <= n)

@objective(eOpt, Max, t)

optimize!(eOpt)

@show objective_value(eOpt);
@show value.(np);

#' ### D-optimal design
#' The most widely used scalarization is called $D$ -optimal design,
#' in which we minimize the determinant of the error covariance matrix $E$.
#' This corresponds to designing the experiment to minimize the volume of the resulting confidence ellipsoid
#' (for a fixed confidence level).
#' Ignoring the constant factor 1$/ m$ in $E$, and taking the logarithm of the objective,
#' we can pose this problem as convex optimization problem

#' $$
#' \begin{array}{ll}{\operatorname{minimize}} & {\log \operatorname{det}\left(\sum_{i=1}^{p} \lambda_{i} v_{i} v_{i}^{T}\right)^{-1}} \\ {\text { subject to }} & {\lambda \succeq 0, \quad \mathbf{1}^{T} \lambda=1}\end{array}
#' $$

dOpt = Model(with_optimizer(SCS.Optimizer, verbose = 0))
@variable(dOpt, np[1:p], lower_bound = 0, upper_bound = nmax)
@variable(dOpt, t)
@objective(dOpt, Max, t)
@constraint(dOpt, sum(np) <= n)
E = V * diagm(0 => np ./ n) * V'
@constraint(dOpt, [t, 1, (E[i, j] for i in 1:q for j in 1:i)...] in MOI.LogDetConeTriangle(q))

optimize!(dOpt)

@show objective_value(dOpt);
@show value.(np);

#' ### References
#'
#' 1. Boyd, S., & Vandenberghe, L. (2004). Convex Optimization. Cambridge: Cambridge University Press. doi:10.1017/CBO9780511804441


# Helper function for lesson 8 lecture notes.
using Distributions

function genDataset(N::Int64)
    # Generate dataset {(x1,y1),...,(xN,yN)}
    # x is a 2d feature vector [x1;x2]
    # y ∈ {false,true} is a binary class label
    # p(x|y) is multi-modal (mixture of uniform and Gaussian distributions)
#     srand(123)
    X = Matrix{Float64}(undef,2,N); y = Vector{Bool}(undef,N)
    for n=1:N
        if (y[n]=(rand()>0.6)) # p(y=true) = 0.6
            # Sample class 1 conditional distribution
            if rand()<0.5
                X[:,n] = [6.0; 0.5] .* rand(2) .+ [3.0; 6.0]
            else
                X[:,n] = sqrt(0.5) * randn(2) .+ [5.5, 0.0]
            end
        else
            # Sample class 2 conditional distribution
            X[:,n] = randn(2) .+ [1., 4.]
        end
    end

    return (X, y)
end

function buildGenerativeDiscriminationBoundary(X::Matrix, y::Vector{Bool})
    # Generate discrimination boundary function x[2] = boundary(x[1]) for a Gaussian generative model:
    # X = [x_1,...,x_N]
    # y = [y_1;...;y_N]
    # x is a 2-d real (feature) vector
    # y ∈ {false,true}
    # x|y ~ 𝓝(x|μ_y, Σ_y)
    # We find the class-conditional Gaussian distributions by MLE
    # See lesson 7 (generative classification) for more details
    (size(X,1)==2) || error("The columns of X should have length 2")

    # MLE of p(y)
    p_1_est = sum(y.==true) / length(y)
    π_hat = [p_1_est; 1 .- p_1_est]

    # MLE of class-conditional multivariate Gaussian densities
    X_cls1 = X[:,y.==true]
    X_cls2 = X[:,y.==false]
    d1 = fit_mle(FullNormal, X_cls1)  # MLE density estimation d1 = N(μ₁, Σ₁)
    d2 = fit_mle(FullNormal, X_cls2)  # MLE density estimation d2 = N(μ₂, Σ₂)
    Σ = π_hat[1] * cov(d1) + π_hat[2] * cov(d2) # Combine Σ₁ and Σ₂ into Σ

    conditionals = [MvNormal(mean(d1), Σ); MvNormal(mean(d2), Σ)] # p(x|C)

    # Discrimination boundary of the posterior (p(apple|x;D) = p(peach|x;D) = 0.5)
    β(k) = inv(Σ)* mean(conditionals[k])
    γ(k) = -0.5 * mean(conditionals[k])' * inv(Σ) * mean(conditionals[k]) + log(π_hat[k])
    function discriminant_x2(x1)
        # Solve discriminant equation for x2

        β12 = β(1) .- β(2)
        γ12 = (γ(1) .- γ(2))[1,1]
        return -1 ./ β12[2]*(β12[1]*x1 .+ γ12)
    end

    return discriminant_x2
end


using OrdinaryDiffEq, ParameterizedFunctions, DiffEqBase, RecursiveArrayTools

# Here are the problems to solve

f1 = @ode_def LotkaVolterraTest begin
  dx = a*x - x*y
  dy = -3y + x*y
end a
u0 = [1.0;1.0]
tspan = (0.0,10.0)
p = [1.5]
prob1 = ODEProblem(f1,u0,tspan,[1.5])

f2 = @ode_def LotkaVolterraMulti begin
  dx = a*x - x*y
  dy = -c*y + x*y
end a c
p = [1.5,3.0]
prob2 = ODEProblem(f2,u0,tspan,p)

f3 = @ode_def LotkaVolterraAll begin
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d
p = [1.5,1.0,3.0,1.0]
prob3 = ODEProblem(f3,u0,tspan,p)

# Generate random data based off of the known solution
sol = solve(prob1,Tsit5())
t = collect(range(0, stop=10, length=200))
randomized = VectorOfArray([(sol(t[i]) + .01randn(2)) for i in 1:length(t)])
data = convert(Array,randomized)


# exercise estimating a linear model by iterative minimization,
# and verifying that OLS gives same results.
# If the data is missing, run BasicDataAnalysis.jl
# in the Examples/Julia directory

using CSV, DataFrames, StatsModels, Econometrics
# prepare the data
card = CSV.read("../Julia/cooked.csv", DataFrame)
display(card)

y = card[:,1]
x = Matrix{Float64}([ones(size(card,1)) card[:,2:7]])

# define the objective function and start value
obj = theta -> (y-x*theta)'*(y-x*theta)
startval = zeros(size(x,2))
# do the minimization
thetahat, objvalue = fminunc(obj, startval)
println("the OLS estimates by numeric min: obj. value: ", round(objvalue,digits=5))
prettyprint(thetahat)

# verify by using OLS, which uses the analytic solution
println("verifying by OLS:")
ols( @formula(lnwage ~ 1 + educ + exper + expsq + black + south + smsa), card)
nothing


compute(n::Int)::Int = n * (n + 1) * (3n ^ 2 - n - 2) ÷ 12


function parametrized_strogatz(du,u,p,t)
    #NumberOfOscillators,CouplingConstant, and NaturalFreqs
    N,K,w = p
    ##Centroid
    du[1] = 0
    for i in 1:N
        du[1] += du[i+1]
    end
    du[1] = du[1]/N
    #Oscillators
    for i in 1:N
        du[i+1] = (1.0-abs2(u[i+1])+w[i]*im)*u[i+1] + K*(u[1]-u[i+1])
    end
end

function getparameters_strogatz(N,K,γ,distribution)
    distribution == "Uniform" ? nothing : error("$(distribution) is not yet supported")
    j = collect(1:N)
    w = broadcast(j->((2j-N-1)/(N-1))*((pi*γ)/2),j)
    p = (N=N, K=K, w=w)
    return p
end

function getstartpoints_strogatz(N)
    u0 = ComplexF64[]
    d = Uniform(-1,1)
    for i in 1:N
        push!(u0,rand(d)+rand(d)im)
    end
    pushfirst!(u0,mean(u0))
    return u0
end

function strogatz(N,K,T,fs,γ,distribution)
    tspan = (0.0,T)
    u0 = getstartpoints_strogatz(N)
    p = getparameters_strogatz(N,K,γ,distribution)
    prob = ODEProblem(parametrized_strogatz,u0,tspan,p)
    sol = solve(prob,RK4(),saveat=collect(0:1/fs:T),progress=true)
end


# It is possible to show that the square root of two can be expressed as an
# infinite continued fraction.
#
# √2=1+1/(2+1/(2+1/(2+...)))
#
# By expanding this for the first four iterations, we get:
#
# 1+1/2 = 3/2 = 1.5
# 1+1/(2+1/2) = 7/5 = 1.4
# 1+1/(2+1/(2+1/2)) = 17/12 =1.41666...
# 1+1/(2+1/(2+1/(2+1/2))) = 41/29 = 1.41379...
#
# The next three expansions are 99/70, 239/169, and 577/408, but the eighth
# expansion, 1393/985, is the first example where the number of digits in the
# numerator exceeds the number of digits in the denominator.
#
# In the first one-thousand expansions, how many fractions contain a numerator
# with more digits than the denominator?

using ProjectEulerSolutions

# Using Julia's built-in rational numbers only works up to n=47 or so, when we
# begin to run into over-runs.  Really this is a pattern matching game.  The
# numerator for iteration i (n_i) and the denominator for iteration i (d_i)
# follow a pattern: d_i = n_{i-1} + d_{i-1}, n_i = d_i + d_{i-1}
function p057solution(n_max::Integer=100)::Integer

    num = BigInt(3)
    denom = BigInt(2)
    count = 0
    for n in 2:n_max
        denom, num = num + denom, num + 2*denom
        if ndigits(num) > ndigits(denom)
            count += 1
        end
    end
    return count
end

p057 = Problems.Problem(p057solution)

Problems.benchmark(p057, 1_000)


using JuMP, Ipopt

# Create JuMP model, using Ipopt as the solver
rocket = Model(optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0))

# Constants
# Note that all parameters in the model have been normalized
# to be dimensionless. See the COPS3 paper for more info.
h_0 = 1    # Initial height
v_0 = 0    # Initial velocity
m_0 = 1    # Initial mass
g_0 = 1    # Gravity at the surface

T_c = 3.5  # Used for thrust
h_c = 500  # Used for drag
v_c = 620  # Used for drag
m_c = 0.6  # Fraction of initial mass left at end

c     = 0.5 * sqrt(g_0 * h_0)  # Thrust-to-fuel mass
m_f   = m_c * m_0            # Final mass
D_c   = 0.5 * v_c * m_0 / g_0    # Drag scaling
T_max = T_c * g_0 * m_0        # Maximum thrust

n = 800   # Time steps

@variables(rocket, begin
    Δt ≥ 0, (start = 1/n) # Time step
    # State variables
    v[1:n] ≥ 0            # Velocity
    h[1:n] ≥ h_0          # Height
    m_f ≤ m[1:n] ≤ m_0    # Mass
    # Control
    0 ≤ T[1:n] ≤ T_max    # Thrust
end)

# Objective: maximize altitude at end of time of flight
@objective(rocket, Max, h[n])

# Initial conditions
@constraints(rocket, begin
    v[1] == v_0
    h[1] == h_0
    m[1] == m_0
    m[n] == m_f
end)

# Forces
# Drag(h,v) = Dc v^2 exp( -hc * (h - h0) / h0 )
@NLexpression(rocket, drag[j = 1:n], D_c * (v[j]^2) * exp(-h_c * (h[j] - h_0) / h_0))
# Grav(h)   = go * (h0 / h)^2
@NLexpression(rocket, grav[j = 1:n], g_0 * (h_0 / h[j])^2)
# Time of flight
@NLexpression(rocket, t_f, Δt * n)

# Dynamics
for j in 2:n
    # h' = v

    # Rectangular integration
    # @NLconstraint(rocket, h[j] == h[j - 1] + Δt * v[j - 1])

    # Trapezoidal integration
    @NLconstraint(rocket,
        h[j] == h[j - 1] + 0.5 * Δt * (v[j] + v[j - 1]))

    # v' = (T-D(h,v))/m - g(h)

    # Rectangular integration
    # @NLconstraint(rocket, v[j] == v[j - 1] + Δt *(
    #                 (T[j - 1] - drag[j - 1]) / m[j - 1] - grav[j - 1]))

    # Trapezoidal integration
    @NLconstraint(rocket,
        v[j] == v[j-1] + 0.5 * Δt * (
            (T[j] - drag[j] - m[j] * grav[j]) / m[j] +
            (T[j - 1] - drag[j - 1] - m[j - 1] * grav[j - 1]) / m[j - 1]))

    # m' = -T/c

    # Rectangular integration
    # @NLconstraint(rocket, m[j] == m[j - 1] - Δt * T[j - 1] / c)

    # Trapezoidal integration
    @NLconstraint(rocket,
        m[j] == m[j - 1] - 0.5 * Δt * (T[j] + T[j-1]) / c)
end

# Solve for the control and state
println("Solving...")
status = optimize!(rocket)

# Display results
# println("Solver status: ", status)
println("Max height: ", objective_value(rocket))

# Can visualize the state and control variables
using Gadfly

h_plot = plot(x = (1:n) * value.(Δt), y = value.(h)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Altitude"))
m_plot = plot(x = (1:n) * value.(Δt), y = value.(m)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Mass"))
v_plot = plot(x = (1:n) * value.(Δt), y = value.(v)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Velocity"))
T_plot = plot(x = (1:n) * value.(Δt), y = value.(T)[:], Geom.line,
                Guide.xlabel("Time (s)"), Guide.ylabel("Thrust"))
draw(SVG(6inch, 6inch), vstack(hstack(h_plot, m_plot), hstack(v_plot, T_plot)))


"""
    CosineKernel(; metric=Euclidean())

Cosine kernel with respect to the `metric`.

# Definition

For inputs ``x, x'`` and metric ``d(\\cdot, \\cdot)``, the cosine kernel is defined as
```math
k(x, x') = \\cos(\\pi d(x, x')).
```
By default, ``d`` is the Euclidean metric ``d(x, x') = \\|x - x'\\|_2``.
"""
struct CosineKernel{M} <: SimpleKernel
    metric::M
end

CosineKernel(; metric=Euclidean()) = CosineKernel(metric)

kappa(::CosineKernel, d::Real) = cospi(d)

metric(k::CosineKernel) = k.metric

Base.show(io::IO, k::CosineKernel) = print(io, "Cosine Kernel (metric = ", k.metric, ")")


using EmpiricalBayes
using Test
using Distributions
using QuadGK

# Move these to other parts eventually

# test if convolution matrix works for small n..
A = convolution_matrix(DiscretizedNormalConvolutionProblem, range(-1, stop=1, length=4), range(-7, stop=7, length=2000))
h = range(-7, stop=7, length=2000)[2] - range(-7, stop=7, length=2000)[1]
@test isapprox(sum(A,1)*h, ones(4)'; atol=1e-4)

# test discretization approach vs. properly integrating..
n_marginal_grid = 1001
marginal_grid =  range(-3, stop=3, length=n_marginal_grid)
marginal_h = marginal_grid[2]-marginal_grid[1]

n_prior_grid = 1001
prior_grid = range(-6, stop=6, length=n_prior_grid)

normal_normal = DiscretizedNormalConvolutionProblem(Normal(0,0.5),
                    prior_grid,
                    marginal_grid)

marginal_dist = Normal(0,sqrt(1+0.5^2))
marginal_l = marginal_grid .- marginal_h/2
marginal_r = marginal_grid .+ marginal_h/2

f_marginal = [QuadGK.quadgk(x->pdf.(marginal_dist,x),marginal_l[i],marginal_r[i])[1] for i=1:n_marginal_grid];
f_marginal ./= sum(f_marginal);

@test isapprox(f_marginal, normal_normal.marginal; atol= 1e-7)

@test sum(normal_normal.prior) ≈ 1
@test sum(normal_normal.marginal) ≈ 1

# check if posterior mean calculated correctly (normal-normal conjugate we know this ofc)
post_mean = posterior_stats(normal_normal, μ -> μ, 2)
@test isapprox(post_mean[3], 2/1/(1+1/0.5^2))

# now check for indicator μ >= 0
# actually tolerances here are rather bad...esp. for ground truth should do a better job.

true_num = quadgk(β ->  pdf(Normal(0,0.5), β)*pdf(Normal(), 2-β) , 0,100)[1]
true_denom = quadgk(β ->  pdf(Normal(0,0.5), β)*pdf(Normal(), 2-β) , -100,100)[1]
true_ratio = true_num/true_denom

posterior_positive = posterior_stats(normal_normal, μ ->  (μ >= 0).*1, 2)
@test isapprox(true_num, posterior_positive[1]; atol=1e-3 )
@test isapprox(true_denom, posterior_positive[2]; atol=1e-3 )
@test isapprox(true_ratio, posterior_positive[3]; atol=1e-2)

# again in this case we know exactly what the value is..

post_Normal = Normal(2/1/(1+1/0.5^2), sqrt(1*0.5^2/(1+0.5^2)))
post_positive_true = ccdf(post_Normal, 0 )

@test isapprox(post_positive_true, true_ratio)
@test isapprox(post_positive_true, posterior_positive[3]; atol=1e-2)

## OK NOW let us check with the more exact object

normal_normal_exact = NormalConvolutionProblem(Normal(0,0.5), marginal_grid)
@test isapprox(f_marginal, normal_normal_exact.marginal; atol= 1e-9)

non_disr_post_positive = posterior_stats(normal_normal_exact, μ ->  (μ >= 0).*1, 2)
@test isapprox(post_positive_true, non_disr_post_positive[3]; atol=1e-8)


using Revise
using ADCME
using PyPlot
using AdFem

m = 40
n = 40
h = 1/n
bdnode = bcnode("all", m, n, h)
xy = gauss_nodes(m, n, h)
xy_fem = fem_nodes(m, n, h)

function poisson1d(κ)
    κ = compute_space_varying_tangent_elasticity_matrix(κ, m, n, h)
    K = compute_fem_stiffness_matrix1(κ, m, n, h)
    K, _ = fem_impose_Dirichlet_boundary_condition1(K, bdnode, m, n, h)
    rhs = compute_fem_source_term1(ones(4m*n), m, n, h)
    rhs[bdnode] .= 0.0
    sol = K\rhs
end

κ = @. 0.1 + sin(xy[:,1]) + (xy[:,2]-1)^2 * xy[:,1] + log(1+xy[:,2])
y = poisson1d(κ)

sess = Session(); init(sess)
SOL = run(sess, y)

# inverse modeling
κnn = squeeze(abs(ae(xy, [20,20,20,1])))
y = poisson1d(κnn)

using Random; Random.seed!(233)
idx = fem_randidx(100, m, n, h)
obs = y[idx]
OBS = SOL[idx]
loss = sum((obs-OBS)^2)

init(sess)
BFGS!(sess, loss, 200)

figure(figsize=(10,4))
subplot(121)
visualize_scalar_on_fem_points(SOL, m, n, h)
subplot(122)
visualize_scalar_on_fem_points(run(sess, y), m, n, h)
plot(xy_fem[idx,1], xy_fem[idx,2], "o", c="red", label="Observation")
legend()

figure(figsize=(10,4))
subplot(121)
visualize_scalar_on_gauss_points(κ, m, n, h)
title("Exact \$K(x, y)\$")
subplot(122)
visualize_scalar_on_gauss_points(run(sess, κnn), m, n, h)
title("Estimated \$K(x, y)\$")

H = gradients(obs, κnn)
H = run(sess, H)
y = OBS
hs = run(sess, obs)
R = (1e-1)^2*diagm(0=>ones(length(obs)))
s = run(sess, κnn)
Q = (1e-2)^2*diagm(0=>ones(length(κnn)))
μ, Σ = uqnlin(y, hs, H, R, s, Q)

σ = diag(Σ)
figure(figsize=(10,4))
subplot(121)
visualize_scalar_on_gauss_points(σ, m, n, h)
title("Standard Deviation")
subplot(122)
visualize_scalar_on_gauss_points(abs.(run(sess, κnn)-κ), m, n, h)
title("Absolute Error")


"""
spitchfork(u,lambda)

The nonlinearity f(u) = u^3 - lamba u. The dynamics for du/du = -f(u)
have a pitchfork bifurcation at lambda=0. The steady-state solution
u=0 is unique for lambda < 0 and there are three steady-state solutions
if lambda > 0. This is a simple-minded version of the buckling beam problem.

The function sptest(u) = spitchfork(u,.5) is the one I call in the testing.

"""
function spitchfork(u, lambda)
    fu = u^3 - lambda * u
    return fu
end

function sptest(u)
    lambda = 0.5
    spt = spitchfork(u, lambda)
    return spt
end

function spitchp(u, lambda)
    fp = 3 * u^2 - lambda
    return fp
end

function sptestp(u)
    lambda = 0.5
    sptp = spitchp(u, lambda)
    return sptp
end


# This file investigates 2D hiddden interpolation mean square error(MSE) with respect to inteprolation point number.

using FractalTools
using Makie

# Construct interpolation data
f(x, y) = [
    x^2 + y^2 + 1,
    x^2 - y^2,
]
vtx = [
    BigFloat.([0.0, 0.0]),
    BigFloat.([1.0, 0.0]),
    BigFloat.([0.5, 1.0])
    ]
freevar = 0.001
npts    = 50 : 5 : 150
ntpts   = 2 * npts[end]

# Construct test data
tpts = getdata(vtx, ntpts)

# Compute errors
mse = map(npts) do npt
    @info npt
    # Construct interpolation data
    pts = getdata(f, vtx, npt)

    # Construct interpolant
    interp = interpolate(pts, HInterp2D(fill(freevar, 2, 2)))

    # Compute error
    fvals = getindex.(map(pnt -> f(pnt...), tpts), 1)
    ivals = getindex.(map(pnt -> interp(pnt...), tpts), 1)
    sum((fvals - ivals).^2) / length(tpts)
end

# Plot mse
fig = Figure()
ax = fig[1, 1] = Axis(fig, xlabel="Number of Points", ylabel="MSE", title="2D Hidden Interpolation MSE")
stem!(ax, npts, mse, color=:black)
save(joinpath(@__DIR__, "hinterp2d_mse.png"), fig)
display(fig)


function score(x, y)
    r = sqrt(x^2 + y^2)
    if r <= 1
        return 10
    elseif r <= 5
        return 5
    elseif r <= 10
        return 1
    else
        return 0
    end
end