using HaloMF
using HCubature
using Plots
Δm = 200
println("Δm = ", Δm)
A(Δm) = 1 + 0.24log10(Δm) * exp(-(4 / log10(Δm))^4)
a(Δm) = 0.44log10(Δm) - 0.88
C(Δm) = 0.019 + 0.107log10(Δm) + 0.19 * exp(-(4 / log10(Δm))^4)
B, b, c = 0.183, 1.5, 2.4
δc = 1.686
bν(lnν) =
   1 - A(Δm) * exp(0.5 * a(Δm) * lnν) / (exp(0.5 * a(Δm) * lnν) + δc^a(Δm)) +
   B * exp(0.5 * b * lnν) +
   C(Δm) * exp(0.5 * c * lnν)
f0(x) = tinker10MF(x, 0, Δm)
norm1, err = hquadrature(f0, -50, 5)
println("Normalisation(MF) = ", norm1)
f1(x) = tinker10MF(x, 0, Δm) * bν(x)
norm2, err = hquadrature(f1, -50, 5)
println("Normalisation(Bias) = ", norm2)
# %% Plot b(ν)
x = -2:0.05:2
Δm = 200
plot(
   log10.(exp.(x)),
   log10.(bν.(x)),
   xlim = [-0.5, 0.6],
   ylim = [-0.5, 1.2],
   xlabel = "log10(ν)",
   ylabel = "log10(b)",
   ls = :dash,
)
Δm = 1600
plot!(log10.(exp.(x)), log10.(bν.(x)))


negbinmodel_stan = "data {
  int<lower=1> N;
  int<lower=0> y[N];
}
parameters {
  real<lower=0> alpha;
  real<lower=0> beta;
}
model {
  alpha ~ cauchy(0,10);
  beta ~ cauchy(0,10);
  for (i in 1:N)
    y[i] ~ neg_binomial(alpha, beta);
}
"

@model negbinmodel(y) = begin
  α ~ Cauchy(0,10)
  β ~ Cauchy(0,10)
  for i = 1:length(y)
    y[i] ~ NegativeBinomial(α, β)  # α > 0, 0 < β < 1
  end
end


using PyPlot
using Printf

pi5(x, y) = x > y ? (x-y)^5/120 : zero(x)
w1 = 1/6
w2 = 5/6
a = 1/sqrt(5)

K(y) = (1-y)^6/720 - ( w2*pi5(-a,y) + w2*pi5(a,y) + w1*pi5(1,y) )

figure(1)
y = range(-1, stop=1, length=201)
plot(y, K.(y))
grid(true)
xlabel(L"$y$")
ylabel(L"$K(y)$")
savefig("quadrature_kernel.pdf")

E2(n) = 2//(2n+1) - (1+5//5^n)/3
for n = 0:3
    err = E2(n)
    @printf("%6d  %0d / %0d\n", n, err.num, err.den)
end


function ylmKCoefficient(l::Integer, m::Integer)
    k = BigInt(4) # BigInt avoids overflow
    for i = l-m+1:l+m
        k *= i
    end

    return sqrt((2.0 * l + 1) / k / π)
end

function ylmCosSinPolynomial(m::Integer, x::Variable, y::Variable)
    sum = 0.0 * (x + y)
    for j = 0:m÷2
        sum += (-1)^j * binomial(m, 2 * j) * (y^(2 * j)) * (x^(m - 2 * j))
    end

    return sum
end

function ylmSinSinPolynomial(m::Integer, x::Variable, y::Variable)
    sum = 0.0 * (x + y)
    for j = 0:(m-1)÷2
        sum += ((-1)^j) * binomial(m, 2 * j + 1) * (y^(2 * j + 1)) * (x^(m - 2 * j - 1))
    end

    return sum
end

"""
    ylm(l, m, x, y, z)

Calculation of the spherical harmonic for a given order (l,m) in Cartesian coordinates

- @arg l: degree of spherical harmonics
- @arg m: order of spherical harmonics
- @arg x, y, z: Cartesian coordinates
- @return: spherical harmonic polynomial

"""
function ylm(l::Integer, m::Integer, x::Variable, y::Variable, z::Variable)
    if abs(m) > l
        throw("-l <= m <= l expected, but m = $m and l = $l.")
    end

    p = 1.0 * (z^2 - 1)^l + 0.0 * (x + y)

    for i = 1:l+abs(m)
        c = i <= l ? 1 / (2 * i) : 1.0
        p = c * differentiate(p, z)
    end

    if m > 0
        return sqrt(2) * ylmKCoefficient(l, m) * ylmCosSinPolynomial(m, x, y) * p
    elseif m < 0
        return sqrt(2) * ylmKCoefficient(l, abs(m)) * ylmSinSinPolynomial(abs(m), x, y) * p
    else
        return ylmKCoefficient(l, 0) * p
    end
end

"""
    rlylm(l, m, x, y, z)

Calculate `r^l * Ylm(x,y,z)``
"""
function rlylm(l::Integer, m::Integer, x::Variable, y::Variable, z::Variable)
    p = ylm(l, m, x, y, z)
    tout = []

    for t in terms(p)
        deg = degree(monomial(t))
        degR = l - deg
        push!(tout, (x^2 + y^2 + z^2)^Int(degR / 2) * t)
    end

    return polynomial(tout)
end

"""
    eval_spherharmonic(points, L)

Evaluate spherical harmonics basis at given quadrature points
"""
function eval_spherharmonic(points::AbstractMatrix{T}, L) where {T<:Real}
    ne = (L + 1)^2
    nq = size(points, 1)
    m = zeros(ne, nq)

    @polyvar x y z
    if L == 0
        spe = [ylm(0, 0, x, y, z)]
    elseif L == 1
        spe = [
            ylm(0, 0, x, y, z),
            ylm(1, -1, x, y, z),
            ylm(1, 0, x, y, z),
            ylm(1, 1, x, y, z),
        ]
    elseif L == 2
        spe = [
            ylm(0, 0, x, y, z),
            ylm(1, -1, x, y, z),
            ylm(1, 0, x, y, z),
            ylm(1, 1, x, y, z),
            ylm(2, -2, x, y, z),
            ylm(2, -1, x, y, z),
            ylm(2, 0, x, y, z),
            ylm(2, 1, x, y, z),
            ylm(2, 2, x, y, z),
        ]
    end

    for j in axes(m, 2), i in axes(m, 1)
        m[i, j] = spe[i](x => points[j, 1], y => points[j, 2], z => points[j, 3])
    end

    return m
end


# function to integrate a unit circle to find pi via monte_carlo
function monte_carlo(n::Int64)

    pi_count = 0
    for i = 1:n
        point_x = rand()
        point_y = rand()

        if (point_x^2 + point_y^2 < 1)
            pi_count += 1
        end
    end

    pi_estimate = 4*pi_count/n
    println(pi - pi_estimate)
end

monte_carlo(10000000)


module Laplace

using StaticArrays
using LinearAlgebra
using ..AeroMDAO: Point2D, Point3D

abstract type AbstractLaplace end

# Performs velocity and potential computations for an object on a grid
grid_data(object :: AbstractLaplace, xs) = velocity(object, xs), potential(object, xs)
velocity(object :: AbstractLaplace, xs) = map(x -> velocity(object, x...), xs)
potential(object :: AbstractLaplace, xs) = map(x -> potential(object, x...), xs)

# Performs velocity and potential calculations on a grid
function grid_data(objects :: Vector{<: AbstractLaplace}, xs)
    vels = foldl((v1, v2) -> [ u .+ v for (u, v) ∈ zip(v1, v2) ], velocity(object, xs) for object ∈ objects)
    pots = foldl((v1, v2) -> v1 + v2, potential(object, xs) for object ∈ objects)

    vels, pots
end

## 2D singularities
#============================================#

struct Singularity2D{T <: Real} <: AbstractLaplace
    strength :: T
    r 		 :: Point2D{T}
end

# Getters
strength(s :: Singularity2D) = s.strength
x(s :: Singularity2D) 		 = s.r.x
y(s :: Singularity2D) 		 = s.r.y

source_velocity(src :: Singularity2D, x, y) = SVector(strength(src) / (2π) * (x - y(src)) / ((x - y(src))^2 + (y - x(src))^2), str / (2π) * (y - x(src)) / ((x - y(src))^2 + (y - x(src))^2))
source_potential(src :: Singularity2D, x, y) = strength(src) / (4π) * log((x - y(src))^2 + (y - x(src))^2)
source_stream(src :: Singularity2D, x, y) = strength(src) / (2π) * atan(y - x(src), x - y(src))

doublet_velocity(dub :: Singularity2D, x, y) = SVector(strength(dub) / (2π) * ((x - y(dub))^2 - (y - x(dub))^2) / ((x - y(dub))^2 + (y - x(dub))^2)^2, - strength(dub) / (2π) * 2 * (x - y(dub)) * (y - x(dub)) / ((x - y(dub))^2 + (y - x(dub))^2)^2)
doublet_potential(dub :: Singularity2D, x, y) = -strength(dub) / (2π) * (y - x(dub)) / ((x - y(dub))^2 + (y - x(dub))^2)
doublet_stream(dub :: Singularity2D, x, y) = -strength(dub) / (2π) * (y - x(dub)) / ((x - y(dub))^2 + (y - x(dub))^2)

vortex_velocity(vor :: Singularity2D, x, y) = SVector(-strength(vor) / (2π) * (y - x(vor)) / ((x - y(vor))^2 + (y - x(vor))^2), str / (2π) * (x - y(vor)) / ((x - y(vor))^2 + (y - x(vor))^2))
vortex_potential(vor :: Singularity2D, x, y) = strength(vor) / (2π) * atan(y - x(vor), x - y(vor))
vortex_stream(vor :: Singularity2D, x, y) = -strength(vor) / (4π) * log((x - y(vor))^2 + (y - x(vor))^2)

struct Uniform2D{T <: Real} <: AbstractLaplace
    magnitude :: T
    angle 	  :: T
    Uniform2D{T}(mag, ang) where T <: Real = new(mag, deg2rad(ang))
end

magnitude(uni :: Uniform2D) = uni.magnitude
angle(uni :: Uniform2D)     = uni.angle

Uniform2D(mag :: T, ang :: T) where T <: Real = Uniform2D{T}(mag, ang)
Uniform2D(mag, ang) = Uniform2D(promote(mag, ang)...)

velocity(uni :: Uniform2D) = let (sa, ca) = sincos(uni.angle); uni.magnitude * SVector(ca, sa) end
potential(uni :: Uniform2D, x, y) = uni.magnitude * (x * cos(uni.angle) + y * sin(uni.angle))
stream(uni :: Uniform2D, x, y)    = uni.magnitude * (y * cos(uni.angle) - x * sin(uni.angle))

## 3D singularities
#============================================#

# struct Singularity3D{T <: Real} <: AbstractLaplace
# 	str :: T
# 	r 	:: Point3D{T}
# end

# source_velocity(src :: Source2D, x, y, z)
# source_potential(src :: Source2D, x, y, z)
# source_stream(src :: Source2D, x, y, z)

## Freestream
#============================================#

struct Freestream{T <: Real} <: AbstractLaplace
    V 	  :: T
    alpha :: T
    beta  :: T
    omega :: SVector{3,T}
    Freestream{T}(V, α_deg, β_deg, Ω) where T <: Real = new(V, deg2rad(α_deg), deg2rad(β_deg), Ω)
end

"""
    Freestream(V, α, β, Ω)

A Freestream flow in spherical polar coordinates with magnitude ``V``, angle-of-attack ``α``, side-slip angle ``β``, and a quasi-steady rotation vector ``\\Omega``.
"""
Freestream(V, α_deg, β_deg, Ω :: AbstractVector{T}) where T <: Real = Freestream{T}(V, α_deg, β_deg, Ω)

# TODO: ForwardDiff testing
# Freestream(V :: Real, α_deg, β_deg, Ω :: AbstractVector{<: Real}) = Freestream{Float64}(V, α_deg, β_deg, Ω)

"""
    Freestream(U, Ω)

A Freestream flow in Cartesian coordinates with vector ``U`` and quasi-steady rotation vector ``\\Omega``.
"""
Freestream(U :: AbstractVector{T}, Ω :: AbstractVector{T}) where T <: Real =
    let (V, α, β) = cartesian_to_freestream(U); Freestream{T}(V, α, β, Ω) end

"""
    freestream_to_cartesian(r, θ, φ)

Convert freestream flow (spherical polar) coordinates to Cartesian coordinates.
"""
freestream_to_cartesian(r, θ, φ) = r .* SVector(cos(θ) * cos(φ), -sin(φ), sin(θ) * cos(φ))

"""
    cartesian_to_freestream(U)

Convert Cartesian coordinates to freestream (spherical polar) flow coordinates.
"""
cartesian_to_freestream(U) = SVector(norm(U), -atand(U[3], U[1]), -atand(U[2], √(U[1]^2 + U[3]^2)))

"""
    velocity(freestream :: Freestream)

Compute the velocity of a `Freestream`.
"""
velocity(freestream :: Freestream) = freestream_to_cartesian(freestream.V, freestream.alpha, freestream.beta)

"""
    aircraft_velocity(freestream :: Freestream)

Compute the velocity of Freestream in the aircraft reference frame.
"""
aircraft_velocity(freestream :: Freestream) = -velocity(freestream)

end


using ModelingToolkit, MethodOfLines, LinearAlgebra, OrdinaryDiffEq
using DomainSets

# using Plots

# local sol
begin #@testset "Test 01: Brusselator equation 2D" begin
       @parameters x y t
       @variables u(..) v(..)
       Dt = Differential(t)
       Dx = Differential(x)
       Dy = Differential(y)
       Dxx = Differential(x)^2
       Dyy = Differential(y)^2

       ∇²(u) = Dxx(u) + Dyy(u)

       brusselator_f(x, y, t) = (((x-0.3)^2 + (y-0.6)^2) <= 0.1^2) * (t >= 1.1) * 5.

       x_min = y_min = t_min = 0.0
       x_max = y_max = 1.0
       t_max = 11.5

       α = 10.

       u0(x,y,t) = 22(y*(1-y))^(3/2)
       v0(x,y,t) = 27(x*(1-x))^(3/2)

       eq = [Dt(u(x,y,t)) ~ 1. + v(x,y,t)*u(x,y,t)^2 - 4.4*u(x,y,t) + α*∇²(u(x,y,t)) + brusselator_f(x, y, t),
             Dt(v(x,y,t)) ~ 3.4*u(x,y,t) - v(x,y,t)*u(x,y,t)^2 + α*∇²(v(x,y,t))]

       domains = [x ∈ Interval(x_min, x_max),
                  y ∈ Interval(y_min, y_max),
                  t ∈ Interval(t_min, t_max)]

       bcs = [u(x,y,0) ~ u0(x,y,0),
              u(0,y,t) ~ u(1,y,t),
              u(x,0,t) ~ u(x,1,t),

              v(x,y,0) ~ v0(x,y,0),
              v(0,y,t) ~ v(1,y,t),
              v(x,0,t) ~ v(x,1,t)]

       @named pdesys = PDESystem(eq,bcs,domains,[x,y,t],[u(x,y,t),v(x,y,t)])

       # Method of lines discretization
       N = 32

       dx = 1/N
       dy = 1/N

       order = 2

       discretization = MOLFiniteDifference([x=>dx, y=>dy], t, approx_order=order)

       #MethodOfLines.generate_code(pdesys, discretization)
       # Convert the PDE problem into an ODE problem
       println("Discretization:")
       @time prob = discretize(pdesys,discretization)

       println("Solve:")
       @time sol = solve(prob, TRBDF2(),saveat=0.01)

       # Solve reference problem

       xyd_brusselator = range(0,stop=1,length=N)
       brusselator_f(x, y, t) = (((x-0.3)^2 + (y-0.6)^2) <= 0.1^2) * (t >= 1.1) * 5.
       limit(a, N) = a == N+1 ? 1 : a == 0 ? N : a
       function brusselator_2d_loop(du, u, p, t)
              A, B, alpha, dx = p
              alpha = alpha/dx^2
              @inbounds for I in CartesianIndices((N, N))
                     i, j = Tuple(I)
           x, y = xyd_brusselator[I[1]], xyd_brusselator[I[2]]
           ip1, im1, jp1, jm1 = limit(i+1, N), limit(i-1, N), limit(j+1, N), limit(j-1, N)
           du[i,j,1] = alpha*(u[im1,j,1] + u[ip1,j,1] + u[i,jp1,1] + u[i,jm1,1] - 4u[i,j,1]) +
           B + u[i,j,1]^2*u[i,j,2] - (A + 1)*u[i,j,1] + brusselator_f(x, y, t)
           du[i,j,2] = alpha*(u[im1,j,2] + u[ip1,j,2] + u[i,jp1,2] + u[i,jm1,2] - 4u[i,j,2]) +
                       A*u[i,j,1] - u[i,j,1]^2*u[i,j,2]
           end
       end
       p = (3.4, 1., 10., step(xyd_brusselator))

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

       msol = solve(prob,TRBDF2(),saveat=0.01) # 2.771 s (5452 allocations: 65.73 MiB)

       # get variables for reshape
       Nx = floor(Int64, (x_max - x_min) / dx) + 1
       Ny = floor(Int64, (y_max - y_min) / dy) + 1

       @variables u[1:Nx,1:Ny](t)
       @variables v[1:Nx,1:Ny](t)

       t = sol[t]
       @testset "." begin
       for k in div(length(t), 2):length(t)
              solu = reshape([sol[u[(i-1)*Ny+j]][k] for i in 1:Nx for j in 1:Ny],(Nx,Ny))[2:end,2:end]
              msolu = msol.u[k][:,:,1]
              @test solu ≈ msolu rtol = 0.1

              solv = reshape([sol[v[(i-1)*Ny+j]][k] for i in 1:Nx for j in 1:Ny],(Nx,Ny))[2:end,2:end]
              msolv = msol.u[k][:,:,2]
              @test solv ≈ msolv rtol = 0.1
       end
       end

       # Nx = floor(Int64, (x_max - x_min) / dx) + 1
       # Ny = floor(Int64, (y_max - y_min) / dy) + 1

       #  @variables u[1:Nx,1:Ny](t)
       #  @variables v[1:Nx,1:Ny](t)
       #  t = sol[t]
       #   anim = @animate for k in 1:length(t)
       #          solu = reshape([sol[u[(i-1)*Ny+j]][k] for i in 1:Nx for j in 1:Ny],(Nx,Ny))
       #          #solv = reshape([sol[v[(i-1)*Ny+j]][k] for i in 1:Nx for j in 1:Ny],(Nx,Ny))
       #          heatmap(solu[2:end, 2:end], title="$(t[k])")
       #   end
       #   gif(anim, "plots/Brusselator2Dsol.gif", fps = 8)
end


using Test, PolynomialBases

ufunc(x) = sinpi(x)^2

function tolerance(p, T=Float64)
    if p <= 2
        1.
    elseif p <= 4
        0.78
    elseif p <= 6
        0.02
    elseif p <= 8
        1.e-4
    elseif p <= 10
        if T==Float32
            4.e-7
        else
            1.5e-7
        end
    elseif p <= 12
        if T==Float32
            3.e-7
        else
            9.e-11
        end
    elseif p <= 14
        if T==Float32
            4.e-7
        else
            3.e-14
        end
    else
        if T==Float32
            5.e-7
        else
            5.e-16
        end
    end
end

for basis_type in subtypes(PolynomialBases.NodalBasis{PolynomialBases.Line})
    basis_type <: Union{ClosedNewtonCotes,ClenshawCurtis} && continue
    for p in 3:15, T in (Float32, Float64)
        basis = basis_type(p, T)
        u = ufunc.(basis.nodes)
        @test abs(integrate(u, basis) - 1) < tolerance(p, T)
        @test abs(integrate(u, basis.weights) - 1) < tolerance(p, T)
    end
end


using EconPDEs

Base.@kwdef  struct CampbellCochraneModel
    # consumption process parameters
    μ::Float64 = 0.0189
    σ::Float64 = 0.015

    # utility
    γ::Float64 = 2.0
    ρ::Float64 = 0.116

    # habit
    κs::Float64 = 0.138
    b::Float64 = 0.0
end
# I choose persistence so that monthly simulation of the model matches processes in CC (1999)
# ρ = 12 * (1 - 0.89^(1/12))
# κs = 12 * (1 - 0.87^(1/12))

function initialize_stategrid(m::CampbellCochraneModel; smin = -300.0, n = 1000)
    μ = m.μ ; σ = m.σ ; γ = m.γ ; ρ = m.ρ ; κs = m.κs ; b = m.b
    Sbar = σ * sqrt(γ / (κs - b / γ))
    sbar = log.(Sbar)
    smax =  sbar + 0.5 * (1 - Sbar^2)
    # corresponds to Grid 3 in Wachter (2005)
    shigh = log.(range(0.0, stop = exp(smax), length = div(n, 10)))
    slow = range(smin, stop = shigh[2], length = n - div(n, 10))
    OrderedDict(:s => vcat(slow[1:(end-1)], shigh[2:end]))
end

function initialize_y(m::CampbellCochraneModel, stategrid)
    OrderedDict(:p => ones(length(stategrid[:s])))
end

function (m::CampbellCochraneModel)(state::NamedTuple, y::NamedTuple)
    μ = m.μ ; σ = m.σ ; γ = m.γ ; ρ = m.ρ ; κs = m.κs ; b = m.b
    s = state.s
    p, ps, pss = y.p, y.ps, y.pss

    # drift and volatility of  s and p
    Sbar = σ * sqrt(γ / (κs - b / γ))
    sbar = log(Sbar)
    λ = 1 / Sbar * sqrt(1 - 2 * (s - sbar)) - 1
    μs = - κs * (s - sbar)
    σs = λ * σ
    σp = ps / p * σs
    μp = ps / p * μs + 0.5 * pss / p * σs^2

    # market price of risk κ
    κ = γ * (σ + σs)

    # risk free rate  r
    r = ρ + γ * μ - (γ * κs - b) / 2 + b * (sbar - s)

    # PDE
    pt = p * (1 / p + μ + μp + σp * σ - r - κ * (σ + σp))
    return (pt,), (μs, ), (p = p, κ = κ, λ = λ, r = r, σp = σp, μs = μs, σs = σs)
end

# Campbell Cochrane (1999)
m = CampbellCochraneModel()
stategrid = initialize_stategrid(m)
y0 = initialize_y(m, stategrid)
y, result, distance = pdesolve(m, stategrid, y0)

# Wachter (2005) calibration
# m = CampbellCochraneModel(μ = 0.022, σ = 0.0086, γ = 2.0, ρ = 0.073, κs = 0.116, b = 0.011)
# stategrid = initialize_stategrid(m)
# y0 = initialize_y(m, stategrid)
# y, result, distance = pdesolve(m, stategrid, y0)


function fib20()
#
#  test polynomial suggested by Goedecker
#
    n = 20;
    p = fib(n)
    z = [-.9473885474109854;
         -.9029230998844866-.2884421994216345*im; -.9029230998844866+.2884421994216345*im;
         -.7734751441751908-.5504669225440185*im; -.7734751441751908+.5504669225440185*im;
         -.5705411148090332-.7619498890625472*im; -.5705411148090332+.7619498890625472*im;
         -.3121525651549263-.9031354642584495*im; -.3121525651549263+.9031354642584495*im;
         -.02129876087300105-.9602891496489760*im; -.02129876087300105+.9602891496489760*im;
         .2760438985065760-.9267417870492216*im; .2760438985065760+.9267417870492216*im;
         .5529956567245064-.8031600819414702*im; .5529956567245064+.8031600819414702*im;
         .7834063973613306-.5969423289081712*im; .7834063973613306+.5969423289081712*im;
         .9416394828514235-.3213776783384489*im; .9416394828514235+.3213776783384489*im;
         1.999999046316589];
    z = [z ones(n)]
    p, PolyZeros(z)
end


#######################
# Spin Operators Test #
#######################

# the Pauli spin matrices sigmax, sigmay and sigmaz obey the commutation
# relations $ [\sigma_a, \sigma_b]  = 2i\epsion_{abc} \sigma_c $, where
# $\epsion_{abc}$ is the Levi-Civita symbol.
# Hence,
@assert commute(sigmax, sigmay) == 2*im*sigmaz
@assert commute(sigmay, sigmaz) == 2*im*sigmax
@assert commute(sigmaz, sigmax) == 2*im*sigmay

@assert coeffs(commute(sigmax, sigmax)) == spzeros(2,2)

# Moreover, the sigmas fulfill anticommutation relations
# ${\sigma_a, \sigma_b} = 2\delta_{a,b} I$ with $\delta_{a,b}$
# being the Kronecker delta and $I$ the 2x2 identity matrix
# Thus,
@assert anticommute(sigmax, sigmax) == 2 * QuArray(speye(2))

@assert spinjp(3.5) == spinjx(3.5) + (im * spinjy(3.5))
@assert spinjm(3.5) == spinjx(3.5) - (im * spinjy(3.5))

####################################################
# Position, Displacement & Momentum Operators Test #
####################################################

# position and momentum operators obey the commutation relation
# $[x,p] = i I$, where $I$ is the identity operator
# However, here we just use the fact that for n=2, x~sigmax and
# p ~ sigmay
p = positionop(2)
m = momentumop(2)
@assert coeffs(commute(sigmax, p)) == spzeros(2,2)
@assert coeffs(commute(sigmay, m)) == spzeros(2,2)

# test coefficients computed by coherentstatevec vs
# Fock basis coefficients
coherentstate_inf = QuBase.coherentstatevec_inf(20,1.0)
coherentstate = coherentstatevec(20, 1.0)
@test_approx_eq_eps coherentstate_inf[1] coherentstate[1] 1e-8

############################
# Squeezing Operators Test #
############################

@assert squeezingop(2,1.0)== QuArray(eye(2))
@test_approx_eq coeffs(squeezingop(lowerop(2), QuArray(eye(2)), -2.0)) coeffs(displaceop(2,1.0))


function genball(R)
    sphere_side = 2R + 1
    sphere = zeros(Bool, (sphere_side, sphere_side, sphere_side))

    for i in -R:R
        for j in -R:R
            for k in -R:R
                dist = i^2 + j^2 + k^2
                if dist < R^2
                    sphere[k+R+1, j+R+1, i+R+1] = 1
                end
            end
        end
    end

    return sphere
end

function gendisk(R)
    sphere  = zeros(Bool, (2R + 1, 2R + 1))

    for i in -R:R
        for j in -R:R
            dist = i^2 + j^2
            if dist < R^2
                sphere[j+R+1, i+R+1] = 1
            end
        end
    end

    return sphere
end

function gentorus(side, r, R)
    a = zeros(Bool, (side, side, side))
    for i in 1:side
        for j in 1:side
            for k in 1:side
                x = i - side÷2
                y = j - side÷2
                z = k - side÷2
                if (x^2 + y^2 + z^2 + R^2 - r^2)^2 < 4R^2*(x^2 + y^2)
                    a[k,j,i] = 1
                end
            end
        end
    end

    return a
end

@test euler_characteristic(genball(100)) == 1
@test euler_characteristic(gendisk(100)) == 1
@test euler_characteristic(ones(Bool, (100, 100, 100))) == 1
@test euler_characteristic(hcat(ones(Bool, (100, 100, 100)),
                                zeros(Bool, (100, 100, 100)),
                                ones(Bool, (100, 100, 100)))) == 2
let a = ones(Bool, (100, 100));
    a[50:60, 50:60] .= 0;
    @test euler_characteristic(a) == 0
end

let a = gentorus(200, 10, 90);
    b = cat(a[:,:,begin:end-5], a[:,:,begin+5:end]; dims = [3])
    @test euler_characteristic(b) == -1
end


abstract type AbstractAcquisition end

setparams!(a, model) = nothing
function acquisitionfunction(a, model)
    x -> begin
        μ, σ² = mean_var(model, x)
        a.(μ, σ²)
    end
end

"""
The probability of improvement measures the probability that a point `x` leads
to an improvement upon an incumbent target `τ`. For Gaussian distributions it is
given by

    Φ[(μ(x) - τ)/σ(x)]

where `Φ` is the standard normal cumulative distribution function and `μ(x)`, `σ(x)`
are mean and standard deviation of the distribution at point `x`.
"""
mutable struct ProbabilityOfImprovement <: AbstractAcquisition
    τ::Float64
end
@inline function (a::ProbabilityOfImprovement)(μ, σ²)
    σ² == 0 && return float(μ > a.τ)
    normal_cdf(μ - a.τ, σ²)
end
ProbabilityOfImprovement(; τ = -Inf) = ProbabilityOfImprovement(τ)

"""
The expected improvement measures the expected improvement `x - τ` of a point `x`
upon an incumbent target `τ`. For Gaussian distributions it is given by

    (μ(x) - τ) * ϕ[(μ(x) - τ)/σ(x)] + σ(x) * Φ[(μ(x) - τ)/σ(x)]

where `ϕ` is the standard normal distribution function and `Φ` is the standard
normal cumulative function, and `μ(x)`, `σ(x)` are mean and standard deviation
of the distribution at point `x`.
"""
mutable struct ExpectedImprovement <: AbstractAcquisition
    τ::Float64
end
ExpectedImprovement(; τ = -Inf) = ExpectedImprovement(τ)
function setparams!(a::Union{ExpectedImprovement,ProbabilityOfImprovement}, model)
    a.τ = max(maxy(model), a.τ)
end
@inline function (a::ExpectedImprovement)(μ, σ²)
    σ² == 0 && return μ > a.τ ? μ - a.τ : 0.
    abs(μ - a.τ) * normal_cdf(μ - a.τ, σ²) + √σ² * normal_pdf(μ - a.τ, σ²)
end

abstract type BetaScaling end
"""
Scales `βt` of `UpperConfidenceBound` as

    βt = √(2 * log(t^(D/2 + 2) * π^2/(3δ)))

where `t` is the number of observations, `D` is the dimensionality of the input
data points and δ is a small constant (default δ = 0.1).

See Brochu E., Cora V. M., de Freitas N. (2010), "A Tutorial on Bayesian
Optimization of Expensive Cost Functions, with Application to Active User
Modeling and Hierarchical Reinforcement Learning", https://arxiv.org/abs/1012.2599v1
page 16.
"""
struct BrochuBetaScaling <: BetaScaling
    δ::Float64
end
"""
Applies no scaling to `βt` of `UpperConfidenceBound`.
"""
struct NoBetaScaling <: BetaScaling end
"""
For Gaussian distributions the upper confidence bound at `x` is given by
    μ(x) + βt * σ(x)

where `βt` is a fixed parameter in the case of `NoBetaScaling` or an observation
size dependent parameter in the case of e.g. `BrochuBetaScaling`.
"""
mutable struct UpperConfidenceBound{S} <: AbstractAcquisition
    scaling::S
    βt::Float64
end
"""
    UpperConfidenceBound(; scaling = BrochuBetaScaling(.1), βt = 1)
"""
UpperConfidenceBound(; scaling = BrochuBetaScaling(.1), βt = 1.) = UpperConfidenceBound(scaling, βt)
function setparams!(a::UpperConfidenceBound{BrochuBetaScaling}, model)
    D, nobs = dims(model)
    nobs == 0 && (nobs = 1)
    a.βt = sqrt(2*log(nobs^(D/2 + 2)*π^2/(3*a.scaling.δ)))
end
(a::UpperConfidenceBound)(μ, σ²) = μ + a.βt * √σ²

"""
The acquisition function associated with `ThompsonSamplingSimple` draws
independent samples for each input `x` a function value from the model. Together
with a gradient-free optimization method this leads to proposal points that
might be similarly distributed as the maxima of true Thompson samples from GPs.
True Thompson samples from a GP are simply functions from a GP. Maximizing these
samples can be tricky, see e.g. http://hildobijl.com/Downloads/GPRT.pdf
chapter 6.
"""
struct ThompsonSamplingSimple <: AbstractAcquisition end
acquisitionfunction(a::ThompsonSamplingSimple, model) = x -> myrand(model, x)

struct MaxMean <: AbstractAcquisition end
acquisitionfunction(a::MaxMean, model) = x -> mean_var(model, x)[1]

"""
The mutual information measures the amount of information gained by querying at
x. The parameter γ̂ gives a lower bound for the information on f from the queries
{x}. For a Gaussian this is
    γ̂ = ∑σ²(x)
and the mutual information at x is
    μ(x) + √(α)*(√(σ²(x)+γ̂) - √(γ̂))

where `μ(x)`, `σ(x)` are mean and standard deviation
of the distribution at point `x`.

See Contal E., Perchet V., Vayatis N. (2014), "Gaussian Process Optimization
with Mutual Information" http://proceedings.mlr.press/v32/contal14.pdf
"""
mutable struct MutualInformation <: AbstractAcquisition
    sqrtα::Float64
    γ̂::Float64
end
MutualInformation(; α = 1.0, γ̂ = 0.0) = MutualInformation(sqrt(α), γ̂)
function setparams!(a::MutualInformation, model)
    D, nobs = dims(model)
    if iszero(nobs)
        a.γ̂ = 0.0
    else
        last_x = @view model.x[:, end]
        μ, σ2 = mean_var(model, last_x)
        a.γ̂ += σ2
    end
end
(a::MutualInformation)(μ, σ²) = μ + a.sqrtα * (sqrt(σ² + a.γ̂) - sqrt(a.γ̂))

# TODO see
# https://github.com/HildoBijl/GPRT/blob/7166548b8587201fabc671a0647aac2ff96f3555/Chapter6/Chapter6.m#L723
# and corresponding thesis page 173
# mutable struct ThompsonSampling{K} <: AbstractAcquisition
# end

# TODO
# mutable struct EntropySearch <: AbstractAcquisition
# end

# TODO
# mutable struct PredictiveEntropySearch <: AbstractAcquisition
# end


@testset verbose=true "map2salm" begin
    # NOTE: Float16 irfft returns Float32, which leads to type conflicts, so we just don't support
    # map2salm on Float16

    function sYlm(s::Int, ell::Int, m::Int, theta::T, phi::T) where {T<:Real}
        # Eqs. (II.7) and (II.8) of https://arxiv.org/abs/0709.0093v3
        # Note their weird definition w.r.t. `-s`
        k_min = max(0, m + s)
        k_max = min(ell + m, ell + s)
        sin_half_theta, cos_half_theta = sincos(theta / 2)
        return (-1)^(-s) * sqrt((2 * ell + 1) / (4 * T(π))) *
            T(sum(
                (-1) ^ (k)
                * sqrt(factorial(big(ell + m)) * factorial(big(ell - m)) * factorial(big(ell - s)) * factorial(big(ell + s)))
                * (cos_half_theta ^ (2 * ell + m + s - 2 * k))
                * (sin_half_theta ^ (2 * k - s - m))
                / (factorial(big(ell + m - k)) * factorial(big(ell + s - k)) * factorial(big(k)) * factorial(big(k - s - m)))
                for k in k_min:k_max
            )) *
            exp(im * m * phi)
    end

    # Eqs. (II.9) through (II.13) of https://arxiv.org/abs/0709.0093v3
    m2Y22(iota::T, phi) where T = sqrt(5 / (64 * T(π))) * (1 + cos(iota)) ^ 2 * exp(im * 2phi)
    m2Y21(iota::T, phi) where T = sqrt(5 / (16 * T(π))) * sin(iota) * (1 + cos(iota)) * exp(im * phi)
    m2Y20(iota::T, phi) where T = sqrt(15 / (32 * T(π))) * sin(iota) ^ 2
    m2Y2m1(iota::T, phi) where T = sqrt(5 / (16 * T(π))) * sin(iota) * (1 - cos(iota)) * exp(im * -1phi)
    m2Y2m2(iota::T, phi) where T = sqrt(5 / (64 * T(π))) * (1 - cos(iota)) ^ 2 * exp(im * -2phi)

    @testset "Input expressions $T" for T in [BigFloat, Float64, Float32]
        # These are just internal consistency tests of the sYlm function
        # above, against the explicit expressions `mY2.`
        s = -2
        ℓ = 2
        Nϑ = 17
        Nφ = 18
        for (m, m2Y2m) in [(2, m2Y22), (1, m2Y21), (0, m2Y20), (-1, m2Y2m1), (-2, m2Y2m2)]
            f1 = mapslices(ϕθ -> sYlm(s, ℓ, m, ϕθ[2], ϕθ[1]), phi_theta(Nφ, Nϑ, T), dims=[3])
            f2 = mapslices(ϕθ -> m2Y2m(ϕθ[2], ϕθ[1]), phi_theta(Nφ, Nϑ, T), dims=[3])
            @test f1 ≈ f2 atol=10eps(T) rtol=10eps(T)
        end
    end

    @testset "map2salm $T" for T in [BigFloat, Float64, Float32]
        # These test the ability of map2salm to precisely decompose the results of `sYlm`.
        ℓmax = 7
        Nϑ = 2ℓmax + 1
        Nφ = 2ℓmax + 2
        for s in -2:2
            for ℓmin in 0:abs(s)
                for ℓ in abs(s):ℓmax
                    for m in -ℓ:ℓ
                        f = mapslices(ϕθ -> sYlm(s, ℓ, m, ϕθ[2], ϕθ[1]), phi_theta(Nφ, Nϑ, T), dims=[3])
                        computed = map2salm(f, s, ℓmax; ℓmin)
                        expected = zeros(Complex{T}, size(computed))
                        expected[SphericalFunctions.Yindex(ℓ, m, ℓmin)] = one(T)
                        if ≉(computed, expected, atol=30eps(T), rtol=30eps(T))
                            @show T
                            @show ℓmax
                            @show Nϑ
                            @show Nφ
                            @show s
                            @show ℓmin
                            @show ℓ
                            @show m
                            println("computed = $computed")
                            println("expected = $expected")
                            println("max_diff = ", maximum(abs, computed .- expected), ";")
                            println()
                        end
                        @test computed ≈ expected atol=30eps(T) rtol=30eps(T)
                    end
                end
            end
        end
    end

end


cd(@__DIR__); include("setups/grid23x22.jl")
gr(dpi = 200)

##
frame = sgwt_frame(W; nf = 6)
filters = sgwt_filter_banks(W, 𝛌; nf = 6)
plot(𝛌, filters', lw = 3, size = (800, 450), grid = false, frame = :box,
    legendfontsize = 14, xlab = latexstring("\\lambda"), xguidefontsize = 14,
    lab = [latexstring("h(\\lambda)") latexstring("g(s_{1}\\lambda)") latexstring("g(s_{2}\\lambda)") latexstring("g(s_{3}\\lambda)") latexstring("g(s_{4}\\lambda)") latexstring("g(s_{5}\\lambda)")])
# plot!(𝛌, sum(filters, dims = 1)[:]/6, c = :black, lw = 3, lab = "partition of unity")
plt = current()
savefig(plt, "../figs/Grid$(Nx)x$(Ny)_SGWT_filter_banks.png")


"""
Riemann-Liouville sense fractional derivative algorithms, please refer to [Riemann-Liouville derivative](https://en.wikipedia.org/wiki/Fractional_calculus#Riemann%E2%80%93Liouville_fractional_derivative)
"""
abstract type RLDiff <: FracDiffAlg end

#RLDiff_LinearInterp maybe??
"""
# Riemann Liouville sense derivative approximation

    fracdiff(f, α, end_point, h, RLDiffApprox())

Using Linear interpolation to approximate fractional derivative in Riemann Liouville  fractional derivative sense.

### Example

```julia-repl
julia> fracdiff(x->x^5, 0.5, 2.5, 0.0001, RLDiffApprox())
141.59707906952633
```

!!! warning
    The RLDiffApprox algorithm only support for 0 < α < 1.
"""
struct RLDiffApprox <: RLDiff end

"""
# Riemann Liouville sense derivative using Triangular Strip Matrix to discrete and compute.

    fracdiff(f, α, end_point, h, RLDiffMatrix())

Using [Triangular Strip Matrix](https://en.wikipedia.org/wiki/Triangle_strip) to approximate fractional derivative.

### Example

```julia-repl
julia> fracdiff(x->x^5, 0.5, 2.5, 0.0001, RLInt_Matrix())
```

!!! info
    Triangular Strip Matrix method returns the derivative in the interval ``[0, T]`` in ```Vector```

```tex
@article{2009,
title={Matrix approach to discrete fractional calculus II: Partial fractional differential equations},
DOI={10.1016/j.jcp.2009.01.014},
author={Podlubny, Igor and Chechkin, Aleksei and Skovranek, Tomas and Chen, YangQuan and Vinagre Jara, Blas M.},
}
```
"""
struct RLDiffMatrix <: RLDiff end

"""
# Riemann Liouville sense linear spline interpolation.

    fracdiff(f, α, end_point, h, RLLinearSplineInterp())

Using linear spline interpolation method to approximate the Riemann Liouville fractional derivative.
"""
struct RLLinearSplineInterp <: RLDiff end

"""
# Riemann Liouville sense G1 scheme

    fracdiff(f, α, start_point, end_point, h, RLG1())

Remove the limit symbol in the definition of Grunwald-Letnikov fractional derivative, thereby leading to a discretization scheme in form of truncated series.

!!! tip
        **RLG1** also can be used to compute fractional integral~
        ``+\\alpha`` for fractional derivative and ``-\\alpha`` for fractional integral.

```tex
@inproceedings{Guo2015FractionalPD,
  title={Fractional Partial Differential Equations and their Numerical Solutions},
  author={Boling Guo and Xueke Pu and Feng-Hui Huang},
  year={2015}
}
```
"""
struct RLG1 <: RLDiff end

"""
# Riemann Liouville sense D scheme

        fracdiff(f, α, point, h, RLD())

```tex
@inproceedings{Guo2015FractionalPD,
  title={Fractional Partial Differential Equations and their Numerical Solutions},
  author={Boling Guo and Xueke Pu and Feng-Hui Huang},
  year={2015}
}
```
"""
struct RLD <: RLDiff end

################################################################
###                    Type definition done                  ###
################################################################

function fracdiff(f::FunctionAndNumber, α, end_point, h::Float64, ::RLDiffApprox)
    #checks(f, α, 0, end_point)
    typeof(f) <: Number ? (end_point == 0 ? (return 0) : (return f/sqrt(pi*end_point))) : nothing
    end_point == 0 ? (return 0) : nothing

    summation = 0
    n = floor(Int, end_point/h)

    @fastmath @inbounds @simd for i ∈ 0:n-1
        summation += (f(end_point-i*h) - f(end_point-(i+1)*h))*((i+1)^(1-α) - i^(1-α))
    end

    result = ((1-α)*f(0)/n^α+summation)*end_point^(-α)*n^α/gamma(2-α)
    return result
end

function fracdiff(f::FunctionAndNumber, α::Float64, end_point::AbstractArray, h::Float64, ::RLDiffApprox)::Vector
    result = map(x->fracdiff(f, α, x, h, RLDiffApprox()), end_point)
    return result
end

function fracdiff(f::Union{Function, Number}, α, end_point, h::Float64, ::RLDiffMatrix)
    N = round(Int, end_point/h+1)
    @views tspan = collect(0:h:end_point)
    return B(N, α, h)*f.(tspan)
end

#Compute the eliminator matrix Sₖ by omiting n-th row
function eliminator(n, row)
    temp = zeros(n, n) + I
    return @views temp[Not(row), :]
end

function B(N, p)
    result = zeros(N, N)
    temp = omega(N, p)

    @inbounds @simd for i ∈ 1:N
        @views result[i, 1:i]=reverse(temp[1:i])
    end

    return result
end
# Multiple dispatch for assigning step size *h*.
function B(N, p, h::Float64)
    result = B(N, p)

    return h^(-p)*result
end

#=
Numerical methods for fractional calculus by Li, Changpin
Page 57

Linear Spline Interpolation
=#
function fracdiff(f::FunctionAndNumber, α, x, h::Float64, ::RLLinearSplineInterp)
    typeof(f) <: Number ? (x == 0 ? (return 0) : (return f/sqrt(pi*x))) : nothing
    x == 0 ? (return 0) : nothing
    N = round(Int, x/h)

    result = 0

    @fastmath @inbounds @simd for k = 0:(N+1)
        result += z̄ₘₖ(N, k, α)*f(k*h)
    end

    return 1/(gamma(4-α)h^α)*result

end

function z̄ₘₖ(m, k, α)
    if k ≤ m-1
        return c̄ⱼₖ(m-1, k, α)-2*c̄ⱼₖ(m, k, α)+c̄ⱼₖ(m+1, k, α)
    elseif k == m
        return -2*c̄ⱼₖ(m, k, α)+c̄ⱼₖ(m+1, k, α)
    elseif k == m+1
        return c̄ⱼₖ(m+1, k, α)
    elseif k > m+1
        return 0
    end
end

function c̄ⱼₖ(j, k, α)
    if k==0
        return (j-1)^(3-α)-j^(2-α)*(j-3+α)
    elseif 1 ≤ k ≤ j-1
        return (j-k+1)^(3-α)-2*(j-k)^(3-α)+(j-k-1)^(3-α)
    elseif k == j
        return 1
    end
end

function fracdiff(f::FunctionAndNumber, α::Float64, end_point::AbstractArray, h::Float64, ::RLLinearSplineInterp)::Vector
    result = map(x->fracdiff(f, α, x, h, RLLinearSplineInterp()), end_point)
    return result
end

function fracdiff(f::FunctionAndNumber, α, start_point, end_point, h::Float64, ::RLG1)
    typeof(f) <: Number ? (end_point == 0 ? (return 0) : (return f/sqrt(pi*end_point))) : nothing
    end_point == 0 ? (return 0) : nothing

    N = round(Int, (end_point-start_point)/h)

    result = zero(Float64)
    @fastmath @inbounds @simd for j = 0:N-1
        result += gamma(j-α)/gamma(j+1)*f(end_point-j*h)
    end

    return h^(-α)/gamma(-α)*result
end

function fracdiff(f::FunctionAndNumber, α, point, h::Float64, ::RLD)
    typeof(f) <: Number ? (point == 0 ? (return 0) : (return f/sqrt(pi*point))) : nothing
    point == 0 ? (return 0) : nothing

    N = round(Int, point/h)

    result = zero(Float64)
    @fastmath @inbounds @simd for k = 0:N
        result += ωₖₙ(N, k, α)*f(point-k*h)
    end

    return point^(-α)/gamma(-α)*result

end

function ωₖₙ(n, k, α)
    temp = 0
    if k == 0
        temp = -1
    elseif 1 ≤ k ≤ n-1
        temp = 2*k^(1-α)-(k-1)^(1-α)-(k+1)^(1-α)
    elseif k == n
        temp = (α-1)*n^(-α)-(n-1)^(1-α)+n^(1-α)
    end
    return n^α/(α*(1-α))*temp
end


"""
    adaptif(f, a, b; delta=10^-9)

adalah fungsi yang digunakan untuk mencari nilai integral dari fungsi `f`
pada interval `[a,b]` menggunakan aturan kuadratur adaptif.
Secara default toleransi yang digunakan adalah `delta=1e-9`.

# Examples
```jldoctest
julia> f(x) = 13*(x-x.^2).*exp(-3*x/2);

julia> sol,err,SRmat = adaptif(f,0,4,delta=0.00001);

julia> sol
-1.5487882341253174

julia> err
2.9680861581356417e-6

julia> SRmat
20×5 Array{Float64,2}:
 0.0     0.0625   0.0228718  1.52153e-8  1.5625e-7
 0.0625  0.125    0.0594869  1.31649e-8  1.5625e-7
 0.125   0.1875   0.0843421  1.13722e-8  1.5625e-7
 0.1875  0.25     0.0996987  9.80639e-9  1.5625e-7
 ⋮
 2.5     2.75    -0.2701     7.07089e-8  6.25e-7
 2.75    3.0     -0.234747   5.4474e-8   6.25e-7
 3.0     3.5     -0.363898   1.03699e-6  1.25e-6
 3.5     4.0     -0.243138   4.10779e-7  1.25e-6
```
return solusi `sol`, estimasi kesalahan `err`, dan
matriks `SRmat` yang berisi sub-interval (kolom 1 dan 2), nilai integral
pada sub-interval (kolom 3), galat integral numerik (kolom 4), dan toleransi pada
sub-interval (kolom 5).

"""
function adaptif(f, a, b; delta=10^-9)
  iterating = 0;
  done = 1;
  SRvec = srule(f,a,b,delta);
  SRmat = SRvec;
  m = 1;
  state = iterating;
  while(state==iterating)
    n = m;
    for j = n:-1:1
      p = j;
      SR0vec = SRmat[p,:];
      err = SR0vec[5];
      delta = SR0vec[6];
      if (delta<=err)
        state=done;
        SR1vec=SR0vec;
        SR2vec=SR0vec;
        a = SR0vec[1];
        b = SR0vec[2];
        c =(a+b)/2;
        err=SR0vec[5];
        delta=SR0vec[6];
        delta2=delta/2;
        SR1vec=srule(f,a,c,delta2);
        SR2vec=srule(f,c,b,delta2);
        err = abs(SR0vec[3]-SR1vec[3]-SR2vec[3])/10;
        if err<delta
          SRmat[p,:]=SR0vec;
          SRmat[p,4]=SR1vec[3]+SR2vec[3];
          SRmat[p,5]=err;
        else
          SRmat = [SRmat; zeros(1,6)]
          SRmat[p+1:m+1,:] = SRmat[p:m,:];
          m=m+1;
          SRmat[p,:]=SR1vec;
          SRmat[p+1,:]=SR2vec;
          state=iterating;
        end
      end
    end
  end
  sol = sum(SRmat[:,4]);
  err = sum(abs.(SRmat[:,5]));
  SRmat = SRmat[1:m,[1,2,4,5,6]];
  return sol, err, SRmat
end

function srule(f,a0,b0,delta0)
  h = (b0-a0)/2;
  C = zeros(1,3)
  C = f.([a0 (a0+b0)/2 b0]);
  S = h*(C[1]+4*C[2]+C[3])/3;
  S2= S;
  delta1=delta0;
  err=delta0;
  Z = [a0 b0 S S2 err delta1];
  return Z
end


using KNITRO
using Compat.Test

#    min  9 - 8x1 - 6x2 - 4x3
#         + 2(x1^2) + 2(x2^2) + (x3^2) + 2(x1*x2) + 2(x1*x3)
#    subject to  c[0]:  x1 + x2 + 2x3 <= 3
#                x1 >= 0
#                x2 >= 0
#                x3 >= 0
#    initpt (0.5, 0.5, 0.5)
#
#    Solution is x1=4/3, x2=7/9, x3=4/9, lambda=2/9  (f* = 1/9)
#
#  The problem comes from Hock and Schittkowski, HS35.

function eval_f(x::Vector{Float64})
    linear_terms = 9.0 - 8.0*x[1] - 6.0*x[2] - 4.0*x[3]
    quad_terms = 2.0*x[1]^2 + 2.0*x[2]^2 + x[3]^2 + 2.0*x[1]*x[2] + 2.0*x[1]*x[3]
    return linear_terms + quad_terms
end

function eval_g(x::Vector{Float64}, cons::Vector{Float64})
    cons[1] = x[1] + x[2] + 2.0*x[3]
end

function eval_grad_f(x::Vector{Float64}, grad::Vector{Float64})
    grad[1] = -8.0 + 4.0*x[1] + 2.0*x[2] + 2.0*x[3]
    grad[2] = -6.0 + 2.0*x[1] + 4.0*x[2]
    grad[3] = -4.0 + 2.0*x[1]            + 2.0*x[3]
end

function eval_jac_g(x::Vector{Float64}, jac::Vector{Float64})
    jac[1] = 1.0
    jac[2] = 1.0
    jac[3] = 2.0
end

objGoal = KTR_OBJGOAL_MINIMIZE
objType = KTR_OBJTYPE_QUADRATIC

n = 3
x_L = zeros(n)
x_U = [KTR_INFBOUND,KTR_INFBOUND,
KTR_INFBOUND]

m = 1
c_Type = [KTR_CONTYPE_LINEAR]
c_L = [-KTR_INFBOUND]
c_U = [3.0]

jac_con = Int32[0,0,0]
jac_var = Int32[0,1,2]

x       = [0.5,0.5,0.5]
lambda  = zeros(n+m)
obj     = [0.0]

kp = createProblem()
setOption(kp, "hessopt", 6)
initializeProblem(kp, objGoal, objType, x_L, x_U, c_Type, c_L, c_U, jac_var, jac_con)
setFuncCallback(kp, eval_f, eval_g)
setGradCallback(kp, eval_grad_f, eval_jac_g)
solveProblem(kp)

# --- test optimal solutions ---
@testset "Test optimal solution" begin
    @test applicationReturnStatus(kp) == :Optimal
    @test isapprox(kp.x, [1.3333333, 0.7777777, 0.4444444], atol=1e-5)
    @test isapprox(kp.obj_val[1], 0.1111111, atol=1e-5)
end

freeProblem(kp)


#Stationary iterative methods
#Templates, section 2.2
export jacobi, jacobi!, gauss_seidel, gauss_seidel!, sor, sor!, ssor, ssor!

jacobi(A::AbstractMatrix, b;
       tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2)^2) =
    jacobi!(zerox(A, b), A, b; tol=tol, maxiter=maxiter)

function jacobi!(x, A::AbstractMatrix, b;
        tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2)^2)
	n = size(A,2)
    xold = copy(x)
    z = zero(Amultype(A, x))
    tol = tol * norm(b)
	resnorms = zeros(typeof(real(b[1])), maxiter)
	for iter=1:maxiter
		for i=1:n
			xi = z
			for j=[1:i-1;i+1:n]
				xi += A[i,j]*xold[j]
			end
			A[i,i]==0 && throw(SingularError())
			x[i]=(b[i]-xi)/A[i,i]
		end
		#check convergence
		resnorms[iter] = norm(A*x-b)
		if resnorms[iter] < tol
			resnorms=resnorms[1:iter]
			break
		end
		copy!(xold, x)
	end
	x, ConvergenceHistory(resnorms[end]<tol, tol, length(resnorms), resnorms)
end

gauss_seidel(A::AbstractMatrix, b;
        tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2)^2) =
    gauss_seidel!(zerox(A, b), A, b; tol=tol, maxiter=maxiter)

function gauss_seidel!(x, A::AbstractMatrix, b;
        tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2)^2)
	n = size(A,2)
    xold = copy(x)
    z = zero(Amultype(A, x))
    tol = tol * norm(b)
	resnorms = zeros(typeof(real(b[1])), maxiter)
	for iter=1:maxiter
		for i=1:n
			σ=z
			for j=1:i-1
				σ+=A[i,j]*x[j]
			end
			for j=i+1:n
				σ+=A[i,j]*xold[j]
			end
			A[i,i]==0 && throw(SingularError())
			x[i]=(b[i]-σ)/A[i,i]
		end
		#check convergence
		resnorms[iter] = norm(A*x-b)
		if resnorms[iter] < tol
			resnorms=resnorms[1:iter]
			break
		end
		copy!(xold, x)
	end
	x, ConvergenceHistory(resnorms[end]<tol, tol, length(resnorms), resnorms)
end

#Successive overrelaxation
sor(A::AbstractMatrix, b, ω::Real;
    tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2)^2) =
    sor!(zerox(A, b), A, b, ω; tol=tol, maxiter=maxiter)

function sor!(x, A::AbstractMatrix, b, ω::Real;
        tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2)^2)
	0 < ω < 2 || warn("ω = $ω lies outside the range 0<ω<2 which is required for convergence")

	n = size(A,2)
    xold = copy(x)
    z = zero(Amultype(A, x))
    tol = tol * norm(b)
	resnorms = zeros(typeof(real(b[1])), maxiter)
	for iter=1:maxiter
		for i=1:n
			σ=z
			for j=1:i-1
				σ+=A[i,j]*x[j]
			end
			for j=i+1:n
				σ+=A[i,j]*xold[j]
			end
			A[i,i]==0 && throw(SingularError())
			σ=(b[i]-σ)/A[i,i]
			x[i]=xold[i]+ω*(σ-xold[i])
		end
		#check convergence
		resnorms[iter] = norm(A*x-b)
		if resnorms[iter] < tol
			resnorms=resnorms[1:iter]
			break
		end
		copy!(xold, x)
	end
	x, ConvergenceHistory(resnorms[end]<tol, tol, length(resnorms), resnorms)
end

#Symmetric successive overrelaxation
#A must be symmetric
ssor(A::AbstractMatrix, b, ω::Real;
     tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2)) =
    ssor!(zerox(A, b), A, b, ω; tol=tol, maxiter=maxiter)

function ssor!(x, A::AbstractMatrix, b, ω::Real;
        tol=size(A,2)^3*eps(typeof(real(b[1]))), maxiter=size(A,2))
	0 < ω < 2 || warn("ω = $ω lies outside the range 0<ω<2 which is required for convergence")

	n = size(A,2)
    xold = copy(x)
    z = zero(Amultype(A, x))
    tol = tol * norm(b)
	resnorms = zeros(typeof(real(b[1])), maxiter)
	for iter=1:maxiter
		for i=1:n #Do a SOR sweep
			σ=z
			for j=1:i-1
				σ+=A[i,j]*x[j]
			end
			for j=i+1:n
				σ+=A[i,j]*xold[j]
			end
			A[i,i]==0 && throw(SingularError())
			σ=(b[i]-σ)/A[i,i]
			x[i]=xold[i]+ω*(σ-xold[i])
		end
		copy!(xold, x)
		for i=n:-1:1 #Do a backward SOR sweep
			σ=z
			for j=1:i-1
				σ+=A[i,j]*xold[j]
			end
			for j=i+1:n
				σ+=A[i,j]*x[j]
			end
			A[i,i]==0 && throw(SingularError())
			σ=(b[i]-σ)/A[i,i] #This line is missing in the Templates reference
			x[i]=xold[i]+ω*(σ-xold[i])
		end
		#check convergence
		resnorms[iter] = norm(A*x-b)
		if resnorms[iter] < tol
			resnorms=resnorms[1:iter]
			break
		end
		copy!(xold, x)
	end
	x, ConvergenceHistory(resnorms[end]<tol, tol, length(resnorms), resnorms)
end


const fdc1_2 = [0, 1/2]
const fdc1_4 = [0, 2/3, -1/12]
const fdc1_6 = [0, 3/4, -3/20,1/60 ]
const fdc1_8 = [0, 4/5, -1/5 ,4/105, -1/280]
const fdc2_2 = [-2, 1]
const fdc2_4 = [-5/2,    4/3,-1/12]
const fdc2_6 = [-49/18 , 3/2,-3/20, 1/90]
const fdc2_8 = [-205/72, 8/5,-1/5,  8/315, -1/560]
const fdc1 = [fdc1_2, fdc1_4, fdc1_6, fdc1_8]
const fdc2 = [fdc2_2, fdc2_4, fdc2_6, fdc2_8]
fdcoeff1 = Vector{OffsetVector{Float64, Vector{Float64}}}(undef, 4)
fdcoeff2 = Vector{OffsetVector{Float64, Vector{Float64}}}(undef, 4)
function init!(fdcoeff1, fdcoeff2)
	 for i in 1:4
		tmp1 = zeros(2*i + 1)
		tmp1[i + 1] = fdc1[i][1]
		tmp2 = zeros(2*i + 1)
		tmp2[i + 1] = fdc2[i][1]
		for k in 1:i
			tmp1[i + 1 + k] =  fdc1[i][k+1]
			tmp1[i + 1 - k] = -fdc1[i][k+1]
			tmp2[i + 1 + k] =  fdc2[i][k+1]
			tmp2[i + 1 - k] =  fdc2[i][k+1]
		end
		fdcoeff1[i] = OffsetVector(tmp1, -i:i)
		fdcoeff2[i] = OffsetVector(tmp2, -i:i)
	end
end
init!(fdcoeff1, fdcoeff2)
cycindex(i,n) = (i + n - 1)%n + 1
function dfdt(f::TVF, dh::T; order = 2, bc = :Zero)where {T, TVF<:AbstractVector{T}}
	@assert order in Set([2,4,6,8])
	@assert bc in Set([:Zero, :Periodic])
	n = length(f)
	m = div(order, 2)
	g = similar(f)
	if bc == :Zero
		for i in 1:n
			for k in 1:m
				g[i] = fdcoeff1[m][0]*f[i]
				if i + k ≤ n
					g[i] += fdcoeff1[m][ k]*f[i+k]
				end
				if 1 ≤ i - k
					g[i] += fdcoeff1[m][-k]*f[i-k]
				end
			end
		end
	else
		for i in 1:n
			g[i] = fdcoeff1[m][0]*f[i]
			for k in 1:m
				g[i] += fdcoeff1[m][ k]*f[cycindex(i + k, n)] + fdcoeff1[m][-k]*f[cycindex(i - k, n)]
			end
		end
	end
	g ./= dh
	return g
end
function d2fdt(f::TVF, dh::T; order = 2, bc = :Zero)where {T, TVF<:AbstractVector{T}}
	@assert order in Set([2,4,6,8])
	@assert bc in Set([:Zero, :Periodic])
	n = length(f)
	m = div(order, 2)
	g = similar(f)
	if bc == :Zero
		for i in 1:n
			for k in 1:m
				g[i] = fdcoeff2[m][0]*f[i]
				if i + k ≤ n
					g[i] += fdcoeff2[m][ k]*f[i+k]
				end
				if 1 ≤ i - k
					g[i] += fdcoeff2[m][-k]*f[i-k]
				end
			end
		end
	else
		for i in 1:n
			g[i] = fdcoeff2[m][0]*f[i]
			for k in 1:m
				g[i] += fdcoeff2[m][ k]*f[cycindex(i + k, n)] + fdcoeff2[m][-k]*f[cycindex(i - k, n)]
			end
		end
	end
	g ./= dh^2
	return g
end
function makefd1mat(n::Integer, dh, order = 2; bc = :Zero)
	@assert order in Set([2,4,6,8])
	@assert bc in Set([:Zero, :Periodic])
	mat = zeros(typeof(float(dh)), (n, n))
	m = div(order, 2)
	if bc == :Zero
		for i in 1:n
			mat[i,i] = fdcoeff1[m][0]
			for k in 1:m
				if i + k ≤ n
					mat[i,i + k] = fdcoeff1[m][ k]
				end
				if 1 ≤ i - k
					mat[i,i - k] = fdcoeff1[m][-k]
				end
			end
		end
	else
		for i in 1:n
			mat[i,i] = fdcoeff1[m][0]
			for k in 1:m
				mat[i,cycindex(i + k, n)] = fdcoeff1[m][ k]
				mat[i,cycindex(i - k, n)] = fdcoeff1[m][-k]
			end
		end
	end
	mat ./= dh
	return mat
end
function makefd2mat(n::Integer, dh, order = 2; bc = :Zero)
	@assert order in Set([2,4,6,8])
	@assert bc in Set([:Zero, :Periodic])
	mat = zeros(typeof(float(dh)), (n, n))
	m = div(order, 2)
	if bc == :Zero
		for i in 1:n
			mat[i,i] = fdcoeff2[m][0]
			for k in 1:m
				if i + k ≤ n
					mat[i,i + k] = fdcoeff2[m][ k]
				end
				if 1 ≤ i - k
					mat[i,i - k] = fdcoeff2[m][-k]
				end
			end
		end
	else
		for i in 1:n
			mat[i,i] = fdcoeff2[m][0]
			for k in 1:m
				mat[i,cycindex(i + k, n)] = fdcoeff2[m][ k]
				mat[i,cycindex(i - k, n)] = fdcoeff2[m][-k]
			end
		end
	end
	mat ./= dh^2
	return mat
end


ENV["GKSwstype"] = "100" #src

# # Density Estimation
#
# Understanding the shape of your data is important for any data analysis.
#
# - Do you have continuous data? Categorical data? Both?
# - Are variables correlated?
# - What insights can you gain from univariate and bivariate data summaries?
#
# There are many ways to estimate and visualize the distribution of data.  In this module,
# we'll cover: TODO
#
# ## Empirical Density Estimation
#
# ### Empirical Cumulative Density Function (ECDF)
#
# The **`StatsBase`** package provides the `ecdf` function, which returns a function that
# can calculate the ECDF evaluated at a point, defined as
#
# $$f_{\text{ECDF}}(x) = \frac{\text{# values less than or equal to x}}{\text{total # values}}.$$
#
# In other words, the number returned by the ECDF is the probability that a random sample
# from the dataset is less than or equal to the argument.

using StatsBase, Plots

y = 1:10

f = ecdf(y)

plot(f, 0, 11, label="", xlab="x", ylab = "P(data <= x)")

# ### Univariate Data
#
# First we will look at how to estimate/plot univariate distributions of data.
# As we saw in the intro module, we can plot histograms of continuous datafrom the
# **`StatsBase`** package.

using StatsBase, Plots

y = randn(10_000)

h = fit(Histogram, y; closed = :left)

plot(h)

# Alternatively, we can create the plot with the `histogram` function from ``*Plots*``.

histogram(y)

# #### Kernel Density Estimation (KDE)
#
# Kernel Density Estimation (KDE) is a method of estimating the probability density function
# by averaging together a kernel function applied to neighboring points.  A KDE with kernel
# $K$ and bandwidth parameter $h>0$ is a function of data points $(x_1,\ldots,x_n)$ that looks like
#
# $$\hat f(x) = \frac{1}{nh} \sum_{i=1}^n K\left(\frac{x-x_i}{h}\right).$$
#
# Here are some examples of kernel functions:
#
# ![](https://user-images.githubusercontent.com/8075494/30523575-acd48de2-9bb1-11e7-8f0f-3ce2ab09c713.png)
#
# With the **`KernelDensity`** package, let's plot some KDEs with a variety of bandwidths
# using the default (Normal) kernel.  Note that the bandwidth choice has a very big effect
# on the shape of the estimated density.

using KernelDensity, StatsPlots

k = kde(y)

p = plot(k, label = "Bandwidth: Auto", w=2)

for bw in .1:.2:.9
    plot!(p, kde(y; bandwidth = bw), label = "Bandwidth: $bw", w=2)
end

histogram!(p, y, alpha = .1, normed=true)

# #### Averaged Shifted Histograms (ASH)
#
# The Averaged Shifted Histogram (ASH) estimator is a bit of a misnomer; It is essentially
# kernel density estimation performed on a fine-partition (many small bins) histogram.
#
# ASH has an advantage over KDEs in terms of performance since the density is calculated
# over the bins instead of all observations.  Similarly, ASH has an advantage over histograms
# in that smoothing the density over many small bins gives a more fine-grained view of the
# distribution.

using AverageShiftedHistograms

a = ash(y)

plot(a)

# The line is a bit noisy, but we can increase the amount of smoothing with parameter `m`

ash!(a; m = 15)

plot(a)

# But make sure you don't oversmooth!

ash!(a; m = 50)
plot(a)

# Let's compare all three of our density estimates.

plot(ash!(a, m = 15), hist=false)
histogram!(y, normed=true, alpha=.2, label = "Histogram")
plot!(k, label = "KDE")

# #### Categorical Data

using OnlineStats

y = rand([1,2,2,3,3,3,4,4,5], 10^5)

o = fit!(CountMap(Int), y)

plot(o)

# ### Bivariate Relationships
#
# So far, we've only looked at univariate distributions.  However, we can also use
# Histograms, KDEs, and ASH estimators on bivariate data.

x = randn(10^4)
y = x .+ randn(10^4)

h = fit(Histogram, (x,y); closed = :left)
k = kde((x,y))
a = ash(x, y)

plot(
    plot(h; title="Histogram"),
    plot(k; title="KDE"),
    plot(a; title = "ASH");
    layout=3
)

# ## Parametric Density Estimation
#
# Suppose you already know some things about a dataset's distribution or you want to see
# how well the data fits a specific distribution.  Here we'll use the **`Distributions`**
# package to fit data to distributions using maximum likelihood estimation (MLE).  We
# also need the **`StatsPlots`** package for plotting the distributions.

using Distributions

# In **`Distributions`**, each distribution is its own type, and there are a variety of
# methods that work for any distribution

for dist in [Normal(0,1), Gamma(5, 1), Poisson(5)]
    println("The probability density function for $dist evaluated at 0 is $(pdf(dist, 0))")
end

# The `fit` function can be used to pick a reasonable method of fitting the distribution
# (typically MLE).  MLE can be explicitly chosen using the `fit_mle` function.

y = 20 .+ randn(1000)

histogram(y, normed=true, label = "Histogram", xlim = extrema(y))

plot!(fit(Normal, y), label = "Normal Fit")
plot!(fit(Gamma, y), label = "Gamma Fit")


using TuringModels, StatsFuns

Turing.setadbackend(:reverse_diff);
#Turing.turnprogress(false);

d = DataFrame(CSV.read(joinpath(@__DIR__, "..", "..", "data", "chimpanzees.csv"),
  delim=';'));
size(d) # Should be 504x8

# pulled_left, condition, prosoc_left
@model m10_3(y, x₁, x₂) = begin
    α ~ Normal(0, 10)
    βp ~ Normal(0, 10)
    βpC ~ Normal(0, 10)

    for i ∈ 1:length(y)
        p = logistic(α + (βp + βpC * x₁[i]) * x₂[i])
        y[i] ~ Binomial(1, p)
    end
end;

chns = sample(m10_3(d[:,:pulled_left], d[:,:condition], d[:,:prosoc_left]),
  Turing.NUTS(0.95), 2000);

# Rethinking result

m_10_03t_result = "
      Mean StdDev lower 0.89 upper 0.89 n_eff Rhat
 a    0.05   0.13      -0.15       0.25  3284    1
 bp   0.62   0.22       0.28       0.98  3032    1
 bpC -0.11   0.26      -0.53       0.29  3184    1
";

# Describe the draws

describe(chns)

# End of m10.03t.jl


"""
    distance(x, y)

Computes euclidian distance between `x` and `y`.
"""
distance(x, y) = norm(x .- y, 2)

"""
    direction(x, y; normed = true)

Computes direction from `x` to `y`.
"""
function direction(x, y; normed = true)
    d = y .- x
    return normed ? d./norm(d) : d
end

"""
    direction_angle(d)

Computes angle in radians of the direction `d`.
"""
direction_angle(d) = atan(d[2], d[1])

"""
    direction_angle(x, y)

Computes angle in radians of the direction from `x` to `y`.
"""
direction_angle(x, y) = atan(direction(x, y))

"""
    euclidian(r::Real, ϕ::Real, origin = (0, 0))

Computes euclidian coordinates from the given polar coordinates coordinates `(r, ϕ)`.
"""
function euclidian(r::Real, ϕ::Real, origin = (0, 0))
    r >= 0 || throw(ArgumentError("r must be greater or equal to 0."))
    ϕ = mod1(ϕ + π, 2π) - π
    return origin .+ r .* (cos(ϕ), sin(ϕ))
end

"""
    polar(x::Real, y::Real)

Computes polar coordinates from the given 2D euclidian coordinates `(x, y)`.
"""
polar(x::Real, y::Real) = (hypot(x, y), atan(y, x))


rtol = 1e-2

# sunrise at equator
date = Dates.DateTime(2020, 2, 20, 6, 11, 0)
lon, lat = [FT(0.0), FT(0.0)]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test sza ≈ π/2 rtol=rtol

# solar noon at equator
date = Dates.DateTime(2020, 2, 20, 12, 14, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test azi ≈ 3π/2 rtol=rtol

# sunset at equator
date = Dates.DateTime(2020, 2, 20, 18, 17, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test sza ≈ π/2 rtol=rtol

# solar noon at equator, eot correction = false
date = Dates.DateTime(2020, 2, 20, 12, 0, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set; eot_correction=false)
@test azi ≈ 3π/2 rtol=rtol

# sunset at equator, eot correction = false
date = Dates.DateTime(2020, 2, 20, 18, 0, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set; eot_correction=false)
@test sza ≈ π/2 rtol=rtol

## Test Polar Night
# polar night NH 1
date = Dates.DateTime(2020, 12, 20, 11, 0, 0)
lon, lat = [FT(0.0), FT(80.0)]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test sza > π/2

# polar night NH 2
date = Dates.DateTime(2020, 12, 20, 23, 0, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test sza > π/2

# polar night SH 1
date = Dates.DateTime(2020, 6, 20, 11, 0, 0)
lon, lat = [FT(0.0), FT(-80.0)]
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test sza > π/2

# polar night SH 2
date = Dates.DateTime(2020, 6, 20, 23, 0, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test sza > π/2

## Test Distance
date = Dates.DateTime(2000, 3, 22, 0, 0, 0)
sza, azi, d = instantaneous_zenith_angle(date, lon, lat, param_set)
@test d ≈ orbit_semimaj(param_set) rtol=rtol


using PQBaseCamp
using DataFrames
using CSV

# make an exponential weight model -
function ew(data::DataFrame, map::Pair{Symbol,Symbol})::Array{Float64,1}

    # initialize -
    ω_array = Array{Float64,1}()
    α = 0.001

    # how many rows do we have?
    (number_of_rows, _) = size(data)
    for row_index ∈ 1:(number_of_rows - 1)

        value = (1 - α)^(row_index - 1)
        push!(ω_array, value)
    end

    # scale - so that we sum to 1
    T = sum(ω_array)
    return reverse((1 / T) * ω_array)
end

# load up a sample data file -
path_to_data_file = "$(pwd())/test/data/MRNA.csv"
df = CSV.read(path_to_data_file, DataFrame)

# what is the w?
ω_array = ew(df, :timestamp => :close)

# build a computation model -
model = LogReturnComputationModel()
model.ticker = "MRNA"
model.data = df
model.map = :timestamp => :close
Δ_table = Δ(model)


function DolittleLU(A::Array; pivot=true)

  #A = the matrix of coefficients 'A' must be squared
  #B = The Vector 'B' right hand side vector Vertical
    m,n =size(A)

    if m != n
        error("Matrix must be squared")
    end

    # System of equations
    U = A
    P=Matrix{Float64}(I,m,m)
    p=zeros(m)
    L=zeros(m,m)
    #Forward Elimination
    for j=1:m-1
        if pivot==true
        k=argmax(abs.(U[j:m,j]))   #Find the Max value in the colum k to change the order
        Ind=j+k-1                  #Indicates de index of the row to change
        U[[j,Ind],:]=U[[Ind,j],:]  #Change the order of rows
        P[[j,Ind],:]=P[[Ind,j],:]
        L[[j,Ind],:]=L[[Ind,j],:]
        end
       for i=j+1:m

            f=U[i,j]/U[j,j]
            L[i,j]=f
            U[i,:]=U[i,:].-f.*U[j,:]
        end
    end
    for i=1:m
        L[i,i]=1
        p[i]=argmax(P[i,:])
    end
    U=UpperTriangular(U)
    L=LowerTriangular(L)

    return L,U, P, p
end


"""
A Julia Package for handling Spherical Geometry. Spherical geometry is the
geometry of the two-dimensional surface of a sphere. This package only
handles geometries generated using great circle sections (arcs).

Angles are specified in [deg].

Types:
* Point: specified by latitude ϕ [deg] and longitude λ [deg]
* Arc: specified as the shortest great circle between two points.
* Line: is a great circle line specified by a point and azimuth [deg]
* Arcs: is a string of continuous line sections defined by a set of points.
* Polygon: is a spherical polygon defined by a set of points.

It includes the calculation of:
* The angular distance to a point, line, arc, multi-arc, or polygon border
* The along line angular distance between a point and a line.
* The intersection points between lines, arcs, multi-arcs, and polygon borders.
* The self intersection points of multi-arcs and polygon borders
* The bounding box of a given polygon or set of arcs.
* The convexhull of a given polygon.
* The normalized point
* The (final) azimuth [deg] between two points
* The spherical angle [deg] and spherical excess [deg] between three points
* The midpoint between two points, of an arc, of an arcs
* The intermediate point at a given fraction between two points, of an arc, of an arcs
* The destination point given a start point, a direction and distance.
* The intersection points of arcs, and polygons
* The self intersection points of arcs or a polygon
* The highest/lowest latitude (point) of a great circle
* The area of a polygon/spherical triangle given a radius

And testing if:
* a point, arc, arcs or polygon is inside a polygon
* a point is on a line, arc, arcs or polygon border (within tolerance)
* a polygon/arcs is self-isselfintersecting
* a polygon is simple, complex, convex or concave
"""
module SphericalGeometry

import Base:(-)
import Base:(*)
import Base.Math.rad2deg, Base.Math.deg2rad
import LinearAlgebra.cross, LinearAlgebra.dot, LinearAlgebra.norm

include("types.jl")
include("utility.jl")
include("angles.jl")
include("distances.jl")
include("points.jl")
include("operations.jl")
include("area.jl")
end

#TODO Check https://spacetelescope.github.io/spherical_geometry/index.html for additional functions
#TODO Intersection ∩ of polygons
#TODO Union ∪ of polygons
#TODO Substraction - of polygons
#TODO isclockwise
#TODO iscounterclockwise


# Patrick Keast, MODERATE-DEGREE TETRAHEDRAL QUADRATURE FORMULAS
# http://mech.fsv.cvut.cz/oofem/resources/doc/oofemrefman/gaussintegrationrule_8C_source.html

function (::Type{QuadratureRule{Tetrahedron}})(quad_type::Gauss, order::Int)
    if order == 0 || order == 1
        points = [Tensors.Vec{3}([1.0/4.0, 1.0/4.0, 1.0/1.4])]
        weigths = [1.0/6.0]
    elseif order == 2
        a = ( 5. + 3. * √(5.) ) / 20.
        b = ( 5. - √(5.) ) / 20.
        w = 1. / 24.
        points = [Tensors.Vec{3}([a,b,b]),
                  Tensors.Vec{3}([b,a,b]),
                  Tensors.Vec{3}([b,b,a]),
                  Tensors.Vec{3}([b,b,b])]
        weigths = [w,w,w,w]
    elseif order == 3
        a1 = 1. / 4.
        a2 = 1. / 2.
        b2 = 1. / 6.
        w1 = -2. / 15.
        w2 = 3. / 40.
        points = [Tensors.Vec{3}([a1,a1,a1]),
                  Tensors.Vec{3}([a2,b2,b2]),
                  Tensors.Vec{3}([b2,a2,b2]),
                  Tensors.Vec{3}([b2,b2,a2]),
                  Tensors.Vec{3}([b2,b2,b2])]
        weigths = [w1,w2,w2,w2,w2]
    elseif order == 4
        a1 = 1. / 4.;
        w1 = -74. / 5625.;

        a2 = 5. / 70.;
        b2 = 11. / 14.;
        w2 = 343. / 45000.;

        a3 = ( 1. + √(5. / 14.) ) / 4.;
        b3 = ( 1. - √(5. / 14.) ) / 4.;
        w3 = 28. / 1125.;
        points = [Tensors.Vec{3}([a1,a1,a1]),
                  Tensors.Vec{3}([b2,a2,a2]),
                  Tensors.Vec{3}([a2,b2,a2]),
                  Tensors.Vec{3}([a2,a2,b2]),
                  Tensors.Vec{3}([a2,a2,a2]),
                  Tensors.Vec{3}([a3,a3,b3]),
                  Tensors.Vec{3}([a3,b3,a3]),
                  Tensors.Vec{3}([a3,b3,b3]),
                  Tensors.Vec{3}([b3,a3,a3]),
                  Tensors.Vec{3}([b3,a3,b3]),
                  Tensors.Vec{3}([b3,b3,a3])]

        weigths = [w1,w2,w2,w2,w2,w3,w3,w3,w3,w3,w3]
    else
        throw(ArgumentError("Gauss rule of order $order for tetrahedron not available"))
    end
    return QuadratureRule{Tetrahedron,3,Float64}(weigths, points)
end


module MySignalProcessing|

# Write your package code here.

# Dependancies
include("types.jl")
using .types: signal

# imports

# Importiong packages that will then be extended.
import Base.+
import Base.*
import DSP

#exports
export impseq
export signal
export stepseq
export sigshift
export realexp
export sigadd
export sigmult
export sigrand
export sigscale
export sigfold
export energy
export +
export *
export sinseq
export sinseqmn
export conv
export cexp

"""
    δ(start::Int, stop::Int)

Unit impulse signal
This function has the form of :

    δ(n) = 1 if n=0
         = 0 if n!=0

# parameters
    `start:: Int` : start of the impulse sequence n (horizontal axis)
    `stop:: Int` : stop of the impulse sequence n (horizontal axis)

# returns
    s::signal
"""
function impseq(start::Int, stop::Int)
    s = signal(0.0, start,stop); #constructor for initial signal

    for x in 1:length(s.n)

        s.A[x] = Float64(s.n[x] == 0) # elegant way to return 1

    end

    return s

end

"""
    stepseq(start::Int, stop::Int)

Unit Step Sequence
This function has the form of :

    stepseq(n) = 1 if n ≥ 0
                = 0 if n < 0

# parameters
    `start:: Int` : start of the sampple position n (horizontal axis)
    `stop:: Int` : stop of the sample position n (horizontal axis)

# returns
    s::signal

"""
function stepseq(start::Int, stop::Int)
    s = signal(0.0, start,stop); #constructor for initial signal

    for x in 1:length(s.n)

        s.A[x] = Float64(s.n[x] >= 0) # elegant way to return 1

    end

    return s

end

"""
    _sigshift(s::signal, m::Int)

Function to shift signal s by adding m to each element of s.n
It implements y(n) = x(n-n₀)

# parameters
    `s::signal` : signal to be shifted
    `n₀::Int` : shift amount

# returns
    s'::signal shifted
"""
function _sigshift(s::signal, n₀::Int)
    y = s.A;
    n = s.n .+ n₀;
    s = signal(y, n);
    return s

end

"""
    sigshift(n₀::Int)

# parameters
    `n₀::Int` : shift amount

# returns
    s:: anonimous  function that shifts a signal (designed to be composable)

"""
function sigshift(n₀::Int)

    return (sig -> _sigshift(sig, n₀))

end

"""
    realexp(a::Real, n₀::Int , nf::Int)

Function to generate a real exponential signal.

Signal of the form:
```math
r(a, n) = a^n
```

# parameters
    `a::Real` : real-valued parameter of the exponential
    `n₀::Int` : start  the sample positon signal
    `nf::Int` : stop  of the sample positon  signal

# returns
    s::signal

 """
function realexp(a::Real, n₀::Int, nf::Int)
    s = signal(0.0, n₀, nf); #constructor for initial signal

    for x in 1:length(s.n)

        s.A[x] = a^(s.n[x])

    end

    return s

end

"""
    sigadd(s1::signal, s2::signal)

Function to add two signal s1 and s2.
pls note that the signals must have the same length
If sequences are of unequal lenghts or if the sample positions are
    different for equal-length sequences, the we cannot directly add
        We have to first augment x1(n) and x2(n) , so that they have the same
         position vector n (and hence the same length)

# parameters
    `s1::signal` : signal to be added
    `s2::signal` : signal to be added

# returns
    `s::signal` : signal s1 + s2

"""
function sigadd(s₁::signal  , s₂::signal)

    n₀ =min(minimum(s₁.n), minimum(s₂.n)) # find the minimum sample position
    nf = max(maximum(s₁.n), maximum(s₂.n)) # find the maximum sample position
    n=n₀:nf; # create the sample position vector

    y₁ = signal(0.0, n₀, nf); #constructor for initial signal
    y₂ = signal(0.0, n₀, nf); #constructor for initial signal

    filter1 = findall((n .≥ minimum(s₁.n)) .& (n .≤ maximum(s₁.n)) .== 1)
    filter2 = findall((n .≥ minimum(s₂.n)) .& (n .≤ maximum(s₂.n)) .== 1)

    y₁.A[filter1] = s₁.A; # fill the signal with the values of s₁
    y₂.A[filter2] = s₂.A; # fill the signal with the values of s₂

    s = signal(0.0, n₀, nf); #constructor for initial signal
    s.A = y₁.A + y₂.A; # add the two signals

    return s

end

"""
    Base.+(s₁::signal  , s₂::signal)

Extends Base.+ to add two signal objects together.

# Arguments

    `s₁::signal` : signal to be added
    `s₂::signal` : signal to be added

# Returns
    `s::signal` : signal s1 + s2
"""
function +(s₁::signal  , s₂::signal)
    s = sigadd(s₁ , s₂)
    return s
end

"""
    sigmult(s1::signal, s2::signal)

Function to multiply two signal s1 and s2.
pls note that the signals must have the same length
If sequences are of unequal lenghts or if the sample positions are
    different for equal-length sequences, the we cannot directly add
        We have to first augment x1(n) and x2(n) , so that they have the same
         position vector n (and hence the same length)

# parameters
    `s1::signal` : signal to be added
    `s2::signal` : signal to be added

# returns
    `s::signal` : signal s1 * s2

"""
function sigmult(s₁::signal  , s₂::signal)

    n₀ =min(minimum(s₁.n), minimum(s₂.n)) # find the minimum sample position
    nf = max(maximum(s₁.n), maximum(s₂.n)) # find the maximum sample position
    n=n₀:nf; # create the sample position vector

    y₁ = signal(0.0, n₀, nf); #constructor for initial signal
    y₂ = signal(0.0, n₀, nf); #constructor for initial signal

    filter1 = findall((n .≥ minimum(s₁.n)) .& (n .≤ maximum(s₁.n)) .== 1)
    filter2 = findall((n .≥ minimum(s₂.n)) .& (n .≤ maximum(s₂.n)) .== 1)

    y₁.A[filter1] = s₁.A; # fill the signal with the values of s₁
    y₂.A[filter2] = s₂.A; # fill the signal with the values of s₂

    s = signal(0.0, n₀, nf); #constructor for initial signal
    s.A = y₁.A .* y₂.A; # add the two signals

    return s

end

"""
    *(s₁::signal  , s₂::signal)

Multiples two signal objects together.

# Arguments

    `s₁::signal` : signal to be multiplied
    `s₂::signal` : signal to be Multiples

# Returns
    `s::signal` : signal s1 * s2

"""
function *(s₁::signal  , s₂::signal)
    s = sigmult(s₁ , s₂)
    return s
end

"""
    sigrand(start::Int, stop::Int)

Function to generate a random signal starting at sample position start and ending at sample position stop.
The function generates a length N random sequence whose elements are uniformely distributed between 0 and 1.

# parameters
    `start::Int` : start  the sample positon signal
    `stop::Int` : stop  of the sample positon  signal

# returns
    s::signal

"""
function sigrand(start::Int, stop::Int)
    n = start:stop |> collect; # create the sample position vector
    y = rand.(length(n)); # generate a random sequence
    s = signal(y, n); #constructor for initial signal
    return s

end

"""
    _sigscale(s::signal, α::Real)

Each signal sample is multiple by a scalar α

# parameters
    `s::signal` : signal to be scaled
    `α::Real` : scalar

# returns
    `s::signal` : scaled signal
"""
function _sigscale(s::signal, α::Real)

    s1 = signal(s.A .* α, s.n); #constructor for initial signal

    return s1

end

"""
    sigscale(α::Real)

Each signal sample is multiple by a scalar α

# parameters
    `α::Real` : scalar

# returns
    anonimous function that takes a signal as argument and returns
         scaled signal (designed to be composable)

"""
function sigscale(α::Real)

    return (sig -> _sigscale(sig, α))
end

"""
    *(s₁::signal  , α::Real)

    scale signal by scalar.

# Arguments

    `s₁::signal` : signal to be multiplied
    `α::Real` : scalar

# Returns
    `s::signal` : signal s1 * α

"""
function *(s₁::signal  , α::Real)
    s = s₁ |> sigscale(α);
    return s
end

"""
    *(α::Real, s₁::signal)

scale signal by scalar.

# Arguments

    `s₁::signal` : signal to be multiplied
    `α::Real` : scalar

# Returns
    `s::signal` : signal s1 * α

"""
function *(α::Real, s₁::signal)
    s = s₁ |> sigscale(α);
    return s
end

"""
    sigfold(s::signal)

In this operation, each sample of x(n) is flipped around n=0 to obtain a folded sequence  y(n)

y(n) = {x(-n)}

# parameters
    `s::signal` : signal to be folded

# returns
    `s::signal` : folded signal

"""
function  sigfold(s::signal)
    s1 = signal(s.A, s.n); #constructor for initial signal
    s1.A = reverse(s.A);
    s1.n = -reverse(s.n);
    return s1
end

"""
    energy(s::signal)

Calculate the energy of a signal
The energy of a signal is given by the sum of the
    absolute squared values of the signal samples

# parameters
    `s::signal` : input signal

# returns
    `e::Real` : energy of the signal

"""
function energy(s::signal)
    e = sum(abs.(s.A) .^ 2);
    return e
end

"""
    sinSeq(start::Int, stop::Int, ω::Real, φ::Real)

Sinusoidal function

# parameters
    `start::Int` : start  the sample positon signal
    `stop::Int` : stop  of the sample positon  signal
    `ω::Real` : frequency of the sinusoid in radians
    `φ::Real` : phase of the sinusoid, in radians

# returns
    s::sinusoidal signal

"""
function sinseq(start::Int, stop::Int, ω::Real, φ::Real= 0.0)
    n = start:stop |> collect; # create the sample position vector
    y = sin.(n .* ω .+ φ); # generate a sinusoidal sequence
    s = signal(y, n); #constructor for initial signal
    return s
end

"""
    sinSeq(start::Int, stop::Int, M::Int,N::Int, φ::Real)

Sinusoidal function with more specific constructor

# parameters
    `start::Int` : start  the sample positon signal
    `stop::Int` : stop  of the sample positon  signal
    `M::Int` : multiple of 2π
    `N::Int` : number of periods before pattern repeats itself

# returns
    s::sinusoidal signal

"""
function sinseqmn(start::Int, stop::Int, M::Int,N::Int, φ::Real= 0.0)
    n = start:stop |> collect; # create the sample position vector
    y = sin.(n .* ((M / N)*2π .+ φ)); # generate a sinusoidal sequence
    s = signal(y, n); #constructor for initial signal
    return s
end

"""
    conv(s₁::signal, s₂::signal)

Convolution function

# parameters
    `s₁::signal` : input signal
    `s₂::signal` : impulse response signal

# returns
    s::convolution signal output

"""
function conv(s₁::signal, s₂::signal)
    n₁ = s₁.n;
    n₂ = s₂.n;
    A₁ = convert(Array{Float64,1}, s₁.A) #conv requires more specific type
    A₂ = convert(Array{Float64,1}, s₂.A) #conv requires more specific type

    Nyb = n₁[1] + n₂[1];
    Nye = n₁[length(A₁)] + n₂[length(A₂)];

    ny = collect(Nyb: Nye);
    y = DSP.conv(A₁, A₂);

    return signal(y, ny);
  end

  """
  function cexp(A::Vector, k, N)

      params:
          - A: Vector of amplitudes
          - n: element index within each Vector, from 0 .. N-1
          - k: index indicating each vector , from 0 .. N-1
          - N: dimension of signal (number of vectors)

      returns:
          - complex exponential signal

  """
  function cexp(A::Vector, k, N = 64)

      n = [n for n in range(0, N-1)]

      w = [exp(((2pi/64)*im)*n*k) for n in range(0, N-1)]
      s = A .*w

      return signal(s, n)

  end

end


module GridCreation

export CFFT, CInterSample, mkCartVecs, cartVecsToPolarGrid

const CFFT = -1;
const CInterSample = -2;

# this function is lifted from pkg Fourier.
# A little copying is better than a little dependency.
function _fftrange(n)
    if iseven(n)
        return -(n ÷ 2):(n÷2-1)
    end
    return -(n ÷ 2):(n÷2)
end
"""
    mkCartVecs(dx, size[, center])

Create a 2D "grid" by returning a row vector x and column vector y.

The grid will have sample spacing dx, size[0] == size(x), and size[1] == size(y).

If size is an int, it is broadcast to [size,size].

The grid will be centered according to such that the center element contains zero.  Special
values for center include -1 (default, FFT-like center) and -2, for "interpixel"
sampling, where 0 is at size/2.  Otherwise, zero is at the centerth element.

The centering rule may not be different between x and y.

# Examples

## FFT-aligned grid

Note that the ceil rounded center element contains zero.
```julia-repl
julia> x,y = mkCartVecs(.1, 4); # implicit center=CFFT
julia> x
1×4 LinearAlgebra.Adjoint{Float64,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}:
 -0.2  -0.1  0.0  0.1
julia> collect(y)
4-element Array{Float64,1}:
 -0.2
 -0.1
  0.0
  0.1
```

## Intersample aligned grid
```julia-repl
x,y=mkCartVecs(.1, 4, center=CInterSample);

julia> x
1×4 LinearAlgebra.Adjoint{Float64,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}:
 -0.15  -0.05  0.05  0.15
```

## index-centered grid
```
julia> x,y=mkCartVecs(.1, 4, center=1);
julia> x
1×4 LinearAlgebra.Adjoint{Float64,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}:
 1.11022e-17  0.1  0.2  0.3
```
"""
function mkCartVecs(dx, size::Tuple{Integer,Integer}; center::Integer=CFFT)
    # fftrange produces a range object.  dot add shifts the range
    # and multiplication scales it.
    if center == CFFT
        X = (_fftrange(size[2])*dx)';
        Y = _fftrange(size[1])*dx;
    elseif center == CInterSample
        X = (_fftrange(size[2])*dx.+(dx/2))';
        Y = (_fftrange(size[1])*dx.+(dx/2));
    else
        X = _fftrange(size[2])*dx;
        Y = _fftrange(size[1])*dx;
        X = (X .- X[center])';
        Y = (Y .- Y[center]);
    end
    return X, Y;
end

function mkCartVecs(dx, size::Integer; center::Integer=CFFT)
    return mkCartVecs(dx, (size,size), center=center);
end

"""
    cartVecsToPolarGrid(X, Y)

Construct a polar grid (ρ,θ) from the row vector X and column vector Y.  Returns
a pair of 2D arrays containing the radial and azimuthal coordinates.

# Examples
```julia-repl
julia> X=(-2:1)'; Y=-2:1;
julia> ρ,θ=cartVecsToPolarGrid(X,Y);
julia> ρ
4×4 Array{Float64,2}:
 2.82843  2.23607  2.0  2.23607
 2.23607  1.41421  1.0  1.41421
 2.0      1.0      0.0  1.0
 2.23607  1.41421  1.0  1.41421
julia> θ
4×4 Array{Float64,2}:
 -2.35619  -2.03444  -1.5708  -1.10715
 -2.67795  -2.35619  -1.5708  -0.785398
  3.14159   3.14159   0.0      0.0
  2.67795   2.35619   1.5708   0.785398
```
See also: [`mkCartVecs`](@ref)
"""
function cartVecsToPolarGrid(X, Y)
    # I don't think allocating a meshgrid is avoidable here.
    # the rho matrix can be made with a dot on x and y,
    # but we would need a 2D view into both X and Y for atan
    # grid_x = [x for x in X, y in Y;
    # grid_y = [y for y in Y, x in X;
    ρ = @. sqrt(X^2 + Y^2);
    θ = [atan(y,x) for y in Y, x in X'];
    return ρ, θ
end

end


using ModelingToolkit
using Test

# Define some variables
@parameters t σ ρ β
@variables x(t) y(t) z(t)
@derivatives D'~t

# Define a differential equation
eqs = [D(x) ~ σ*(y-x),
       D(y) ~ x*(ρ-z)-y,
       D(z) ~ x*y - β*z]
de = DiffEqSystem(eqs,t,[x,y,z],[σ,ρ,β])
generate_function(de)
generate_function(de;version=ModelingToolkit.SArrayFunction)
jac_expr = generate_jacobian(de)
jac = calculate_jacobian(de)
f = ODEFunction(de)
ModelingToolkit.generate_ode_iW(de)

# Differential equation with automatic extraction of variables
de2 = DiffEqSystem(eqs, t)

function test_vars_extraction(de, de2)
    @test isequal(de.iv, de2.iv)
    for el in (:dvs, :ps)
        names2 = sort(collect(var.name for var in getfield(de2,el)))
        names = sort(collect(var.name for var in getfield(de,el)))
        @test names2 == names
    end
end
test_vars_extraction(de, de2)

@testset "time-varying parameters" begin
    @parameters σ′(t)
    eqs = [D(x) ~ σ′*(y-x),
           D(y) ~ x*(ρ-z)-y,
           D(z) ~ x*y - β*z]
    de = DiffEqSystem(eqs,t,[x,y,z],[σ′,ρ,β])
    @test begin
        f = eval(generate_function(de))
        du = [0.0,0.0,0.0]
        f(du, [1.0,2.0,3.0], [x->x+7,2,3], 5.0)
        du ≈ [12, -3, -7]
    end
end

# Conversion to first-order ODEs #17
@derivatives D3'''~t
@derivatives D2''~t
@variables u(t) u_tt(t) u_t(t) x_t(t)
eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1
       D2(x) ~ D(x) + 2]
de = DiffEqSystem(eqs, t)
de1 = ode_order_lowering(de)
lowered_eqs = [D(u_tt) ~ 2u_tt + u_t + x_t + 1
               D(x_t)  ~ x_t + 2
               D(u_t)  ~ u_tt
               D(u)    ~ u_t
               D(x)    ~ x_t]
@test de1.eqs == convert.(ModelingToolkit.DiffEq, lowered_eqs)
@test isequal(de1.dvs, [u_tt, x_t, u_t, u, x])
@test isequal(de1.iv, t)
du = zeros(5)
ODEFunction(de1)(du, ones(5), nothing, 0.1)
@test du == [5.0, 3.0, 1.0, 1.0, 1.0]

# Internal calculations
a = y - x
eqs = [D(x) ~ σ*a,
       D(y) ~ x*(ρ-z)-y,
       D(z) ~ x*y - β*z]
de = DiffEqSystem(eqs,t,[x,y,z],[σ,ρ,β])
generate_function(de)
jac = calculate_jacobian(de)
f = ODEFunction(de)

# Define a nonlinear system
eqs = [0 ~ σ*(y-x),
       0 ~ x*(ρ-z)-y,
       0 ~ x*y - β*z]
ns = NonlinearSystem(eqs,[x,y,z],[t,σ,ρ,β])
ns2 = NonlinearSystem(eqs)
for el in (:vs, :ps)
    names2 = sort(collect(var.name for var in getfield(ns2,el)))
    names = sort(collect(var.name for var in getfield(ns,el)))
    @test names2 == names
end

generate_function(ns)

@derivatives D'~t
@parameters A B C
_x = y / C
eqs = [D(x) ~ -A*x,
       D(y) ~ A*x - B*_x]
de = DiffEqSystem(eqs,t,[x,y],[A,B,C])
test_vars_extraction(de, DiffEqSystem(eqs,t))
test_vars_extraction(de, DiffEqSystem(eqs))
@test begin
    f = eval(generate_function(de))
    du = [0.0,0.0]
    f(du, [1.0,2.0], [1,2,3], 0.0)
    du ≈ [-1, -1/3]
end

# Now nonlinear system with only variables
@variables x y z
@parameters σ ρ β

# Define a nonlinear system
eqs = [0 ~ σ*(y-x),
       0 ~ x*(ρ-z)-y,
       0 ~ x*y - β*z]
ns = NonlinearSystem(eqs, [x,y,z], [σ,ρ,β])
jac = calculate_jacobian(ns)
@testset "nlsys jacobian" begin
    @test isequal(jac[1,1], σ * -1)
    @test isequal(jac[1,2], σ)
    @test isequal(jac[1,3], 0)
    @test isequal(jac[2,1], ρ - z)
    @test isequal(jac[2,2], -1)
    @test isequal(jac[2,3], x * -1)
    @test isequal(jac[3,1], y)
    @test isequal(jac[3,2], x)
    @test isequal(jac[3,3], -1 * β)
end
nlsys_func = generate_function(ns)
jac_func = generate_jacobian(ns)
f = @eval eval(nlsys_func)

# Intermediate calculations
# Define a nonlinear system
eqs = [0 ~ σ*a,
       0 ~ x*(ρ-z)-y,
       0 ~ x*y - β*z]
ns = NonlinearSystem(eqs,[x,y,z],[σ,ρ,β])
nlsys_func = generate_function(ns)
jac = calculate_jacobian(ns)
jac = generate_jacobian(ns)


A = matrixdepot("toeplitz", [1,2,3,4,5])
@test A == matrixdepot("toeplitz", 5)

B = matrixdepot("toeplitz", [1,2,3], [1,4,5])
@test B[1,3] == 5
@test B[3,1] == 3
@test B[1,2] == 4
println("'toeplitz' passed test...")

n = rand(2:10)
A = matrixdepot("prolate", n)
@test issym(A)

println("'prolate' passed test...")


######################
# This is SemiLinearHeat but using FFTW

using FFTW
x=range(0,stop=2π,length=100)
u(x) = sin(x)
freqs = fft(u.(x))[ 1 : length(x) ÷ 2 + 1]  ## ÷ this means integer division
c= 2*abs.(freqs/length(x))

######################
# instead of the above section
using ApproxFun
s = Fourier()
n = 100
T = ApproxFun.plan_transform(s,n)
Ti = ApproxFun.plan_itransform(s,n)

x = points(s,n)    # 0  to 2*pi
r = (T*(cos.(cos.(x.-0.1))))

der = Derivative(s,2)

####################
# PDE-derived ODE

using Sundials
using Plots; gr()
using ApproxFun
using Sundials
using LinearAlgebra
using LinearMaps

import Base.At_mul_B!

S = Fourier()
n=100
x = points(S,n)
T = ApproxFun.plan_transform(S,n)
Ti = ApproxFun.plan_itransform(S,n)

u0= T*cos.(cos.(x.-0.1))

D2 = Derivative(S,2)
L = D2[1:n,1:n]
tspan = (0.0,10.0)

function heat(du,u,L,t)
    mul!(du,L,u)
end

prob = ODEProblem(heat, u0,tspan,L)
sol = solve(prob)
plot(x,Ti*sol(0.0))
plot!(x,Ti*sol(0.5))
plot!(x,Ti*sol(2.0))
plot!(x,Ti*sol(3.0))
plot!(x,Ti*sol(10.0))

Missing around and learning DifferentialEquations.jl/demos.jl


export ricker, dampedSinusoid, sinc, spectSynth

"""

    ricker(;f::Float64=20.0, n::Int64=100, dt::Float64=0.001, nargout::Bool=false)

Generate a Ricker wavelet.

# Arguments
- `f::Float64,`    : Peak frequnecy
- `n::Int64`    : Number of points
- `dt::Float64`    : Sampling interval

# Output
- `s`::Array{Float64,1}    : Computed Ricker wavelet
- `t`::Array{Float64,1}    : Time axis

"""
function ricker(;f::Float64=20.0, n::Int64=41, dt::Float64=0.05, tpoint::Float64=100.0)
    # generate time axis
    t = timeAxis(dt, 0., n)
    # create the wavelet
    s = (1.0 .- 2π^2 * f^2 .* (t.-tpoint).^2).*exp.(-π^2 * f^2 .* (t.-tpoint).^2)

    return (s, t)
end

"""

    dampedSinusoid(;A::Float64=1.0, ω::Float64=1.0, ϕ::Float64=0.0, n::Int64=100, dt::Float64=0.001, λ::Float64=2.0)

Generate a damped sinusoid.

# Arguments
- `A::Float64`    : Amplitude of the damped sinusoid
- `n::Int64`    : Number of points
- `dt::Float64`    : Sampling interval
- `ω::Float64`    : Frequency
- `λ::Float64`    : Decay constant
- `ϕ::Float64`    : Phase angle at t=0
- `η::Float64`    : Shift in function maximum

# Output
- `s::Array{Float64,1}`    : Computed damped sinusoid
- `t::Array{Float64,1}`    : Time axis

"""
function dampedSinusoid(;A::Float64=1.0, ω::Float64=1.0, ϕ::Float64=0.0, η::Float64=0.0, n::Int64=100, dt::Float64=0.001, t0::Float64=0.0, λ::Float64=2.0)
    # generate time axis
    t = timeAxis(dt, t0, n)
    # damped sinusoidal function
    s = A*exp.(-λ*abs.(t.-η)) .* cos.(ω*t .+ ϕ)

    return (s, t)
end

"""

    sinc(;A::Float64=1.0, ω::Float64=1.0, ϕ::Float64=0.0, n::Int64=100, dt::Float64=0.001, t0=0.0)

Generate a sinc (cardinal-sin) function

# Arguments
- `A::Float64`    : Amplitude of the damped sinusoid
- `n::Int64`    : Number of points
- `dt::Float64`    : Sampling interval
- `ω::Float64`    : Frequency
- `ϕ::Float64`    : Phase angle at t=0

# Output
- `s::Array{Float64,1}`    : Computed sinc
- `t::Array{Float64,1}`    : Time axis

"""
function sinc(;A::Float64=1.0, ω::Float64=1.0, ϕ::Float64=0.0, n::Int64=100, dt::Float64=0.001, t0::Float64=0.0)
    # generate time axis
    t = timeAxis(dt, t0, n)
    # sinc function
    s = A.*Base.sinc.(ω.*t.+ϕ)

    return (s, t)
end

"""

    chirp(;c::Array{Float64,1}=collect(range(0.15, stop=15.0, length=100)), tp::Array{Float64,1}=collect(range(0.04, stop=4.0, length=100)), mintp::Float64=0.04, maxtp::Float64=4.0, dist::Float64=1000.0, n::Int64=500, dt::Float64=0.002, t0::Float64=0.0)

# Arguments
- `c::Array{Float64,1}`    : phase velocity array
- `tp::Array{Float64,1}`    : period array
- `mintp::Float64`    : minimum period
- `maxtp::Float64`    : maximum period
- `dist::Float64`    : distance
- `n::Int64`    : number of samples in arrays
- `dt::Float64`    : sampling interval
- `t0::Float64`    : start time of time vector

# Output
- `s::Array{Float64,1}`    : Computed signal
- `t::Array{Float64,1}`    : Time axis

"""
function chirp(;c::Array{Float64,1}=collect(range(0.15, stop=15.0, length=100)), tp::Array{Float64,1}=collect(range(0.04, stop=4.0, length=100)), mintp::Float64=0.04, maxtp::Float64=4.0, dist::Float64=1000.0, n::Int64=500, dt::Float64=0.002, t0::Float64=0.0)
    # generate time axis
    t = timeAxis(dt, t0, n)
    # generate a chirp
    tp_ind = findall(x->(x≤maxtp && x≥mintp), tp)
    s = [sum(cos.((2π ./ tp[tp_ind]) .* (j .- dist ./ c[tp_ind]))) for j in time]

    return (s, t)
end

"""

    spectSynth(;A::Float64=1.0, n::Int64=100, dt::Float64=0.001, t0=0.0)

Generate a synthetic by definition in the frequency domain and inverse FFT

# Arguments
- `A::Float64`    : Amplitude of the damped sinusoid
- `n::Int64`    : Number of points
- `dt::Float64`    : Sampling interval

# Output
- `s::Array{Float64,1}`    : Computed signal
- `t::Array{Float64,1}`    : Time axis

"""
function spectSynth(;A::Float64=1.0, n::Int64=100, dt::Float64=0.001, t0::Float64=0.0)
    # generate time axis
    t = timeAxis(dt, t0, n)
    # generate a signal with flat amplitude spectrum and random phase in [-π, π]
    # this is of length (n/2)+1 to get a final signal of length n
    N = convert(Int64, floor(length(t)/2)+2)
    spectrum = A.+(((rand(N).-0.5).*2π).*im)
    # inverse FFT
    s = real(irfft(spectrum, n+1))

    return (s[2:end], t)
end

"""

    timeAxis(dt::Float64, t0::Float64, n::Float64)

Create a vector of times given sample frequency, number of points, and start time.

# Arguments
- `dt::Float64`    : Sample interval
- `t0::Int64`    : Start time
- `n::Int64`    : Number of points in the time vector

# Output
- `t::Array{Float64,1}`    : Time axis

"""
function timeAxis(dt::Float64, t0::Float64, n::Int64)
    T = dt * (n-1) # end time
    # create time axis from 0 to end time at dt spacing
    t = collect(0:dt:T) .+ t0

    return t
end


#!/usr/bin/env julia

"""
# Problem 4: Largest palindrome product

The prime factors of 13195 are 5, 7, 13 and 29.

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 `d`-digit numbers.
"""
module Problem004

include("Digits.jl")

"""
    solve(; d::Integer = 3)

Returns the solution for [`Problem004`](@ref) with the given parameters.

## Preconditions
- `d > 0`
"""
function solve(; d::Integer = 3)
    # calculate max and min d-digit numbers
    base = convert(typeof(d), 10)
    max_factor = base^d - oneunit(d)
    min_factor = base^(d - oneunit(d))

    # search for palindromic products of d-digit numbers
    max_product = -oneunit(d)
    for n in max_factor:-oneunit(d):min_factor
        for m in n:-oneunit(d):min_factor
            product = n * m

            # stop checking products with n when we drop below max_product
            if max_product ≥ product
                break
            end

            # if product is a palindrome, it's the max possible for this n
            if Digits.is_palindrome(product)
                max_product = product
                break
            end
        end
    end

    return max_product
end

end


"""
NumDiffTools.gradient(f, x::AbstractArray, [abstol, reltol, h, t, maxiters])

Returns gradient `∇f` of `f` evaluted at `x`, assuming `f` is called as `f(x)`.

Optional Arguments:\n
`abstol`: Target absolute error in derivative estimate (default `1e-12`)\n
`reltol`: Target relative error in derivative estimate (default `1e-12`)\n
`h`: Initial step size (default `0.1`)\n
`t`: Ratio of step sizes for each Richardson iteration (default `2.0001`.  Should not be an exact integer for periodic functions)\n
`maxiters`: Maximum number of Richardson iterations to perform (default `6`)\n
"""
function gradient(f, x::AbstractArray; abstol::Real = 1e-12, reltol::Real = 1e-12, h::Real = 0.1, t::Real = 2.0001, maxiters::Integer = 6)
    fx = f(x)

    # Initialize Work Arrays
    A1 = Vector{typeof(fx)}(undef, maxiters)
    A2 = Vector{typeof(fx)}(undef, maxiters)

    # Vector of step sizes
    hh = [h/t^(i - 1) for i = 1:maxiters]
    # Vector of orders
    k = 1:maxiters

    result = Vector{typeof(fx)}(undef, length(x))
    dx = zero(x)
    for i = 1:length(x)
        dx[i] = 1

        result[i] = extrapolate(h->(f(x + h*dx) - fx)/h,          # Function to extrapolate
                                A1, A2,                           # Work Arrays
                                hh, k,                            # Sequence of step sizes (h)
                                abstol, reltol                    # tolerances
                                )
        dx[i] = 0
    end
    result
end

"""
NumDiffTools.gradient!(result::AbstractArray, f, x::AbstractArray, [abstol, reltol, h, t, maxiters])

Stores gradient `∇f` of `f` evaluted at `x` in `result`, assuming `f` is called as `f(x)`.

Optional Arguments:\n
`abstol`: Target absolute error in derivative estimate (default `1e-12`)\n
`reltol`: Target relative error in derivative estimate (default `1e-12`)\n
`h`: Initial step size (default `0.1`)\n
`t`: Ratio of step sizes for each Richardson iteration (default `2.0001`.  Should not be an exact integer for periodic functions)\n
`maxiters`: Maximum number of Richardson iterations to perform (default `6`)\n
"""
function gradient!(result::AbstractArray, f, x::AbstractArray; abstol::Real = 1e-12, reltol::Real = 1e-12, h::Real = 0.1, t::Real = 2.0001, maxiters::Integer = 6)
    fx = f(x)

    # Initialize Work Arrays
    A1 = Vector{typeof(fx)}(undef, maxiters)
    A2 = Vector{typeof(fx)}(undef, maxiters)

    # Vector of step sizes
    hh = [h/t^(i - 1) for i = 1:maxiters]
    # Vector of orders
    k = 1:maxiters

    dx = zero(x)
    for i = 1:length(x)
        dx[i] = 1

        result[i] = extrapolate(h->(f(x + h*dx) - fx)/h,          # Function to extrapolate
                                A1, A2,                           # Work Arrays
                                hh, k,                            # Sequence of step sizes (h)
                                abstol, reltol                    # tolerances
                                )
        dx[i] = 0
    end
    nothing
end


export Chebyshev

immutable Chebyshev <: ParametricFamily
    n::Int
end

domain(::Chebyshev) = -1..1

points(c::Chebyshev, T = Float64) = cospi.(((c.n:-1:1)-T(0.5))/c.n)

degf(c::Chebyshev) = c.n

"Recurrence relation for Chebyshev polynomials."
@inline _chebyshev_recurrence(x, T₋₁, T₋₂) = 2*x*T₋₁ - T₋₂, T₋₁

"Initialize T₋₁, T₋₂ for the Chebyshev recurrence."
@inline _chebyshev_recurrence_init{T}(x::T) = x, one(T)

macro _chebyshev_recurrence!(x, T₋₁, T₋₂)
    T₋₁ = esc(T₋₁)
    T₋₂ = esc(T₋₂)
    :(($(T₋₁), $(T₋₂)) = _chebyshev_recurrence($(esc(x)), $(T₋₁), $(T₋₂)))
end

function basis!{T}(c::Chebyshev, x::T, b::AbstractVector{T})
    @argcheck c.n == length(b)
    T₋₁, T₋₂ = _chebyshev_recurrence_init(x)
    for i in 1:c.n
        if i == 1
            b[i] = T₋₂
        else
            if i > 2
                @_chebyshev_recurrence! x T₋₁ T₋₂
            end
            b[i] = T₋₁
        end
    end
    b
end

# note: after benchmarking, it was found that this is faster than Clenshaw,
# and at least as accurate.
function evaluate{T}(c::Chebyshev, θ, x::T)
    T₋₁, T₋₂ = _chebyshev_recurrence_init(x)
    value = zero(T)
    for i in 1:c.n
        if i == 1
            value += T₋₂*θ[i]
        else
            if i > 2
                @_chebyshev_recurrence! x T₋₁ T₋₂
            end
            value += T₋₁*θ[i]
        end
    end
    value
end

function evaluate{T}(c::Chebyshev, θ, vp::ValuePartial{T})
    ## if T(x) = 2xT₋₁(x) - T₋₂(x)
    ## then T′(x) = 2T₋₁(x) + 2xT′₋₁(x) - T′₋₂(x), with T′₂ = 1 and T′₁ = 0
    x = vp.x
    value = partial = zero(T)
    T₋₁ = x
    T₋₂ = T′₋₁ = one(T)
    T′₋₂ = zero(T)
    for i in 1:c.n
        if i == 1
            value += T₋₂*θ[i]
        else
            if i > 2
                @_chebyshev_recurrence! x T′₋₁ T′₋₂
                T′₋₁ += 2*T₋₁
                @_chebyshev_recurrence! x T₋₁ T₋₂
            end
            value += T₋₁*θ[i]
            partial += T′₋₁*θ[i]
        end
    end
    value, partial
end

evaluate{T}(c::Chebyshev, θ, x::Partial{T}) = evaluate(c, θ, ValuePartial(x.x))[2]

function fit!{T}(c::Chebyshev, ys::AbstractVector{T}, θ::AbstractVector{T})
    @argcheck c.n == length(ys) == length(θ)
    x = points(c, T)
    T₋₂ = ones(T, c.n)
    T₋₁ = copy(x)
    for i in 1:c.n
        if i == 1
            θ[1] = sum(ys) / c.n
        else
            if i > 2
                for j in 1:c.n
                    @_chebyshev_recurrence! x[j] T₋₁[j] T₋₂[j]
                end
            end
            θ[i] = dot(T₋₁, ys) *2 / c.n
        end
    end
    θ
end


"""
Implementation of a Rauch-Tung-Striebel smoother based on
Ch.8 of Bayesian Filtering & Smoothing (Särkka, 2014).

Author: Wouter Kouw
Last update: 03-07-2021
"""

using Distributions
using Random

function fixedinterval_rts_smoother(observations,
                                    transition_matrix,
                                    emission_matrix,
                                    process_noise,
                                    measurement_noise,
                                    state0)
    """
    Fixed-interval Rauch-Tung-Striebel smoother (Th. 8.2)

    This filter is built for a linear Gaussian dynamical system with known
    transition coefficients, process and measurement noise.
    """

    # Dimensionality
    Dx = size(process_noise,1)
    Dy = size(measurement_noise,1)

    # Recast process noise to matrix
    if Dx == 1
        if typeof(process_noise) != Array{Float64,2}
            process_noise = reshape([process_noise], 1, 1)
        end
        if typeof(measurement_noise) != Array{Float64,2}
            measurement_noise = reshape([measurement_noise], 1, 1)
        end
    end

    # Time horizon
    time_horizon = length(observations)

    # Initialize estimate arrays
    mk = zeros(Dx, time_horizon)
    Pk = zeros(Dx, Dx, time_horizon)

    # Initial state prior
    m_0, P_0 = state0

    # Start previous state variable
    m_kmin = m_0
    P_kmin = P_0

    "Forward pass"
    for k = 1:time_horizon

        # Forward prediction step
        m_k_pred = transition_matrix*m_kmin
        P_k_pred = transition_matrix*P_kmin*transition_matrix' .+ process_noise

        # Forward update step
        v_k = observations[:,k] .- emission_matrix*m_k_pred
        S_k = emission_matrix*P_k_pred*emission_matrix' .+ measurement_noise
        K_k = P_k_pred*emission_matrix'*inv(S_k)
        m_k = m_k_pred .+ K_k*v_k
        P_k = P_k_pred .- K_k*S_k*K_k'

        # Store estimates
        mk[:,k] = m_k
        Pk[:,:,k] = P_k

        # Update previous state variable
        m_kmin = m_k
        P_kmin = P_k

    end

    # Initialize smoothing estimate arrays
    msk = zeros(Dx, time_horizon)
    Psk = zeros(Dx, Dx, time_horizon)

    # Smoothed estimates at time horizon
    msk[:,time_horizon] = mk[:,time_horizon]
    Psk[:,:,time_horizon] = Pk[:,:,time_horizon]

    "Backward pass"
    for k = time_horizon-1:-1:1

        # Backward prediction
        m_kplus = transition_matrix * mk[:,k]
        P_kplus = transition_matrix * Pk[:,:,k] * transition_matrix' .+ process_noise

        # Backward update step
        G_k = Pk[:,:,k]*transition_matrix' * inv(P_kplus)
        msk[:,k] = mk[:,k] + G_k*(msk[:,k+1] - m_kplus)
        Psk[:,:,k] = Pk[:,:,k] + G_k*(Psk[:,:,k+1] - P_kplus)*G_k'

    end

    return msk, Psk
end


# ---
# title: 390. Elimination Game
# id: problem390
# author: Indigo
# date: 2021-06-20
# difficulty: Medium
# categories:
# link: <https://leetcode.com/problems/elimination-game/description/>
# hidden: true
# ---
#
# There is a list of sorted integers from 1 to _n_. Starting from left to right,
# remove the first number and every other number afterward until you reach the
# end of the list.
#
# Repeat the previous step again, but this time from right to left, remove the
# right most number and every other number from the remaining numbers.
#
# We keep repeating the steps again, alternating left to right and right to
# left, until a single number remains.
#
# Find the last number that remains starting with a list of length _n_.
#
# **Example:**
#
#
#
#     Input:
#     n = 9,
#     _1_ 2 _3_ 4 _5_ 6 _7_ 8 _9_
#     2 _4_ 6 _8_
#     _2_ 6
#     6
#
#     Output:
#     6
#
#
#
## @lc code=start
using LeetCode

last_remaining(n::Int)::Int = n == 1 ? 1 : 2 * (n ÷ 2 + 1 - last_remaining(n ÷ 2))

## @lc code=end