# 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


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


# 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 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


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, ")")


"""
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


export fourier_diff

"""
    fourier_diff([T=Float64,] N; order=1)

Create a Fourier differentiation matrix with numerical type T on the domain
`x = range(0, 2π, length=N+1)[1:end-1]`.
"""
function fourier_diff(T::Type{<:Number}, N::Integer; order=1)
    D = zeros(T, N, N)
    n1 = (N - 1) ÷ 2
    n2 = N ÷ 2
    x = LinRange{T}(0, π, N+1)
    if order == 1
        for i in 2:N
            sgn = (one(T)/2 - iseven(i))
            D[i, 1] = iseven(N) ? sgn*cot(x[i]) : sgn*csc(x[i])
        end
    elseif order == 2
        D[1, 1] = iseven(N) ? -N^2*one(T)/12 - one(T)/6 : -N^2*one(T)/12 + one(T)/12
        for i in 2:N
            sgn = -(one(T)/2 - iseven(i))
            D[i, 1] = iseven(N) ? sgn*csc(x[i]).^2 : sgn*cot(x[i])*csc(x[i])
        end
    else
        error("Not implemented")
    end
    for j in 2:N
        D[1, j] = D[N, j-1]
        D[2:N, j] .= D[1:N-1, j-1]
    end
    return D
end
fourier_diff(N::Integer; kwargs...) = fourier_diff(Float64, N; kwargs...)


### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

# ╔═╡ dcfde69e-20c7-11eb-0108-dbd2f5cb0098
begin
	using Pkg
    Pkg.activate(pwd())
	Pkg.instantiate()
end

# ╔═╡ 864115a0-20d4-11eb-16ff-71d198f9342e
begin
	using CSV
	using DataFrames
	using RDatasets
	using StatsBase
	using Plots
	using LinearAlgebra
	import Statistics: covm
end

# ╔═╡ a33c14a0-20c2-11eb-0bf8-837173d9d91c
md"""
# Assignment 1: Implement PCA
### Aadam (CS1945)
### CSE 662 - Deep Learning by Dr. Masroor Hussain

---

## Assignment Statement:
Write the code in C, Python or Matlab for $l=1$ PCA as discussed in class of Chapter no. 2. Five bonus points for $1 =< l < n$

---
"""

# ╔═╡ cf5c9eb0-20c2-11eb-05ba-2f5dd23fdaf4
md"""
# Principal Component Analysis (PCA)
PCA is a technique that is used to derive an orthogonal projection to convert a given set of observations to linearly uncorrelated variables, called **principal components**.

One of the most widely used application of PCA is **dimensionality reduction**, which helps us out, when training our models on large datasets, by minimizing noise and redundancy in the dataset. Basic idea of the dimensionality reduction is to represent an $M$-dimensional data into an $N$-dimensional subspace, where $N < M$. This would result in data loss, but our aim is to minimize it as much as possible. For that purposes, we try to find some **principal components** in the data that can represent the features as a linear combination, without loosing much information.

PCA uses **covariance matrix** to analyze:
- variance of each feature, showing if a feature is relevant or pure noise.
- linear relationship strength between pairs of features, spotting redundant features.

There are two main approaches to identify principal components:
1. Calculate the **eigenvectors** of the covariance matrix.
2. Calculate the **single value decomposition** of the covariance matrix.

Although **SVD** has higher numerical accuracy, it has lower running time, as compared to the **eigenvector decomposition** method.

For this assignment, we'll use **Eigendecomposition** method to find principal components.
"""

# ╔═╡ b6ed7820-20ce-11eb-0584-9130387e39ef
md"""
# Solution:
First, let's create a dummy dataset.
"""

# ╔═╡ 85bf8990-20d4-11eb-1a7b-e35d9e62f35a
gr();

# ╔═╡ f5d95832-20ea-11eb-2828-39ebb61c877f
md"""
### Utility functions
"""

# ╔═╡ 0024d7b0-20eb-11eb-2959-af3584eb7750
begin
	centralize(x::AbstractVector, m::AbstractVector) = (isempty(m) ? x : x - m)
	centralize(x::AbstractMatrix, m::AbstractVector) = (isempty(m) ? x : x .- m)

	decentralize(x::AbstractVector, m::AbstractVector) = (isempty(m) ? x : x + m)
	decentralize(x::AbstractMatrix, m::AbstractVector) = (isempty(m) ? x : x .+ m)

	transform(P, x, mv) = transpose(P) * centralize(x, mv)
	reconstruct(P, y, mv) = decentralize(P * y, mv)

	# extract k values/vectors
	function extract_kv(fac, ord, k)
		si = ord[1:k]
		vals = fac.values[si]
		vecs = fac.vectors[:, si]
		return (vals, vecs)
	end
end

# ╔═╡ 85976820-20d4-11eb-2afa-47a833fdccc4
md"""
## Input Data
For this example, we'll use **iris** dataset.
"""

# ╔═╡ dcce4b20-20c7-11eb-3cb1-852a46b75c31
iris = dataset("datasets", "iris") # DataFrame(rand(100, 10))

# ╔═╡ dcb4a8a2-20c7-11eb-3a1a-9fb620e84e57
size(iris)

# ╔═╡ dc975ca0-20c7-11eb-0a2b-b9e470c6e311
describe(iris)

# ╔═╡ 9241d120-20d2-11eb-0b30-25249995c00c
md"""
#### Let's create separate train and test datasets.
"""

# ╔═╡ b2a8ccc0-20d2-11eb-28eb-87dc018ae352
Xtrain = Array(iris[1:2:end,1:4])';

# ╔═╡ afb22c8e-20d3-11eb-339a-09f39705a02f
Ytrain = Array(iris[1:2:end,5]);

# ╔═╡ afe6aa10-20d3-11eb-03a4-89a46ade9feb
Xtest = Array(iris[2:2:end,1:4])';

# ╔═╡ b0004c90-20d3-11eb-2b38-452b177600d8
Ytest = Array(iris[2:2:end,5]);

# ╔═╡ b02fe810-20d3-11eb-21f4-f9939c117c12
md"""
Suppose `Xtrain` and `Xtest` are training and testing data matrix, with each observation in a column.

## Data Normalization
Now, we need to normalize the data.

"""

# ╔═╡ 399f2a20-20d4-11eb-1c3a-e76ac9707e29
mean_iris = vec(mean(Xtrain, dims=2))

# ╔═╡ c58d8a10-20e1-11eb-301a-eb715e5926d8
md"""
## Covariance Matrix Computation
Covariance matrix is an $N \times N$ symmetric matrix, where $N$ is the number of dimensions. This tells us about the relation between any two features, and the variation of data from the mean.
"""

# ╔═╡ 39b65ba2-20d4-11eb-11bd-7daa396810ac
C_iris = covm(Xtrain, mean_iris, 2);

# ╔═╡ 6493e090-20e3-11eb-0dec-0bdef8eb1c6a
DataFrame(C_iris)

# ╔═╡ c308e590-20e2-11eb-0ddb-055f8bdedb85
md"""
## Eigendecomposition of Covariance Matrix
We need to find the eigenvalues and eigenvectors of the covariance matrix in order to find the principal components of the data. These components are created in such a way that they are uncorrelated, and the first component contains the maximum amount of information possible, and the second contains the maximum remaining, and so on.
"""

# ╔═╡ 39e24da0-20d4-11eb-10e5-8320e876fb74
eg = eigen(Symmetric(C_iris));

# ╔═╡ b04fa510-20d3-11eb-0d40-21f2bf50e115
eg_values = eg.values

# ╔═╡ f010e210-20e5-11eb-1952-37e6ed84ea22
eg_vectors = eg.vectors

# ╔═╡ 35710110-20e5-11eb-0ad2-ef911c8d78bc
md"""
Let's sort the eigenvalues in reverse order, so the component which contains the most information is at the front.
"""

# ╔═╡ b1d8dc20-20de-11eb-1364-59fc5f488736
ord = sortperm(eg_values; rev=true)

# ╔═╡ 6f9d6dfe-20e5-11eb-2745-e95f930eaf88
md"""
Let's see how much information each component contains. For that, we need to find the total variance of the input, and then divide the principal component's variance with it. We'll plot the values for better visualization.
"""

# ╔═╡ b211ed80-20de-11eb-3abc-7f0b887915a5
tvar = sum(eg_values)

# ╔═╡ 9fefec80-20e6-11eb-3e16-e5f17e1d9560
eg_values[ord] ./ tvar * 100

# ╔═╡ b25a1a10-20de-11eb-3bcb-fbe70a5b974d
bar(eg_values[ord] ./ tvar * 100, title="Scree Plot")

# ╔═╡ b26da210-20de-11eb-026b-8fc4efce305c
md"""
By looking at the above graph, we can see that the first component, $PC 1$, contains more than $90 \%$ of the information, while $PC 2$ contains less than $5 \%$.

Even though many believe $PCA$ to be a dimensionality reduction algorithm, primarily it's a data transformation algorithm. It just makes our data amenable to data reduction, as shown above. Here, as most of the information is contained in the first couple of principal components, we can decide to keep only those componenets and discard the rest, resulting in a reduction in the dimensionality of the dataset.

### $l = 1$:
This means that we only take the first PC and discard the rest.
"""

# ╔═╡ d6429920-20cf-11eb-0bf1-8f74c17dc643
v, P = extract_kv(eg, ord, 1)

# ╔═╡ 72f60020-20eb-11eb-049a-d14c26e12ddb
md"""
This returns the eigen values, as well as the project matrix $P$. We can use $P$ to transform dataset. Transforming the `Xtest` using $P$ would result in:
"""

# ╔═╡ d628f6a0-20cf-11eb-11cc-01f1ca2b6f13
tr_data = transform(P, Xtest, mean_iris)

# ╔═╡ 6ae889c0-20eb-11eb-3ecb-4f13647511c3
md"""
which is a $1 \times 75$ array, instead of the original $4 \times 75$.

Let's try to reconstruct the original data from the transformed data using $P$.
"""

# ╔═╡ 1081c390-20f8-11eb-3e81-133df7444871
recon_data = reconstruct(P, tr_data, mean_iris);

# ╔═╡ 3313dc90-20f8-11eb-27eb-e9ea7e6a3805
md"""
Printing the first 5 observations from both the reconstructed data as well as the original data.
"""

# ╔═╡ d5e95590-20cf-11eb-0d5f-e3e5f2be14c0
DataFrame(recon_data)

# ╔═╡ d5b26710-20cf-11eb-0f4c-7f81055af3e3
DataFrame(Xtest[:, 1:5])

# ╔═╡ 4cd4fb30-20c3-11eb-0631-a9c8840a4cf9
md"""
As can be seen, the reconstructed data is fairly close to the original data, although not exactly the same because this is a lossy method. Even though we retained around $92\%$ information, we still lost some information by reducing the dimensions.

We can retain more information by increasing $l$, i.e. increasing the number of output dimensions.

## $1 <= l < n$
Instead of taking only $PC 1$, we will take the first three PCs.
"""

# ╔═╡ 740d6e20-20ec-11eb-1b12-1d614d3efb08
v3, P3 = extract_kv(eg, ord, 3)

# ╔═╡ 7466b1b2-20ec-11eb-1a71-e7061e6a7ad6
md"""
Again, let's first transform and reconstruct the data using `P3`.
"""

# ╔═╡ 747f1bb0-20ec-11eb-3cbf-a3a8d7a64563
tr3_data = transform(P3, Xtest, mean_iris);

# ╔═╡ 65ce7a10-20fc-11eb-1d02-f57de17db24e
size(tr3_data)

# ╔═╡ 7493b520-20ec-11eb-0e19-57cbd43d09af
recon3_data = reconstruct(P3, tr3_data, mean_iris);

# ╔═╡ 74a9ae20-20ec-11eb-1e5d-7984c0a399f3
DataFrame(recon3_data)

# ╔═╡ ff1b4e90-20f8-11eb-3924-2fa0c61677fd
DataFrame(Xtest[:, 1:5])

# ╔═╡ 053f9290-20f9-11eb-1cb6-6f5c7b26e279
md"""
We can see that using $l = 3$ gives a better approximation of the original data as compared to $l = 1$.

## Visualization
Let's visualize the first 3 principal components in a 3D plot.
"""

# ╔═╡ 9599eb10-20f9-11eb-044b-cd71da0c30a3
begin
	# group by labels, for color coding
	setosa = tr3_data[:,Ytest.=="setosa"];
	versicolor = tr3_data[:,Ytest.=="versicolor"];
	virginica = tr3_data[:,Ytest.=="virginica"];

	p = scatter(setosa[1,:],setosa[2,:],setosa[3,:],marker=:circle,linewidth=0)
	scatter!(versicolor[1,:],versicolor[2,:],versicolor[3,:],marker=:circle,linewidth=0)
	scatter!(virginica[1,:],virginica[2,:],virginica[3,:],marker=:circle,linewidth=0)
	plot!(p,xlabel="PC1",ylabel="PC2",zlabel="PC3")
end

# ╔═╡ 645934a0-20fb-11eb-1750-cf7ca28ab9e6
md"""
## Conclusion
In this notebook, we showed how we can use `PCA` to find principal components of some data, and then use those for dimensionality reduction without severly affecting and losing the information.
"""

# ╔═╡ 4d526890-20c3-11eb-082b-e912d7c9b3e2
md"""
# Resources

- [StatQuest: Principal Component Analysis (PCA), Step-by-Step](https://www.youtube.com/watch?v=FgakZw6K1QQ&t=458s&ab_channel=StatQuestwithJoshStarmer)
- [Data Analysis 6: Principal Component Analysis (PCA) - Computerphile](https://www.youtube.com/watch?v=TJdH6rPA-TI&ab_channel=Computerphile)
- [Making sense of principal component analysis, eigenvectors & eigenvalues](https://stats.stackexchange.com/questions/2691/making-sense-of-principal-component-analysis-eigenvectors-eigenvalues/140579)
- [A STEP BY STEP EXPLANATION OF PRINCIPAL COMPONENT ANALYSIS](https://builtin.com/data-science/step-step-explanation-principal-component-analysis)
- [Machine Learning — Singular Value Decomposition (SVD) & Principal Component Analysis (PCA)](https://jonathan-hui.medium.com/machine-learning-singular-value-decomposition-svd-principal-component-analysis-pca-1d45e885e491)
- [Relationship between SVD and PCA. How to use SVD to perform PCA?](https://stats.stackexchange.com/questions/134282/relationship-between-svd-and-pca-how-to-use-svd-to-perform-pca)
"""

# ╔═╡ Cell order:
# ╟─a33c14a0-20c2-11eb-0bf8-837173d9d91c
# ╟─cf5c9eb0-20c2-11eb-05ba-2f5dd23fdaf4
# ╟─dcfde69e-20c7-11eb-0108-dbd2f5cb0098
# ╟─b6ed7820-20ce-11eb-0584-9130387e39ef
# ╠═864115a0-20d4-11eb-16ff-71d198f9342e
# ╟─85bf8990-20d4-11eb-1a7b-e35d9e62f35a
# ╟─f5d95832-20ea-11eb-2828-39ebb61c877f
# ╠═0024d7b0-20eb-11eb-2959-af3584eb7750
# ╟─85976820-20d4-11eb-2afa-47a833fdccc4
# ╠═dcce4b20-20c7-11eb-3cb1-852a46b75c31
# ╠═dcb4a8a2-20c7-11eb-3a1a-9fb620e84e57
# ╠═dc975ca0-20c7-11eb-0a2b-b9e470c6e311
# ╟─9241d120-20d2-11eb-0b30-25249995c00c
# ╠═b2a8ccc0-20d2-11eb-28eb-87dc018ae352
# ╠═afb22c8e-20d3-11eb-339a-09f39705a02f
# ╠═afe6aa10-20d3-11eb-03a4-89a46ade9feb
# ╠═b0004c90-20d3-11eb-2b38-452b177600d8
# ╟─b02fe810-20d3-11eb-21f4-f9939c117c12
# ╠═399f2a20-20d4-11eb-1c3a-e76ac9707e29
# ╟─c58d8a10-20e1-11eb-301a-eb715e5926d8
# ╠═39b65ba2-20d4-11eb-11bd-7daa396810ac
# ╟─6493e090-20e3-11eb-0dec-0bdef8eb1c6a
# ╟─c308e590-20e2-11eb-0ddb-055f8bdedb85
# ╠═39e24da0-20d4-11eb-10e5-8320e876fb74
# ╠═b04fa510-20d3-11eb-0d40-21f2bf50e115
# ╠═f010e210-20e5-11eb-1952-37e6ed84ea22
# ╟─35710110-20e5-11eb-0ad2-ef911c8d78bc
# ╠═b1d8dc20-20de-11eb-1364-59fc5f488736
# ╟─6f9d6dfe-20e5-11eb-2745-e95f930eaf88
# ╠═b211ed80-20de-11eb-3abc-7f0b887915a5
# ╟─9fefec80-20e6-11eb-3e16-e5f17e1d9560
# ╠═b25a1a10-20de-11eb-3bcb-fbe70a5b974d
# ╟─b26da210-20de-11eb-026b-8fc4efce305c
# ╠═d6429920-20cf-11eb-0bf1-8f74c17dc643
# ╟─72f60020-20eb-11eb-049a-d14c26e12ddb
# ╠═d628f6a0-20cf-11eb-11cc-01f1ca2b6f13
# ╟─6ae889c0-20eb-11eb-3ecb-4f13647511c3
# ╠═1081c390-20f8-11eb-3e81-133df7444871
# ╟─3313dc90-20f8-11eb-27eb-e9ea7e6a3805
# ╠═d5e95590-20cf-11eb-0d5f-e3e5f2be14c0
# ╠═d5b26710-20cf-11eb-0f4c-7f81055af3e3
# ╟─4cd4fb30-20c3-11eb-0631-a9c8840a4cf9
# ╠═740d6e20-20ec-11eb-1b12-1d614d3efb08
# ╟─7466b1b2-20ec-11eb-1a71-e7061e6a7ad6
# ╠═747f1bb0-20ec-11eb-3cbf-a3a8d7a64563
# ╠═65ce7a10-20fc-11eb-1d02-f57de17db24e
# ╠═7493b520-20ec-11eb-0e19-57cbd43d09af
# ╠═74a9ae20-20ec-11eb-1e5d-7984c0a399f3
# ╠═ff1b4e90-20f8-11eb-3924-2fa0c61677fd
# ╟─053f9290-20f9-11eb-1cb6-6f5c7b26e279
# ╟─9599eb10-20f9-11eb-044b-cd71da0c30a3
# ╟─645934a0-20fb-11eb-1750-cf7ca28ab9e6
# ╟─4d526890-20c3-11eb-082b-e912d7c9b3e2


function axis_angle_to_quaternion(x)
    @assert length(x) == 3
    θ = norm(x)
    if θ > 0.0
        r = x ./ θ
        q = Quaternion(cos(0.5 * θ), sin(0.5 * θ) * r)
    else
        q = Quaternion(1.0, 0.0, 0.0, 0.0)
    end
    return q
end

function daxis_angle_to_quaterniondx(x)
    θ = norm(x)
    if θ > 0.0
        r = x ./ θ

        ∂qw∂x = -0.5 * sin(0.5 * θ) * transpose(x) ./ θ
        ∂qx∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[1] + [sin(0.5 * θ) / θ 0.0 0.0] - sin(0.5 * θ) * x[1] / θ^2 * transpose(x) ./ θ
        ∂qy∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[2] + [0.0 sin(0.5 * θ) / θ 0.0] - sin(0.5 * θ) * x[2] / θ^2 * transpose(x) ./ θ
        ∂qz∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[3] + [0.0 0.0 sin(0.5 * θ) / θ] - sin(0.5 * θ) * x[3] / θ^2 * transpose(x) ./ θ

        return [
                ∂qw∂x;
                ∂qx∂x;
                ∂qy∂x;
                ∂qz∂x;
               ]
    else
        return [
                    0.0  0.0  0.0;
                    0.5  0.0  0.0;
                    0.0  0.5  0.0;
                    0.0  0.0  0.5;
                ]
    end
end

function axes_pair_to_quaternion(n1, n2)
	if norm(n1 + n2, Inf) < 1e-5
		n2 = n2 + 1e-5ones(3)
	end

	reg(x) = 1e-20 * (x == 0) + x
	# provides the quaternion that rotates n1 into n2, assuming n1 and n2 are normalized
	n1 ./= reg(norm(n1))
	n2 ./= reg(norm(n2))
	n3 = skew(n1)*n2
	cθ = n1' * n2 # cosine
	sθ = norm(n3) # sine
	axis = n3 ./ reg(sθ)
	tanθhalf = sθ / reg(1 + cθ)
	q = [1; tanθhalf * axis]
	q /= norm(q)
	return Quaternion(q...)
end


# p30.jl - spectral integration, ODE style (compare p12.jl)

# Computation: various values of N, four functions:
Nmax = 50; E = zeros(4,Nmax); clf();
for N = 1:Nmax
    i = 1:N; (D,x) = cheb(N);
    x = x[i]; Di = inv(D[i,i]); w = Di[1,:];
    f = @. abs(x)^3;     E[1,N] = abs(dot(w,f) - .5);
    f = @. exp(-x^(-2)); E[2,N] = abs(dot(w,f) - 2*(exp(-1)+sqrt(pi)*(erf(1)-1)));
    f = @. 1/(1+x^2);    E[3,N] = abs(dot(w,f) - pi/2);
    f = x.^10;           E[4,N] = abs(dot(w,f) - 2/11);
end

# Plot results:
labels = [L"|x|^3",L"\exp(-x^2)",L"1/(1+x^2)",L"x^{10}"];
for iplot = 1:4
    subplot(2,2,iplot)
    semilogy(E[iplot,:]+1e-100,".-",markersize=10);
    axis([0,Nmax,1e-18,1e3]); grid(true);
    xticks(0:10:Nmax); yticks((10.0).^(-15:5:0));
    ylabel("error"); text(32,.004,labels[iplot]);
end


# Copyright (c) 2021 Idiap Research Institute, http://www.idiap.ch/
#  Niccolò Antonello <nantonel@idiap.ch>

export BoolWeight

"""
`BoolWeight(x::Bool)`

| Set           |     ``\\oplus``      |  ``\\otimes``  | ``\\bar{0}`` | ``\\bar{1}`` |
|:-------------:|:--------------------:|:--------------:|:------------:|:------------:|
| ``\\{0,1\\}`` |       ``\\lor``      |  ``\\land``    |   ``0``      |    ``1``     |
"""
struct BoolWeight <: Semiring
  x::Bool
end

zero(::Type{BoolWeight}) = BoolWeight(false)
one(::Type{BoolWeight}) = BoolWeight(true)

*(a::BoolWeight, b::BoolWeight) = BoolWeight(a.x && b.x)
+(a::BoolWeight, b::BoolWeight) = BoolWeight(a.x || b.x)

#properties
isidempotent(::Type{W}) where {W <: BoolWeight} = true
iscommulative(::Type{W}) where {W <: BoolWeight} = true
isleft(::Type{W}) where {W <: BoolWeight}= true
isright(::Type{W}) where {W <: BoolWeight}= true
ispath(::Type{W}) where {W <: BoolWeight}= true
iscomplete(::Type{W}) where {W <: BoolWeight}= true


using Distributions
using DataStructures

f(x) = x^2 - 5 * x + 6

function filter_best_solutions(solutions; pa=0.25)
    new_best_solutions=SortedDict(Dict{Float64, Float64}())
    println("***FILTER RCVD : ", length(solutions))
    println("Filterting : ", floor((length(solutions) - length(solutions)*pa)))
    let
        i = 1
        for (key, value) in solutions
            new_best_solutions[key] = value
            i = i + 1
            if i >= (length(solutions) - length(solutions)*pa)
                return merge(new_best_solutions, initialize_population(floor((length(solutions) - length(solutions)*pa))))
            end
        end
    end
end

function get_best_solution(soln)
    best = soln[1]
    for s in soln
        if f(s) < f(best)
            best = s
        end
    end
    return best
end

heaviside(x::AbstractFloat) = ifelse(x < 0, zero(x), ifelse(x > 0, one(x), oftype(x,0.5)))

function generate_new_population(population; pa=0.25)
    new_population=SortedDict(Dict{Float64, Float64}())
    for (key, value) in population
        new_value = value + heaviside(pa - rand()) * (rand(population)[2] - rand(population)[2])
        new_population[f(new_value)] = new_value
    end
    return new_population
end

function initialize_population(total)
    population = SortedDict(Dict{Float64,Float64}())
    for i=1:total
        soln = rand(Float64)
        population[f(soln)] = soln
    end
    return population
end

function cuckoo_search(f;total_cuckoos=25,pa = 0.25,α=0.01, λ=0.5, MaxGenerations = 15)
    cuckoos = initialize_population(total_cuckoos)
    println("Initial population of Cuckoos: ", length(cuckoos))
    L = Levy(λ)
    best = nothing
    t=1
    while t < MaxGenerations
        r_cuckoo = rand(cuckoos)
        new_best = r_cuckoo[2] + α * rand(L)
        if (f(new_best) < r_cuckoo[2])
            best = new_best
        end
        #
        cuckoos = filter_best_solutions(cuckoos, pa=pa)
        println("After FILTER Total Cuckoos: ", length(cuckoos))
        cuckoos = generate_new_population(cuckoos, pa=pa)
        best = first(cuckoos)[2]
        println("Current Best: ", best)
        println("Total Cuckoos: ", length(cuckoos))
        t = t +1
    end
    return best
end

global_minimum=cuckoo_search(f)

println("Final Global Minimum: ", global_minimum)


using Symata
using Test

@sym begin
  LagrangePolynomial(xi_, yi_) := Module([N, sum, num, den],
    begin
      N = Length(xi)
      sum = 0
      For( i=1, i <= N, Increment(i),
        begin
          num = 1.0
          den = 1.0
          For( j=1, j <= N, Increment(j),
            begin
              num = If(j != i, num = num * (x-xi[j]), num)
              den = If(j != i, den = den * (xi[i]-xi[j]), den)
            end
          )
          sum = sum + yi[i] * num/den
        end
      ),
      Return(Simplify(sum))
    end
  )
end

@sym begin
  CollocationWeightedResidualMethod(xi_, yi_, doprint_) := Module([Y, R],
    begin
      Y(x_) := LagrangePolynomial(xi, yi)
      If(doprint, Println("\nY(x) = ", Y(x), "\n"))
      #
      # Can be formulated as ytile(x) = F(x) + C1(a) * Ψ(x)
      #
      # F(x_) := 2*x^2 - x
      # C(a_) := -4a
      # Ψ(x_) := x^2 - x
      #
      #Y(x_) := F(x) + C(a)*Ψ(x)
      #
      R(x_) = Simplify(D(Y(x), x, 2) - 3*x - 4*Y(x))
      R(x_) = Simplify(R(x) ./ (a => -C1/4))
      If(doprint, Println("R(x) = ", R(x), "\n"))
      Return(R(x))
    end
  )
end

function wrm(x1,y1, doprint=true)
    setsymata(:xin, List(x1...))
    setsymata(:yin, List(y1...))
    setsymata(:doprint, doprint ? true : false)
    #symtranseval(Meta.parse("Compile(Evaluate(CollocationWeightedResidualMethod(xin,yin,doprint)))"))
    symparseeval("Compile(Evaluate(CollocationWeightedResidualMethod(xin,yin,doprint)))")
end

println("\nExample 7.13: y'' = 3x + 4y, y(0)=0, y(1)=1")
println("Residual for Weighted Residual Method using 1 point Lagragian Polynomial\n")

#
# A consequence of the Julia function based approach is that Y(x) and R(x) are local
# to the Symata function and are not available for examination, e.g. @sym R(x)
#

lp_eq = @sym ToString(LagrangePolynomial([0.0,0.5,1.0], [0.0,a,1.0]))
println(lp_eq)
@test lp_eq == "x*(-1.0 + 2.0*x + (-4.0)*a*(-1.0 + x))"
wrm_eq = @sym ToString(CollocationWeightedResidualMethod([0.0,0.5,1.0], [0.0,a,1.0], false))
println(wrm_eq)
@test wrm_eq == "4.0 + 2.0*C1 - 3x - 4x*(-1.0 + 2.0*x + C1*(-1.0 + x))"

#
# Another consideration is that below yt is yt(C1::Any, x::Any)
#

yt = wrm([0.0,0.5,1.0], [0.0,:a,1.0], true)

println("( Example 7.13 gives: R = -4x^2*(2 + C1) + x*(1 + 4C1) + 2*(2 + C1) )", "\n")

"
```
The function ytilde(x, C1) contains the 1-point Weighted Residual Method
polynomial with parameter C1.
```
Arguments to ytilde(x, C1):

x::Float64          : x value\n
C1::Float64         : Weighted Residual Method constant.

C1 can be estimated by e.g. collocation, subdomain, least squares or Galerkin.
See examples 7.15 through 7.18 and Fig.7.14.jl
"
ytilde(x, C1) = yt(C1, x)
println("ytilde(1.0, 0.0) = $(ytilde(1.0, 0.0))")
@test ytilde(1.0, 0.0) == -3.0

println("ytilde(0.0, 1.0) = $(ytilde(0.0, 1.0))")
@test ytilde(0.0, 1.0) == 6.0

#
# Tests with @symExpr
#

setsymata(:x1, [0.0,0.5,1.0])
wrm_1pt_col(x, C1) = @symExpr CollocationWeightedResidualMethod(x1, [0.0,a,1.0], false)

#
# The order of the arguments can be chosen. This is nice.
#

println("wrm_1pt_col(1.0, 0.0) = $(wrm_1pt_col(1.0, 0.0))")
@test wrm_1pt_col(1.0, 0.0) == ytilde(1.0, 0.0)

#
# Like @sym, I think @symExpr is not intended to return an expression from a Julia function.
#

function wrm_1(xin, xinval, C1inval)
  setsymata(:x1, xin)
  f(x, C1) = @symExpr CollocationWeightedResidualMethod(x1, [0.0,a,1.0], false)
  f(xinval, C1inval)
end

@test wrm_1([0.0,0.5,1.0], 1.0, 0.0) == ytilde(1.0, 0.0)


# ---
# title: 62. Unique Paths
# id: problem62
# author: AquaIndigo
# date: 2020-12-09
# difficulty: Medium
# categories: Array, Dynamic Programming
# link: <https://leetcode.com/problems/unique-paths/description/>
# hidden: true
# ---
#
# A robot is located at the top-left corner of a `m x n` grid (marked 'Start' in
# the diagram below).
#
# The robot can only move either down or right at any point in time. The robot
# is trying to reach the bottom-right corner of the grid (marked 'Finish' in the
# diagram below).
#
# How many possible unique paths are there?
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png)
#
#
#
#     Input: m = 3, n = 7
#     Output: 28
#
#
# **Example 2:**
#
#
#
#     Input: m = 3, n = 2
#     Output: 3
#     Explanation:
#     From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
#     1. Right -> Down -> Down
#     2. Down -> Down -> Right
#     3. Down -> Right -> Down
#
#
# **Example 3:**
#
#
#
#     Input: m = 7, n = 3
#     Output: 28
#
#
# **Example 4:**
#
#
#
#     Input: m = 3, n = 3
#     Output: 6
#
#
#
#
# **Constraints:**
#
#   * `1 <= m, n <= 100`
#   * It's guaranteed that the answer will be less than or equal to `2 * 109`.
#
#
## @lc code=start
using LeetCode

unique_paths(m::Int, n::Int) = binomial(m + n - 2, n - 1)
## @lc code=end


"""
    create_upper_tri(vec,V)

Create an upper triangluar matrix from a vector of the form [12, ... 1V,23,...(V-1)V]
to the form [0 12 13  ... 1V]
            [0 0  23  ... 2V]
            [...............]
            [0 0  0...(V-1)V]
# Arguments
- `vec`: vector containing values to put into the upper triangluar matrix
- `V`  : dimension of output matrix

# Returns
Upper triangluar matrix containing values of `vec`
"""
function create_upper_tri(vec::Array{T,1},V::Int64) where T <: Union{Int64,Float64}
    mat = zeros(T,V,V)
    vec2 = deepcopy(vec)
    for k = 1:V
        for l = k+1:V
            mat[k,l] = popfirst!(vec2)
        end
    end
    return mat
end

"""
    upper_triangle(matrix)

Return the upper triangle (without the diagonal) of the matrix as a vector

# Arguments
- `matrix`: matrix of which to capture the upper triangle

# Returns
Vector of upper triangluar section of `matrix`
"""
function upper_triangle(matrix::Array{T,2}) where T <: Union{Int64,Float64}
    k = 1
    ret = zeros(T,convert(Int64, round(size(matrix,1)*(size(matrix,2) - 1)/2)))
    for i in 1:size(matrix,1)
        for j in (i+1):size(matrix,2)
            ret[k] = matrix[i,j]
            k = k + 1
        end
    end
    return ret
end


using RadauBVP, Plots
pyplot()

ProjDir = dirname(@__FILE__)
cd(ProjDir) do

  if !isdefined(Main, :bc!)
    function bc!(y0, yn, r)
      r[1] = y0[1] - 0.0
      r[2] = yn[2] - (-1.0)
    end

    function dbc!(y0, yn, jac)
      jac[:,:] = 0.0
      jac[1,1] = 1.0
      jac[2,4] = 1.0
    end

    function odeeq!(t, y, dydt)
      dydt[1] = y[2]
      dydt[2] = -y[1]
    end

    function dodeeq!(t, y, jac)
      jac[1,1] = 0.0
      jac[1,2] = 1.0
      jac[2,1] = -1.0
      jac[2,2] = 0.0
    end
  end

  nt = 5
  const y0 = zeros(Float64, (2, 3*(nt-1)+1))

  for n=1:3*(nt-1)+1
    y0[:, n] = [0.0, 1.0]
  end

  @time (conv, ttot, ytot, result) =
    RadauBVP.radau3(bc!, odeeq!, dbc!, dodeeq!, y0, 0.0, convert(Float64, π))

  result |> display

  yres = ytot[1,:]
  vres = ytot[2,:]

  let t = linspace(0.0, π, 100)

    global possol = sin.(t)
    global vsol = cos.(t)
    global ysol = Array(Float64, 2*100)

    ysol[1:2:end-1] = possol
    ysol[2:2:end] = vsol
  end

  p1 = plot(
  linspace(0.0, π, 100),
  possol,
  title = "Coordinate (n = $nt)",
  label = "Exact")
  plot!(ttot, yres, label = "Numerical", linetype = :scatter)

  p2 = plot(
  linspace(0.0, π, 100),
  vsol,
  title = "Velocity (n = $nt)",
  label = "Exact")
  plot!(ttot, vres, label = "Numerical", linetype = :scatter)

  plot(p1,p2)
  savefig("radau.png")
  plot(p1,p2)

end


"LU Decomposition"
module LUDecomposition

using LinearAlgebra: dot

function get_n(A::Matrix)::Int
    (n_r, n_c) = size(A)
    @assert n_r == n_c "Input matrix is not square"
    n_r
end

"Row permutation"
struct Permutation
    indices::Vector{Int}
    n_pivots::Int
end

raw"""
LU factorization with partial pivoting.

`A` is changed to $(L - E) + U$ s.t. $P A = L U$.
"""
function lu_decompose!(A::Matrix{<:AbstractFloat})::Union{Permutation, Nothing}
    n = get_n(A)
    p = collect(1:n)
    n_pivots = n  # for computing the determinant

    @inbounds for c in 1:n
        # Using ArrayView here doesn't help.
        # See https://github.com/JuliaLang/julia/issues/19198#issuecomment-257986870.
        (max_value, c_max) = findmax(abs.(A[c:n, c]))
        if max_value < eps()
            return nothing
        end
        c_max += c - 1

        if c_max != c
            @views A[[c, c_max], :] .= A[[c_max, c], :]
            p[c], p[c_max] = p[c_max], p[c]
            n_pivots += 1
        end

        # `A[r, c]` is assigned to 0 by `A[r, c] -= A[c, c] * (A[r, c] / A[c, c])`, so the element
        # in `L` is `(A[r, c] / A[c, c])`.
        rg = (c + 1):n;
        @views A[rg, c] /= A[c, c]
        @. @views A[rg, rg] -= A[rg, c] * A[c, rg]'
    end

    Permutation(p, n_pivots)
end

raw"""
Solves $A x = b$.

**Precondition**: `LU` is obtained by appling `lu_decompose!` on `A`.
"""
function lu_solve(LU::Matrix{T}, perm::Permutation, b::Vector{T})::Vector{T} where T<:AbstractFloat
    n = get_n(LU)

    # transforms `b`, stored in `x`
    x = b[perm.indices]
    for r in 1:n
        @views x[r] -= dot(LU[r, 1:(r - 1)], x[1:(r - 1)])
    end

    # computes `x`
    for r in n:-1:1
        p = @views dot(LU[r, (r + 1):n], x[(r + 1):n])
        x[r] = (x[r] - p) / LU[r, r]
    end

    x
end

function lu_det(LU::Matrix{T}, n_pivots::Int)::T where T<:AbstractFloat
    n = get_n(LU)
    det = foldl(*, LU[i, i] for i in 1:n)
    if iseven(n - n_pivots)
        det
    else
        -det
    end
end

#===== Unit Tests =====#
using Test

using LinearAlgebra: Diagonal, I, LowerTriangular, UpperTriangular
using LinearAlgebra: det  # for reference implementation
using Random: seed!

@testset "LU decomposition" begin
    n = 32
    seed!()
    A = rand(n, n)
    b = rand(n)

    LU = copy(A)
    perm = lu_decompose!(LU)
    if perm != nothing
        L = LowerTriangular(LU) - Diagonal(LU) + I
        U = UpperTriangular(LU)
        @test L * U ≈ A[perm.indices[:], :]

        @test lu_det(LU, perm.n_pivots) ≈ det(A)

        x = lu_solve(LU, perm, b)
        @test A * x ≈ b
    end
end

end # module


# Laplacian Eigenmaps
# -------------------
# Laplacian Eigenmaps for Dimensionality Reduction and Data Representation,
# M. Belkin, P. Niyogi, Neural Computation, June 2003; 15 (6):1373-1396

"""
    LEM{NN <: AbstractNearestNeighbors, T <: Real} <: AbstractDimensionalityReduction

The `LEM` type represents a Laplacian eigenmaps model constructed for `T` type data with a help of the `NN` nearest neighbor algorithm.
"""
struct LEM{NN <: AbstractNearestNeighbors, T <: Real} <: AbstractDimensionalityReduction
    λ::AbstractVector{T}
    ɛ::T
    proj::Projection{T}
    nearestneighbors::NN
    component::AbstractVector{Int}
end

## properties
outdim(R::LEM) = size(R.proj, 1)
eigvals(R::LEM) = R.λ
neighbors(R::LEM) = R.nearestneighbors.k
vertices(R::LEM) = R.component

## show
summary(io::IO, R::LEM) = print(io, "Laplacian Eigenmaps(outdim = $(outdim(R)), neighbors = $(neighbors(R)), ɛ = $(R.ɛ))")

## interface functions
"""
    fit(LEM, data; k=12, maxoutdim=2, ɛ=1.0, nntype=BruteForce)

Fit a Laplacian eigenmaps model to `data`.

# Arguments
* `data`: a matrix of observations. Each column of `data` is an observation.

# Keyword arguments
* `k`: a number of nearest neighbors for construction of local subspace representation
* `maxoutdim`: a dimension of the reduced space.
* `nntype`: a nearest neighbor construction class (derived from `AbstractNearestNeighbors`)
* `ɛ`: a Gaussian kernel variance (the scale parameter)

# Examples
```julia
M = fit(LEM, rand(3,100)) # construct Laplacian eigenmaps model
R = transform(M)          # perform dimensionality reduction
```
"""
function fit(::Type{LEM}, X::AbstractMatrix{T};
        k::Int=12, maxoutdim::Int=2, ɛ::Real=1.0, nntype=BruteForce) where {T<:Real}
    # Construct NN graph
    NN = fit(nntype, X, k)
    D, E = knn(NN, X)
    G, C = largest_component(SimpleWeightedGraph(adjmat(D,E)))

    # Compute weights
    W = weights(G)
    W .^= 2
    W ./= maximum(W)

    W[W .> eps(T)] = exp.(-W[W .> eps(T)] ./ convert(T,ɛ))
    D = spdiagm(0=>vec(sum(W, dims=2)))
    L = D - W

    λ, V = decompose(L, D, maxoutdim)
    return LEM{nntype, T}(λ, ɛ, transpose(V), NN, C)
end

"""
    transform(R::LEM)

Transforms the data fitted to the Laplacian eigenmaps model `R` into a reduced space representation.
"""
transform(R::LEM) = R.proj


using Makie
using GeometryBasics

outtrig = Triangle(
    Point(0., 0.),
    Point(1., 0.),
    Point(0.5, 1.)
)

intrig = Triangle(
    Point(rand(2)...),
    Point(rand(2)...),
    Point(rand(2)...)
)

fig = Figure()
ax1 = Axis(fig[1, 1])
ax2 = Axis(fig[1, 2])
mesh!(ax1, outtrig.points)
mesh!(ax2, intrig.points)
using GeometryBasics
using Makie
using FractalTools

using Random
Random.seed!(0)

# Define the triangle
trig = Triangle(
    Point(-1., -1.),
    Point(1., -1.),
    Point(0., 2)
)

# Get valid points in the domain
pnts2d = disperse(trig, 10)

# Define function
f(x, y) = x^2 + y^2

# Evaluate function
pnts3d = [Point(pnt[1], pnt[2], f(pnt[1], pnt[2])) for pnt in pnts2d]  # Interpolation data

# Check transformations
_, msh3 = triangulate(pnts3d)
outtrig = findouttriangle(pnts3d)
outtrigpnts2d = project(outtrig.points)

i = Node(39)
ttrig = @lift(msh3[$i])

ttrigpnts2d = lift(ttrig) do ttrig
    project(ttrig.points)
end

mtrig = lift(ttrig) do ttrig
    tfm = gettransform(outtrig, ttrig, 0.01)
    L(x) = tfm.A[1:2, 1:2] * x + tfm.b[1:2]
    Triangle(map(pnt -> Point(L(pnt)...), outtrigpnts2d)...)
end

fig = Figure()
ls = LScene(fig[1, 1])
buttongrid = fig[1, 2] =  GridLayout(tellheight=false)
button1 = buttongrid[1, 1] = Button(fig, label = "Inc")
button2 = buttongrid[2, 1] = Button(fig, label = "Dec")
label = buttongrid[3, 1] = Label(fig, "Tri idx:$(string(i[]))")
mesh!(ls, outtrig)
wireframe!(outtrigpnts2d, color=:black, linewidth=3)
mesh!(ttrig, color=:blue)
wireframe!(ttrigpnts2d, color=:blue, linewidth=3)
mesh!(mtrig, color=:green)

on(button1.clicks) do n
    i[] += 1
    label.text[] = "Tri idx: " * string(i[])
end

on(button2.clicks) do n
    i[] -= 1
    label.text[] = "Tri idx: " * string(i[])
end

display(fig)

# # Interpolate the data
# itp = interpolate(pnts3d, maxiters=4)

# # Evaluat the itp
# tpnt = pnts2d
# # tpnt = [getpoint(trig) for i in 1 : 500]
# val = map(pnt -> itp(pnt...), tpnt)
# rval =  map(pnt -> f(pnt...), tpnt)
# relerr = abs.(val - rval) ./ abs.(rval) * 100

# @show findall(isnan, val)
# @show sum(relerr .≥ 1)

# # Plots
# tess, mesh3 = triangulate(pnts3d)
# mesh2 = GeometryBasics.Mesh(project(coordinates(mesh3)), faces(mesh3))
# fig, ax, plt = scatter(pnts3d, color=:red, markersize=20)
# wireframe!(trig.points, linewidth=3)
# wireframe!(mesh2)
# scatter!(pnts2d, markersize=8, color=:black)
# scatter!(tpnt, markersize=8, color=:green)
# # mesh!(mesh3, color=first.(mesh3.position))
# wireframe!(mesh3, color=:black)

# # Test point
# tpnt = getpoint(trig)
# idx = tess.find_simplex(tpnt)[1] + 1
# scatter!(tpnt, markersize=30, color=:orange)
# mesh!(mesh2[idx])

# tpnt2 = (trig.points[1] + trig.points[2]) / 2
# idx = tess.find_simplex(tpnt2)[1] + 1
# scatter!(tpnt2, markersize=30, color=:blue)
# mesh!(mesh2[idx], color=:green)

# tpnt3 = pnts2d[40]
# idx = tess.find_simplex(tpnt3)[1] + 1
# scatter!(tpnt3, markersize=30, color=:green)
# mesh!(mesh2[idx], color=:black)

# tpnt4 = pnts2d[39]
# scatter!(tpnt4, markersize=30, color=:blue)

# tpnt5 = (pnts2d[40] + pnts2d[39]) / 2
# scatter!(tpnt5, markersize=30, color=:blue)
# idx = tess.find_simplex(tpnt5)[1] + 1
# mesh!(mesh2[idx])

# display(fig)