"""
   Implements basic ideas in

      S. Olver and A. Townsend,
      SIAM review, 2013,
      arXiv:1202.1347

   For more reviews, see Sec. 3 of

      A. Townsend and S. Olver,
      The automatic solution of partial differential
      equations using a global spectral method,
      Journal of Computational Physics, 2015
      arXiv:1409.2789

   and/or Sec. 6 of

      A. Townsend,
      Computing with functions in two dimensions,
      PhD Thesis, University of Oxford
      (can be accessed at https://pi.math.cornell.edu/~ajt/)

"""
module Gegenbauer

export compute_S, compute_D, compute_MX1, compute_MXp

include("CustomTypes.jl")
include("Chebyshev.jl")
using .CustomTypes
using .Chebyshev
using LinearAlgebra, SparseArrays

"""
   compute_S(n::myI, lam::myI)

   Computes n×n S_{λ} matrix.
"""
function compute_S(n::myI, lam::myI)
   @assert lam>=0
   @assert n>=3
   X = Vector{myI}(undef,0)
   Y = Vector{myI}(undef,0)
   V = Vector{myF}(undef,0)
   push!(X,1)
   push!(Y,1)
   push!(V,tomyF(1.0))
   if lam==0
      push!(X,1)
      push!(Y,3)
      push!(V,tomyF(-0.5))
      for i=2:(n-2)
         push!(X,i)
         push!(Y,i)
         push!(V,tomyF(0.5))
         push!(X,i)
         push!(Y,i+2)
         push!(V,tomyF(-0.5))
      end
      push!(X,n-1)
      push!(Y,n-1)
      push!(V,tomyF(0.5))
      push!(X,n)
      push!(Y,n)
      push!(V,tomyF(0.5))
   else
      push!(X,1)
      push!(Y,3)
      push!(V,tomyF(-lam/(lam+2.0)))
      for i=2:(n-2)
         push!(X,i)
         push!(Y,i)
         push!(V,tomyF(lam/(lam+i-1.0)))
         push!(X,i)
         push!(Y,i+2)
         push!(V,tomyF(-lam/(lam+i+1.0)))
      end
      push!(X,n-1)
      push!(Y,n-1)
      push!(V,tomyF(lam/(lam+n-2.0)))
      push!(X,n)
      push!(Y,n)
      push!(V,tomyF(lam/(lam+n-1.0)))
   end
   return sparse(X,Y,V)
end

"""
   compute_D(n::myI, lam::myI)

   Computes n×n D_{λ} matrix.
"""
function compute_D(n::myI, lam::myI)
   @assert lam>=1
   @assert n>=lam
   X = Vector{myI}(undef,0)
   Y = Vector{myI}(undef,0)
   V = Vector{myF}(undef,0)
   for i=1:(n-lam)
      push!(X,i)
      push!(Y,i+lam)
      push!(V,tomyF(lam+i-1))
   end
   for i=1:lam
      push!(X,n-lam+i)
      push!(Y,n)
      push!(V,tomyF(0))
   end
   return dropzeros(tomyF((2^(lam-1))*factorial(lam-1)).*sparse(X,Y,V))
end

"""
   compute_MX1(n::myI, lam::myI)

   Computes n×n M_{λ} matrix for polynomial x.
   For a simple derivation see Sec. 6.3.1 of
      Computing with functions in two dimensions,
      Townsend, Alex
      PhD Thesis, University of Oxford
   This can be accessed at
   https://pi.math.cornell.edu/~ajt/
"""
function compute_MX1(n::myI, lam::myI)
   @assert lam>=1
   @assert n>=lam
   X = Vector{myI}(undef,0)
   Y = Vector{myI}(undef,0)
   V = Vector{myF}(undef,0)
   push!(X,1)
   push!(Y,2)
   push!(V,tomyF(lam/(lam+1.0)))
   for i=2:(n-1)
      push!(X,i)
      push!(Y,i+1)
      push!(V,tomyF(((2.0*lam)+i-1)/(2.0*(lam+i))))
      push!(X,i)
      push!(Y,i-1)
      push!(V,tomyF((i-1)/(2.0*(lam+i-2))))
   end
   push!(X,n)
   push!(Y,n-1)
   push!(V,tomyF((n-1)/(2.0*(lam+n-2))))
   return sparse(X,Y,V)
end

"""
   compute_MCp(n::myI, lam::myI, p::myI)

   Computes n×n M_{λ} matrix for polynomial C^{λ}_p.
   For a simple derivation see Sec. 6.3.1 of
      Computing with functions in two dimensions,
      Townsend, Alex
      PhD Thesis, University of Oxford
   This can be accessed at
   https://pi.math.cornell.edu/~ajt/
"""
function compute_MCp(n::myI, lam::myI, p::myI)
   @assert p>=0

   MX0 = sparse(I,n,n)
   MX1 = compute_MX1(n,lam)

   if p==0
      return MX0
   elseif p==1
      return (2.0*lam).*MX1
   else
      MCp1 = deepcopy(sparse(I,n,n))
      MC   = deepcopy((2.0*lam).*MX1)
      MCm1 = deepcopy(MX0)

      for k=1:(p-1)
         MCp1 = (
            (2.0*(k+lam)/(k+1)).*(MX1*MC)
            -
            ((k-1+2*lam)/(k+1)).*MCm1
           )
         MCm1 = deepcopy(MC)
         MC   = deepcopy(MCp1)
      end
      return MCp1
   end
end

"""
   compute_MXp(n::myI, lam::myI, p::myI)

   Computes n×n M_{λ} matrix for polynomial x_p.
   For a simple derivation see Sec. 6.3.1 of
      Computing with functions in two dimensions,
      Townsend, Alex
      PhD Thesis, University of Oxford
   This can be accessed at
   https://pi.math.cornell.edu/~ajt/
"""
function compute_MXp(n::myI, lam::myI, p::myI)
   @assert p>=0

   MCp = compute_MCp(n,lam,p)

   if p==0
      return dropzeros(MCp)
   elseif p==1
      return dropzeros((0.5/lam).*MCp)
   elseif p==2
      MC0 = compute_MCp(n,lam,0)
      return dropzeros(((0.5/lam)/(1.0+lam)).*(MCp + lam.*MC0))
   elseif p==3
      return nothing
   elseif p==4
      return nothing
   end
end

"""
   compute_D(n::myI, lam::myI)

   Computes n×n D_{λ} matrix over the interval [xmin,xmax].
"""
function compute_D(n::myI, lam::myI, xmin::myF, xmax::myF)
   D = compute_D(n,lam)

   a = tomyF(0.5*(xmax-xmin))

   return dropzeros((a^(-lam)).*D)
end

"""
   compute_MXp(n::myI, lam::myI, p::myI, xmin::myF, xmax::myF)

   Computes n×n M_{λ} matrix for polynomial x_p over the interval [xmin,xmax].
"""
function compute_MXp(n::myI, lam::myI, p::myI, xmin::myF, xmax::myF)

   a = tomyF(0.5*(xmax-xmin))
   b = tomyF(0.5*(xmax+xmin))

   if p==0
      return compute_MXp(n,lam,0)
   elseif p==1
      return dropzeros(
               a*compute_MXp(n,lam,1)
               +
               b*compute_MXp(n,lam,0)
              )
   elseif p==2
      return dropzeros(
               (a^2)*compute_MXp(n,lam,2)
               +
               2*a*b*compute_MXp(n,lam,1)
               +
               (b^2)*compute_MXp(n,lam,0)
              )
   end
end

end


function SeisDelay(d; delay=0.1, dt=0.001)

    nt = size(d,1)
    nf = nt
    D = fft(d,1);
    dw = 2*pi/nt/dt
    nw = round(Int,nt/2) + 1
    for iw = 1 : nw
	w = (iw-1)*dw
	D[iw,:] = D[iw,:]*exp(-1im*w*delay)
    end
    # symmetries
    for iw=nw+1:nf
	D[iw,:] = conj(D[nf-iw+2,:])
    end
    d2 = ifft(D,1)
    d2 = real(d2[1:nt,:])
    return d2

end


using FFTW
using LinearAlgebra
using DSP
using Plots

function pcfm(m,k,nWaveforms=1)
  """
  pcfm(m,k)
  Compute the PCFM waveform for a randomly initialized phase code

    # Arguments
    - `m::Integer`: Phase code length
    - `k::Integer`: Oversampling factor
  """
  # Number of unique phase code values (i.e., no oversampling)
  n = trunc(Int,m/k)
  # Construct the array of phase code changes
  minAlpha = -pi/k
  maxAlpha = pi/k
  alpha = minAlpha .+(maxAlpha-minAlpha).*rand(Float64,(n, nWaveforms))
  # Phase shaping filter
  g = ones(k,1)./k
  g = vec(vcat(g,zeros(m-k,1)))
  B = zeros(m,n)
  for ii in 1:n
    B[:,ii] = cumsum(shiftsignal(g,ii*k))
  end
  s = exp.(im.*B*alpha)
  return (s,alpha,B)
end


cd(@__DIR__)
using Pkg; Pkg.activate("."); Pkg.instantiate()

using Flux, StochasticDiffEq, LinearAlgebra, Statistics, Test, NeuralNetDiffEq
using Random
Random.seed!(0)

d = 100 # number of dimensions
x0 = fill(0.0f0,d)
tspan = (0.0f0, 1.0f0)
m = 100 # number of trajectories (batch size)
λ = 1.0f0
#
g(X) = log(0.5f0 + 0.5f0*sum(X.^2))
f(X,u,σᵀ∇u,p,t) = -λ*sum(σᵀ∇u.^2)
μ(X,p,t) = zero(X)  #Vector d x 1 λ
σ(X,p,t) = Diagonal(sqrt(2.0f0)*ones(Float32,d)) #Matrix d x d
prob = TerminalPDEProblem(g, f, μ, σ, x0, tspan)

hls = 10 + d #hidden layer size
opt = Flux.ADAM(0.03)  #optimizer
#sub-neural network approximating solutions at the desired point
u0 = Flux.Chain(Dense(d,hls,relu),
                Dense(hls,hls,relu),
                Dense(hls,1))
# sub-neural network approximating the spatial gradients at time point
σᵀ∇u = Flux.Chain(Dense(d+1,hls,relu),
                  Dense(hls,hls,relu),
                  Dense(hls,hls,relu),
                  Dense(hls,d))
pdealg = NNPDENS(u0, σᵀ∇u, opt=opt)
#
@time ans = solve(prob, pdealg, verbose=true, maxiters=500, trajectories=m,
                            alg=LambaEM(), pabstol = 1f-2, reltol=1e-4, abstol=1e-4)

T = tspan[2]
MC = 10^5
W() = randn(d,1)
u_analytical(x, t) = -(1/λ)*log(mean(exp(-λ*g(x .+ sqrt(2.0)*abs.(T-t).*W())) for _ = 1:MC))
analytical_ans = u_analytical(x0, tspan[1])

error_l2 = sqrt((ans - analytical_ans)^2/ans^2)

println("Hamilton Jacobi Bellman Equation")
# println("numerical = ", ans)
# println("analytical = " , analytical_ans)
println("error_l2 = ", error_l2, "\n")
@test error_l2 < 0.2


# utilities for applying an FFTLog transformation

struct FFTLogPlan{T, OT, AA<:AbstractArray{Complex{T},1},
                  AAR<:AbstractArray{T,1}, PT<:Plan, IPT<:Plan}
    L::T
    N::Int
    μ::OT
    q::T
    r₀::T
    k₀r₀::T
    uₘ::AA
    r::AAR
    k::AAR
    fftplan!::PT
    ifftplan!::IPT
end

function plan_fftlog(r::AA, μ, q, k₀r₀=1.0;
                     kropt=true) where {T, AA<:AbstractArray{T,1}}
    logrmin = log(first(r))
    logrmax = log(last(r))
    r₀ = exp((logrmin + logrmax)/2)
    @assert logrmin < logrmax
    N = length(r)
    L = logrmax - logrmin
    dlnr = L / (N - 1)
    if kropt
        k₀r₀ = k₀r₀_low_ringing(N, μ, q, L, k₀r₀)
    end
    k₀ = k₀r₀ / r₀
    Nhalf = N ÷ 2
    n = range(-Nhalf,Nhalf,length=N)
    k = reverse(k₀ .* exp.(n .* L / N))

    m = fftfreq(N, N)  # get indicies that go from [-N/2] to [N/2]
    uₘ_coeff = similar(r, Complex{T})
    for i in eachindex(m)
        uₘ_coeff[i] = uₘ(m[i], μ, q, dlnr, k₀r₀, N)
    end
    uₘ_coeff[N÷2+1] = real(uₘ_coeff[N÷2+1])  # eq 19
    fftplan! = plan_fft!(uₘ_coeff)
    ifftplan! = plan_ifft!(uₘ_coeff)
    return FFTLogPlan(L, N, μ, q, r₀, k₀r₀, uₘ_coeff, r, k, fftplan!, ifftplan!)
end

U_μ(μ, x) = exp(x * log(2.) - lgamma(0.5 * (μ + 1 - x)) + lgamma(0.5 * (μ + 1 + x)))
uₘ(m, μ, q, dlnr, k₀r₀, N) = (k₀r₀)^(-2π * im * m / (dlnr * N)) * U_μ(μ, q + 2π * im * m / (dlnr * N) )

function k₀r₀_low_ringing(N, μ, q, L, k₀r₀=1.0)
    # from pyfftlog
    dlnr = L / (N-1)
    xp = (μ + 1 + q) / 2
    xm = (μ + 1 - q) / 2
    y = π * im / 2 / dlnr
    zp = lgamma(xp + y)
    zm = lgamma(xm + y)
    arg = log(2 / k₀r₀) / dlnr + imag(zp + zm) / π
    return k₀r₀ * exp((arg - round(arg))* dlnr)
end

function mul!(Y, pl::FFTLogPlan, A)
    Y .= A
    Y .*= (pl.r).^(-pl.q)
    pl.fftplan! * Y
    Y .*= pl.uₘ
    pl.ifftplan! * Y
    Y .*= (pl.r).^(pl.q)
end

function ldiv!(Y, pl::FFTLogPlan, A)
    Y .= A
    Y .*= (pl.r).^(-pl.q)
    pl.fftplan! * Y
    Y ./= pl.uₘ
    pl.ifftplan! * Y
    Y .*= (pl.r).^(pl.q)
end


import Base.sqrt

function power_of_2_gt(n::Int)
    Int(2^ceil(log(2,n)))
end

struct ScaledGeoMeanAtom <: AbstractExpr
    head::Symbol
    id_hash::UInt64
    children::Tuple{AbstractExpr, AbstractExpr}
    size::Tuple{Int, Int}

    function ScaledGeoMeanAtom(x::AbstractExpr)
        children = (x)
        return new(:scaledgeomean, hash(children), children, x.size)
    end
end

function sign(q::ScaledGeoMeanAtom)
    return Positive()
end

function monotonicity(q::ScaledGeoMeanAtom)
    return (Nondecreasing(),)
end

function curvature(q::ScaledGeoMeanAtom)
    return ConcaveVexity()
end

function evaluate(q::ScaledGeoMeanAtom)
    nbar = power_of_2_gt(length(q.children))
    return prod(evaluate(q.children[1]))^(1/nbar)
end

function conic_form!(q::ScaledGeoMeanAtom, unique_conic_forms::UniqueConicForms)
    if !has_conic_form(unique_conic_forms, q)
        t = Variable()
        qol_objective = conic_form!(t, unique_conic_forms)
        x, y = q.children
        conic_form!(SOCElemConstraint(y + x, y - x, 2 * t), unique_conic_forms)
        conic_form!(x >= 0, unique_conic_forms)
        conic_form!(y >= 0, unique_conic_forms)
        cache_conic_form!(unique_conic_forms, q, qol_objective)
    end
    return get_conic_form(unique_conic_forms, q)
end

function scaledgeomean(x::AbstractExpr)
    if length(x) > 2
        return ScaledGeoMeanAtom(x)
    elseif length(x) == 2
        return geomean(x[1],x[2])
    else
        return x
    end
end


a = 1
c = -5
limit = 10

mf = SigmoidMF(a, c, limit)

# Evaluation tests
@assert Fuzzy.eval(mf, c) == 0.5
@assert Fuzzy.eval(mf, c - 5) < Fuzzy.eval(mf, c + 5)

# Mean finding tests
@assert Fuzzy.mean_at(mf, 1) > Fuzzy.mean_at(mf, 0.6) > Fuzzy.mean_at(mf, 0.3) > Fuzzy.mean_at(mf, 0)


using StatsBase: mean

include("decisiontree.jl")

"""Gradient tree boosting algorithm.

The implmentation follows **Algorithm 10.3** in the *Elements of
Statistical Learning*. The original implementation of this algorithm called
MART for "multiple additive regression tree.
"""

# Loss functions
abstract type LossFunction end

struct MeanSquaredError <: LossFunction end

function (mse::MeanSquaredError)(y, pred)
    mean((y - pred).^2)
end

negativegradient(mse::MeanSquaredError, y, pred) = y - pred

"Initial prediction in case of Meansquared error is global mean."
predict(lossfunc::MeanSquaredError, y) = mean(y)

struct LogLoss <: LossFunction end

"""
LogLoss for binary classification problem.
The implmentation follows the article:
http://zpz.github.io/blog/gradient-boosting-tree-for-binary-classification/.
Here `pred` is the predicted log-odds and logistic/sigmmoid function
converted the log-odds to the predicted probability.
"""
function (logloss::LogLoss)(y, pred)
    sum(log.(1+exp.(pred)) - y.*pred)
end

sigmoid(pred) = 1 / (1 + exp(-pred))

gradient(logloss::LogLoss, y, pred) =  sigmoid.(pred) .- y

function hessian(logloss::LogLoss, y, pred)
    p = sigmoid.(pred)
    return p.*(1 .- p)
end
negativegradient(logloss::LogLoss, y, pred) =  -gradient(logloss, y, pred)

function predict(logloss::LogLoss, y)
    numobs = length(y)
    numpos = sum(y)
    return log(numpos/(numobs-numpos))
end

"""
Stopping rule for boosting model.
TODO:
    - Check the minimal increase between two consecutive model updates.
"""
struct BoostingStoppingRule
    numtrees::Int
    treestoppingrule::TreeStoppingRule
end

# Gradient boosting model
mutable struct GradientBoostingTrees
    lossfunc::LossFunction
    boostingstoppingrule::BoostingStoppingRule
    trees::Vector{DecisionTree}
    learningrate::Float64
    initialpred::Float64

    function GradientBoostingTrees(X, y, lossfunc, boostingstoppingrule; learningrate=0.1)
        numtrees = boostingstoppingrule.numtrees
        treestoppingrule = boostingstoppingrule.treestoppingrule
        trees = RegressionTree[]

        # Initialize boosting model: f0(x)
        initialpred = predict(lossfunc, y)
        preds = fill(initialpred, length(y))

        for m = 1:numtrees
            # generalized residual (negative gradient)
            residuals = negativegradient(lossfunc, y, preds)
            tree = RegressionTree(X, residuals, treestoppingrule)

            # update predictions on the terminal regions (leaves) in the newly fitted tree
            updateleaves!(lossfunc, tree, X, y, preds)
            newtreepreds = predict(tree, X)
            preds += learningrate * newtreepreds
            push!(trees, tree)
        end

        new(lossfunc, boostingstoppingrule, trees, learningrate, initialpred)
    end

end

"""
    updateleaves!(lossfunc, tree, X)

Update tree leaves with Meansquared error as the loss function.
For Meansquared error we have a closed form solution to minimize the loss
w.r.t the current model: the optimal point the mean of the residual in each
leave. See https://explained.ai/gradient-boosting/descent.html for details.

Because the regression tree has already fitted the residual, we don't need to
change the leaf value in this case.
"""
function updateleaves!(lossfunc, tree, X, y, preds) end

"""
    updateleaves!(lossfunc::LogLoss, tree, X)

Update tree leaves for log-loss. In general, we don't have a closed form solution.
We will conduct one step update using Newton's method. To use Newton's method,
we need gradient, Hessian and regularization terms.
"""
function updateleaves!(lossfunc::LogLoss, tree, X, y, preds)
    numregions = tree.numregions
    residuals = negativegradient(lossfunc, y, preds)
    hessians = hessian(lossfunc, y, preds)

    regionresidualsums = zeros(numregions)
    regionhessiansums = zeros(numregions)

    for i in 1:size(X)[1]
        regiondid = findregionid(lossfunc, tree, X[i, :])
        regionresidualsums[regiondid] += residuals[i]
        regionhessiansums[regiondid] += hessians[i]
    end

    regionupdates = regionresidualsums ./ regionhessiansums

    replacepreds!(tree, regionupdates)
end

function findregionid(lossfunc::LogLoss, tree, x)
    node = tree.rootnode
    while !node.isleaf
        featureindex = node.featureindex
        thresh = node.thresh
        if x[featureindex] < thresh
            node = node.leftnode
        else
            node = node.rightnode
        end
    end

    return node.regionid
end

function replacepreds!(tree::DecisionTree, regionupdates)
    stack = [tree.rootnode]

    while !isempty(stack)
        node = pop!(stack)
        if node.isleaf
            node.prediction = regionupdates[node.regionid]
        else
            @assert !isnothing(node.leftnode)
            @assert !isnothing(node.rightnode)
            push!(stack, node.leftnode)
            push!(stack, node.rightnode)
        end
    end

end

function predict(boostingmodel::GradientBoostingTrees, X)
    lossfunc = boostingmodel.lossfunc
    learningrate = boostingmodel.learningrate
    preds = fill(boostingmodel.initialpred, size(X)[1])

    for tree in boostingmodel.trees
        preds += learningrate*predict(tree, X)
    end

    preds
end


export atmospherefit, atmospheretable, gravity

#Define Properties Constants
const Tsl = 288.15  #sea level temperature (K)
const Psl = 101325.0 #sea level pressure (Pa)
const musl = 0.0000181206  #sea level viscosity (N-s/m^2)
const Sc = 113.0  #constant in Sutherlands formula (K)
const gamma = 1.4  #ratio of specific heats (air)
const R_M = 287.0520809957016 #Molar Gas Constant: R (gas constant) = 8.31432e3 #N m kmol−1 K−1, M (mean molar mass) = 28.9645 #g/mol, R_M = R/M
const earthradius = 6369.0e3 #radius of the earth (m)
const g = 9.80665 #gravitational constant at mean sea level

"""
atmospherefit(altitude::Real)

Return density (kg/m^3), viscosity (Pa-s), and speed of sound (m/s) functions.

Input altitude must be in meters.
"""
function atmospherefit(altitude::Real)
    if altitude > 47000.0
        @warn("air properties for altitudes above 47000 meters will be innacurate.")
    end
    T, P = temp_presfit(altitude)
    rho = density(T, P)
    mu = viscosity(T)
    a = speedofsound(T)

    return rho, mu, a

end #atmospherefit

"""
temp_presfit(altitude::Real)

Calculate atmospheric temperature and pressure for input altitude (meters) using fits of the standard atmosphere model,
slightly modified from those found in Flight Vehicle Aerodynamics by Mark Drela.

"""
function temp_presfit(altitude::Real)

    #Convert Altitude to km
    altkm = altitude/1000

    T = Tsl - 71.5 + 2*log(1+exp(35.75-3.25*altkm)+exp(-3.0+0.0003*altkm^3))
    P = Psl*exp(-0.118*altkm-(0.0015*altkm^2)/(1-0.018*altkm+0.0011*altkm^2))

    return T, P

end #temp_presfit

"""
atmospheretable(altitude::Real)

Return density (kg/m^3), viscosity (Pa-s), and speed of sound (m/s) functions.

Input altitude must be in meters.
"""
function atmospheretable(altitude::Real)
    if altitude > 86000.0
        @warn("air properties for altitudes above 86000 meters will be innacurate.")
    end

    T, P = temp_prestable(altitude)
    rho = density(T, P)
    mu = viscosity(T)
    a = speedofsound(T)

    return rho, mu, a

end #atmospherefit

"""
temp_prestable(altitude::Real)

Calculate atmospheric temperature and pressure for input altitude (meters)
from 1976 Standard Atmosphere Model.

"""
function temp_prestable(altitude::Real)
    #Convert Geometric Altitude to Geopotential Altitude
    altgeopot = altitude*earthradius/(altitude+earthradius)

    #table values
    altitudetable = [0.0; 11.0; 20.0; 32.0; 47.0; 51.0; 71.0; 84.5]*1e3
    temperaturetable = [288.150; 216.650; 216.650; 228.650; 270.650; 270.650; 214.650; 187.650]
    temperaturegradient = [-0.0065; 0.0; 0.001; 0.0028; 0.0; -0.0028; -0.002; 0.0]
    pressuretable = [101325.0; 22632.0; 5474.8; 868.01; 110.90; 66.938; 3.9564; 0.39814]

    #find relavent index in tables
    idx = findall(altitudetable.<=altgeopot)[end]

    #find temperature
    T = temperaturetable[idx] + (altgeopot-altitudetable[idx])*temperaturegradient[idx]

    #find pressure
    if temperaturegradient[idx] == 0.0
        P = pressuretable[idx]*exp(-g*(altgeopot-altitudetable[idx])/R_M/temperaturetable[idx])
    else
        P = pressuretable[idx]*(temperaturetable[idx]/T)^(g/R_M/temperaturegradient[idx])
    end

    return T, P

end

"""
density(Temperature::Real, Pressure::Real)

Return Air Density (kg/m^3) from Ideal Gas Law.

Input Temperature must be in Kelvin, and Input Pressure must be in Pascals.
"""
function density(Temperature::Real, Pressure::Real)
    #Ideal Gas Law
    rho = Pressure/(R_M*Temperature)
    return rho
end #density

"""
speedofsound(Temperature::Real)

Return Speed of Sound (m/s) from Ideal Gas Law.

Input Temperature must be in Kelvin.
"""
function speedofsound(Temperature::Real)
    #speed of sound (ideal gas law)
    a = sqrt(gamma*R_M*Temperature)
    return a
end #speedofsound

"""
viscosity(Temperature::Real)

Return Dynamic Viscosity (Pa-s) from Sutherlands Equation.

Input Temperature must be in Kelvin.
"""
function viscosity(Temperature::Real)
    mu = musl*(Temperature/Tsl)^(3.0/2)*(Tsl+Sc)/(Temperature+Sc)
    return mu
end #viscosity

"""
gravity(altitude::Real)

Calculate adjusted gravitational acceleration at altitude (meters) and latitude (degrees).

1984 Ellipsoidal Gravity Formula.

"""
function gravity(altitude::Real, latitude::Real=45.0)

    #latitude adjustment of mean sea level gravitational acceleration.
    gravadjlat = 9.7803253359*(1 + 0.00193185265241*sind(latitude)^2)/sqrt(1 - 0.00669437999013*sind(latitude)^2)
    #altitude adjustment of gravitational constant.
    gravadjalt = gravadjlat*(earthradius/(altitude+earthradius))^2

    return gravadjalt
end #gravity


# Importing the physical constants:
constants = Constants()

T = 28.0 - constants.K₀ # Current temperature
Tᵣ = 25.0 - constants.K₀ # Reference temperature
A = Fvcb()

@testset "Γ_star()" begin
    @test PlantBiophysics.Γ_star(T,Tᵣ,constants.R) ==
        PlantBiophysics.arrhenius(42.75,37830.0,T,Tᵣ,constants.R)
end;

@testset "standard arrhenius()" begin
    @test PlantBiophysics.arrhenius(42.75,37830.0,T,Tᵣ,constants.R) ≈ 49.76935360399572
end;

@testset "arrhenius() with negative effect of too high T" begin
    @test PlantBiophysics.arrhenius(A.JMaxRef,A.Eₐⱼ,T,Tᵣ,A.Hdⱼ,A.Δₛⱼ) ≈ 278.5161762418
    # NB: value checked using plantecophys.
end;

@testset "arrhenius() with negative effect of too high T" begin
    @test PlantBiophysics.arrhenius(A.JMaxRef,A.Eₐⱼ,T,Tᵣ,A.Hdⱼ,A.Δₛⱼ) ≈ 278.5161762418
    # NB: value checked using plantecophys.
end;

@testset "compare arrhenius() implementations" begin
# arrhenius with negative effect of too high T should yield the same result as the standard Arrhenius
# when Δₛ = 0.0
    @test PlantBiophysics.arrhenius(A.JMaxRef,A.Eₐⱼ,28.0-constants.K₀,A.Tᵣ-constants.K₀,A.Hdⱼ,0.0) ==
        PlantBiophysics.arrhenius(A.JMaxRef,A.Eₐⱼ,28.0-constants.K₀,A.Tᵣ-constants.K₀)
end;


"""
    distFunction(states1::Vector{Float64}, states2::Vector{Float64})::Array{Float64,2}

Calculates the distance abs(x-y) between all points of `states1` and `states2`.
"""
function distFunction(states1::Vector{Float64}, states2::Vector{Float64})::Array{Float64,2}
    n1= length(states1); n2= length(states2)
    dMatrix= Array{Float64}(undef, (n1,n2))
    for i= 1:n1, j= 1:n2
		dMatrix[i,j]= abs(states2[j]- states1[i])
    end
    return dMatrix
end

"""
    Wasserstein(p1::Vector{Float64}, p2::Vector{Float64}, distMatrix::Array{Float64,2}, r::Float64=1.)

Calculates the Wasserstein with marginal distributions `p1` and `p2` for the distance matrix `distMatrix.

Optionally the Hölder norm can be specified. The distance together with the optimal transport density is outputted.
"""
function Wasserstein(p1::Vector{Float64}, p2::Vector{Float64}, distMatrix::Array{Float64,2}, r::Float64=1.)
	ontoSimplex!(p1); ontoSimplex!(p2)
    n1= length(p1);   n2= length(p2)

    A= kron(ones(n2)', Matrix{Float64}(I, n1, n1))
    B= kron(Matrix{Float64}(I, n2, n2), ones(n1)')

	model = Model(Clp.Optimizer)
	set_silent(model)
	@variable(model, x[i=1:n1*n2] >= 0)
	@objective(model, Min, vec(distMatrix.^r)' * x)
	@constraint(model, [A;B] * x .== [p1;p2])
	JuMP.optimize!(model)
    return (distance= (objective_value(model))^(1/ r), π= reshape(value.(x), (n1, n2)))
end

#	Sinkhorn-Knopp iteration algorithm
function Sinkhorn(p1::Vector{Float64}, p2::Vector{Float64}, distMatrix::Array{Float64,2}, r::Float64= 1., λ::Float64= 1.)
	ontoSimplex!(p1); ontoSimplex!(p2); count= 0
	βr= Array{Float64}(undef, length(p1));
	γc= ones(size(p2))		# guess a starting value
	distMatrix.^= r; K= exp.(-λ * distMatrix)
	while count < 1000		# Sinkhorn iteration
		γc= γc./ (p2'*γc)		# rescale
		βr= p1./ (K* γc)		# vector operation
		γc= p2./ (K'* βr)		# vector operation
		count += 1				# iteration count
	end
#	println("r=", βr, p1'*βr); println("c=", γc, p2'*γc)
	π= Diagonal(βr)*K*Diagonal(γc)
	return (distance= (sum(π.* distMatrix)) ^(1/r), π= π)
end

"""
    nestedWasserstein(trr1,trr2,r=2)

Calculates the nested distance based on the Wasserstein distance for two trees `trr1` and `trr2` Optionally the Hölder norm `r` can be specified.
"""
function nestedWasserstein(trr1,trr2,r=1.0)
    T = height(trr1)
    #cumulProb!(trr1); cumulProb!(trr2);

    #d_new = Array{Float64}(undef,length(nodes(trr1,T)),length(nodes(trr2,T))).*0.0

    # initialize with all combinations of leave nodes of trr1 and trr2
    l1 = leaves(trr1)[1]; l2 = leaves(trr2)[1];
    println("distFunctionStart")
    d_new = distFunction(trr1.state[l1],trr2.state[l2])
    println("distFunctionEnd")

    for t= T-1:-1:0

        for (index1,i) in enumerate(nodes(trr1,t))
            #probability of transitioning from i to somewhere
            p1 = trr1.probability[trr1.children[i+1]]
            for (index2,j) in enumerate(nodes(trr2,t))
                p2 = trr2.probability[trr2.children[j+1]]
                #subset d_old for relevant index1/i and index2/j values
                ind1 = [findall(x->x==trr1.children[i+1][k], l1)[1] for k=1:length(trr1.children[i+1]) ]
                ind2 = [findall(x->x==trr2.children[j+1][k], l2)[1] for k=1:length(trr2.children[j+1]) ]
                d_old = d_new[ind1,ind2]
                # p1 is conditional (marginal) probability of transition for trr1
                d_new[index1,index2] = Wasserstein(p1,p2,d_old,r)[1]
            end

        end
        l1 = nodes(trr1,t); l2 = nodes(trr2,t)
        println(t)
    end
    return(d_new[1,1])
end


using ModelingToolkit
using Base.Test

@IVar t
@DVar x(t) y(t) z(t)
@test_throws AssertionError @DVar w

@Deriv D'~t # Default of first derivative, Derivative(t,1)
@Param σ ρ β
@Const c=0

# Default values
p = Parameter(:p, 1)
u = DependentVariable(:u, [1], dependents = [t])

s = JumpVariable(:s,3,dependents=[t])
n = NoiseVariable(:n,dependents=[t])

σ*(y-x)
D*x
D*x ~ -σ*(y-x)
D*y ~ x*(ρ-z)-sin(y)

@test D*t == Constant(1)


"""
blanchardkahn()

Blanchard-Kahn method for solving linear rational
expectations models.

"""

function blanchardkahn(Γ::AbstractArray{T};output_all=false) where T

    A = [Γ.A₁₁ Γ.A₁₂ ; Γ.A₂₁ Γ.A₂₂]
    B = [Γ.B₁₁ Γ.B₁₂ ; Γ.B₂₁ Γ.B₂₂]

    F_ = schur(A,B)
    F  = ordschur(F_,abs.(F_.α./F_.β) .< 1) # push large eigs to the bottom

    nx,ny = size(Γ.A₁₂)
    S₁₁,S₁₂,S₂₁,S₂₂ = blocks(F.S,nx)
    T₁₁,T₁₂,T₂₁,T₂₂ = blocks(F.T,nx)
    Qᵀ₁₁,Qᵀ₁₂,Qᵀ₂₁,Qᵀ₂₂ = blocks(F.Q',nx)
    Zᵀ₁₁,Zᵀ₁₂,Zᵀ₂₁,Zᵀ₂₂ = blocks(F.Z',nx)

    V = inv(Zᵀ₂₂)
    N = V*Zᵀ₂₁
    L = V*(S₂₂\(Qᵀ₂₁*Γ.G₁ + Qᵀ₂₂*Γ.G₂))

    β₁⁻¹ = inv(Γ.B₁₁ - Γ.B₁₂*N)
    C  = β₁⁻¹*(Γ.A₁₁ - Γ.A₁₂*N)
    D  = β₁⁻¹*(Γ.G₁ - Γ.A₁₂*L)

    axes = (Axis(
            C=ViewAxis((1:length(C)), ShapedAxis(size(C), NamedTuple())),
            D=ViewAxis(length(C) .+ (1:length(D)), ShapedAxis(size(D), NamedTuple())),
            ),)
    Φ = ComponentArray([vec(C);vec(D)],axes)

    if output_all
        return Φ,N,L,β₁⁻¹,nx,ny
    else
        return Φ
    end
end

function ChainRulesCore.rrule(::typeof(blanchardkahn), Γ)

    Φ,N,L,β₁⁻¹,nx,ny = blanchardkahn(Γ;output_all=true)

    β₁⁻ᵗ = β₁⁻¹'
    ηᵗ   = β₁⁻ᵗ*(Γ.B₂₁' - N'*Γ.B₂₂')
    nε   = size(Φ.D)[2]

    function blanchardkahn_pullback(ΔΦ)

        # If you use or extend this code, please cite
        # Duncan, Alfred (2021) "Reverse Mode Differentiation for DSGE Models,"
        # University of Kent School of Economics Discussion Papers 2108.

        CAΔΦ = ComponentArray(ΔΦ,getaxes(Φ))

        α₂  = Γ.A₁₂'*ηᵗ-Γ.A₂₂'
        β₂  = Γ.B₂₂'-Γ.B₁₂'*ηᵗ
        Ξ   = kron(Φ.C,β₂) + kron(I(nx),α₂)

        β₁⁻ᵗΔC = β₁⁻ᵗ*CAΔΦ.C
        β₁⁻ᵗΔD = β₁⁻ᵗ*CAΔΦ.D
        βCD = β₁⁻ᵗΔC*Φ.C'+β₁⁻ᵗΔD*Φ.D'

        Q   = inv(α₂)*Γ.A₁₂'*β₁⁻ᵗΔD

        w   = -Ξ\vec(Γ.B₁₂'*βCD - Γ.A₁₂'*β₁⁻ᵗΔC + β₂*Q*Φ.D')
        W   =  reshape(w,ny,nx)

        ∂Γ = similar(Γ)
        ∂Γ.A₁₁ = -ηᵗ*W + β₁⁻ᵗΔC
        ∂Γ.A₂₁ =  W
        ∂Γ.A₂₂ = -W*N' - Q*L'
        ∂Γ.A₁₂ = -ηᵗ*∂Γ.A₂₂ - β₁⁻ᵗΔC*N' - β₁⁻ᵗΔD*L'
        ∂Γ.B₂₁ = -W*Φ.C' - Q*Φ.D'
        ∂Γ.B₁₁ = -ηᵗ*∂Γ.B₂₁ - βCD
        ∂Γ.B₁₂ = -∂Γ.B₁₁*N'
        ∂Γ.B₂₂ = -∂Γ.B₂₁*N'
        ∂Γ.G₁  = -ηᵗ*Q+β₁⁻ᵗΔD
        ∂Γ.G₂  =  Q

        return NO_FIELDS, ∂Γ
    end
    return Φ, blanchardkahn_pullback
end

blocks(A::AbstractArray,nx) =
    A[1:nx,1:nx],A[1:nx,nx+1:size(A)[1]],
    A[nx+1:size(A)[1],1:nx],A[nx+1:size(A)[1],nx+1:size(A)[1]]


using Images, FileIO
using FixedPointNumbers

function initial_means(img_data, k_num)
    return rand(img_data, k_num)
end

function update_groups!(k_groups, img_data, means)
    for iter in eachindex(img_data)
        min_idx = 1
        min_val = 1000.0 # Marking this as float made a big difference as well
        for i = 1:16
            dist = norm(means[i] - img_data[iter])
            if dist < min_val
                min_val = dist
                min_idx = i
            end
        end
        k_groups[iter] = min_idx
        # This was the previous code that was not optimal and had a lot of gc
        # Got 2x speedup by refactoring to above
        # local distance = norm.(means .- img_data[iter])
        # _, idx = findmin(distance)
        # k_groups[iter] = idx
    end
end

function update_means!(means, img_data, k_groups, k_num::Int64)
    # means = Array(RGB{Float64}, k_num)
    for i = 1:k_num
        mask = k_groups .== i
        pixels = img_data[mask]
        means[i] = mean(pixels)
    end
end

function k_means(img_data, k_num)
    means = initial_means(img_data, k_num)
    k_groups = Array(Int64, (size(img_data, 1), size(img_data,2)))
    update_groups!(k_groups, img_data, means)
    loop_num = 0
    while loop_num < 5
        update_means!(means, img_data, k_groups, k_num)
        update_groups!(k_groups,img_data, means)
        loop_num += 1
        print("Iteration $loop_num \n")
    end
    return means
end

function reduce_image!(img2, means, k_groups, k_num)
    for i = 1:k_num
        mask = k_groups .== i
        img2[mask] = means[i]
    end
end
## Main script here....
srand(1234)  # random seed
const k= 16  # How many clusters in k-means

# Load Images, Small Mandrill and large Mandrill
img1_ = load("mandrill-small.tiff")
img2_ = load("mandrill-large.tiff")
# Convert the images to float values and assign with const
const img = float64.(img1_)  # convert to float 64
const img2 = float64.(img2_)

# Perform K Means
means = k_means(img, k)

# Use the Means to compress the larger madrill picture
groups = Array(Int64, (size(img2, 1), size(img2,2)))
update_groups!(groups, img2, means)
reduce_image!(img2, means, groups, k)
save("mandrill-large-reduced-julia.tiff", img2)


using Pkg, DrWatson

@quickactivate "StatisticalRethinkingTuring"
using Turing
using StatisticalRethinking

Turing.setprogress!(false)

df = CSV.read(sr_datadir("Howell1.csv"), DataFrame)
df = df[df.age .>= 18, :]

@model function ppl4_1(heights)
    μ ~ Normal(178, 20)
    σ ~ Uniform(0, 50)
    heights .~ Normal(μ, σ)
end

m4_1t = ppl4_1(df.height)
precis(m4_1t)

res = quap(m4_1t)


### A Pluto.jl notebook ###
# v0.17.5

using Markdown
using InteractiveUtils

# ╔═╡ 754d60ac-4cb0-443a-af2a-25235e544a31
begin
  using Pkg
  Pkg.activate(@__DIR__)
  Pkg.instantiate()
end

# ╔═╡ 0256dc40-e8b0-40a8-a6bf-47194d7e8e12
using Unitful

# ╔═╡ 0f76a79f-8675-4ec1-ba1f-6363f43ec697
using Measurements

# ╔═╡ 4dbb0300-d546-48dd-9bca-7c69b794f8ce
md"# Basic demonstration of Julia package composability"

# ╔═╡ eb23f2c6-61f4-4a82-b795-033f6f2a0674
md"Notebook from [HelloJulia.jl](https://github.com/ablaom/HelloJulia.jl)"

# ╔═╡ ec6ad8e5-c854-41db-935f-ddf6a6bfbbdd
md"Instantiate package environment:"

# ╔═╡ b35b6a58-0335-4dab-8af5-148d95ea2900
md"The Unitiful package allows you to **bind physical units** to numerical data:"

# ╔═╡ a2808946-3e6a-4c31-828a-4cc485149963
a = 5.0u"m^2/s^2"

# ╔═╡ 92fe3d8a-f4c0-4de4-9e54-3f7b3ca87ecb
md"""
The using Measurements package allows you to **propogate uncertainties**
in numerical computations:
"""

# ╔═╡ bf15f7d4-5ef4-4184-95ea-2955abd45275
b = 5.0 ± 1.2 # or measurement(5.0, 1.2)

# ╔═╡ 4c307469-e7bd-4041-b1b3-dd47e367ba54
sqrt(b)

# ╔═╡ 9e262366-a19c-4d4c-8d7e-8e449afd1c48
md"""
The Unitful and Measurements packages are blissfully ignorant of one
another. That, is neither package is a dependency of the other. And yet the
following "just works", as if by magic:
"""

# ╔═╡ cddcd722-2cd4-4d95-a949-7f3bd292fe31
c = 5.0u"m^2/s^2" ± 0.1u"m^2/s^2"

# ╔═╡ 76009ff9-a8ba-4cba-8514-99938a2932db
sqrt(c)

# ╔═╡ 135dac9b-0bd9-4e1d-a0de-1721c1bc2df2
md"""
---

*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*
"""

# ╔═╡ Cell order:
# ╟─4dbb0300-d546-48dd-9bca-7c69b794f8ce
# ╟─eb23f2c6-61f4-4a82-b795-033f6f2a0674
# ╟─ec6ad8e5-c854-41db-935f-ddf6a6bfbbdd
# ╠═754d60ac-4cb0-443a-af2a-25235e544a31
# ╟─b35b6a58-0335-4dab-8af5-148d95ea2900
# ╠═0256dc40-e8b0-40a8-a6bf-47194d7e8e12
# ╠═a2808946-3e6a-4c31-828a-4cc485149963
# ╟─92fe3d8a-f4c0-4de4-9e54-3f7b3ca87ecb
# ╠═0f76a79f-8675-4ec1-ba1f-6363f43ec697
# ╠═bf15f7d4-5ef4-4184-95ea-2955abd45275
# ╠═4c307469-e7bd-4041-b1b3-dd47e367ba54
# ╟─9e262366-a19c-4d4c-8d7e-8e449afd1c48
# ╠═cddcd722-2cd4-4d95-a949-7f3bd292fe31
# ╠═76009ff9-a8ba-4cba-8514-99938a2932db
# ╟─135dac9b-0bd9-4e1d-a0de-1721c1bc2df2


# This file is for the attractor of Sierpinski

using FractalTools
using Plots
using BenchmarkTools
using Plots
# Construct ifs
ifs = Sierpinski()
initset = [rand(2)]
atr = attractor(ifs, initset, alg=RandAlg(), numiter=Int(1e4), allocated=true)

# Plot the attractor
scatter(getindex.(atr.set,1), getindex.(atr.set,2), markersize=2)

# ch = Channel(0)
# task = @async FractalTools._randalg_sequential(ch, only(initset), ifs.ws, 1000, ifs.probs)


module Normalizations

using Statistics

function norm_range!(data::T,min_vals::T, max_vals::T,new_min::F,new_max::F) where {F<:AbstractFloat,N,T<:Array{F,N}}
    data .= ((data .- min_vals)./(max_vals .- min_vals)).*(new_max .- new_min) .+ new_min
    return nothing
end

function norm_range!(data::Vector{T},new_min::F,new_max::F) where {F<:AbstractFloat,N,T<:Array{F,N}}
    num = size(data[1],N)
    min_vals = T(undef,ntuple(x->1,Val(N-1))...,num)
    max_vals = T(undef,ntuple(x->1,Val(N-1))...,num)
    for i = 1:num
        min_vals[i] = minimum(cat(selectdim.(data, N, i)...,dims=Val(N)))
        max_vals[i] = maximum(cat(selectdim.(data, N, i)...,dims=Val(N)))
    end
    map(x -> norm_range!(x,min_vals, max_vals,new_min,new_max), data)
    return min_vals, max_vals
end

"""
    norm_01!(data::T,min_vals::T, max_vals::T) where {F<:AbstractFloat,N,T<:Array{F,N}}

Rescales each feature (last dimension) to be in the range [0,1].
"""
function norm_01!(data::T,min_vals::T, max_vals::T) where {F<:AbstractFloat,N,T<:Array{F,N}}
    norm_range!(data,min_vals, max_vals,zero(F),one(F))
    return nothing
end

"""
    norm_01!(data::Vector{T}) where {F<:AbstractFloat,N,T<:Array{F,N}}

Rescales each feature (last dimension) to be in the range [0,1]. Returns min and max values for each feature.
"""
function norm_01!(data::Vector{T}) where {F<:AbstractFloat,N,T<:Array{F,N}}
    min_vals, max_vals = norm_range!(data,zero(F),one(F))
    return min_vals, max_vals
end

"""
    norm_negpos1(data::T,min_vals::T,max_vals::T) where {F<:AbstractFloat,N,T<:Array{F,N}}

Rescales each feature (last dimension) to be in the range [-1,1].
"""
function norm_negpos1!(data::T,min_vals::T,max_vals::T) where {F<:AbstractFloat,N,T<:Array{F,N}}
    norm_range!(data,min_vals, max_vals,-one(F),one(F))
    return nothing
end

"""
    norm_negpos1(data::Vector{T}) where {F<:AbstractFloat,N,T<:Array{F,N}}

Rescales each feature (last dimension) to be in the range [-1,1]. Returns min and max values for each feature.
"""
function norm_negpos1!(data::Vector{T}) where {F<:AbstractFloat,N,T<:Array{F,N}}
    min_vals, max_vals = norm_range!(data,-one(F),one(F))
    return min_vals, max_vals
end

"""
    norm_zerocenter!(data::T,mean_vals::T) where {F<:AbstractFloat,N,T<:Array{F,N}}

Subtracts the mean of each feature (last dimension).
"""
function norm_zerocenter!(data::T,mean_vals::T) where {N,T<:Array{<:AbstractFloat,N}}
    data .= data .- mean_vals
    return nothing
end

"""
    norm_zerocenter!(data::Vector{T}) where {F<:AbstractFloat,N,T<:Array{F,N}}

Subtracts the mean of each feature (last dimension). Returns a mean value for each feature.
"""
function norm_zerocenter!(data::Vector{T}) where {N,T<:Array{<:AbstractFloat,N}}
    num = size(data[1],N)
    mean_vals = T(undef,ntuple(x->1,Val(N-1))...,num)
    for i = 1:num
        mean_vals[i] = mean(cat(selectdim.(data, N, i)...,dims=Val(N)))
    end
    map(x -> norm_zerocenter!(x,mean_vals), data)
    return mean_vals
end

"""
    norm_zscore!(data::T,mean_vals::T,std_vals::T) where {F<:AbstractFloat,N,T<:Array{F,N}}

Subtracts the mean and divides by the standard deviation of each feature (last dimension).
"""
function norm_zscore!(data::T,mean_vals::T,std_vals::T) where {N,T<:Array{<:AbstractFloat,N}}
    data .= (data .- mean_vals)./std_vals
    return nothing
end

"""
    norm_zscore!(data::Vector{T}) where {F<:AbstractFloat,N,T<:Array{F,N}}

Subtracts the mean and divides by the standard deviation of each feature (last dimension).
Returns mean and standard deviation values for each feature.
"""
function norm_zscore!(data::Vector{T}) where {N,T<:Array{<:AbstractFloat,N}}
    num = size(data[1],N)
    mean_vals = T(undef,ntuple(x->1,Val(N-1))...,num)
    std_vals = T(undef,ntuple(x->1,Val(N-1))...,num)
    for i = 1:num
        mean_vals[i] = mean(cat(selectdim.(data, N, i)...,dims=Val(N)))
        std_vals[i] = std(cat(selectdim.(data, N, i)...,dims=Val(N)))
    end
    map(x -> norm_zscore!(x,mean_vals,std_vals), data)
    return mean_vals, std_vals
end

export norm_01!, norm_negpos1!, norm_zerocenter!, norm_zscore!

end


"""
```
    Canny <: AbstractEdgeDetectionAlgorithm
    Canny(; spatial_scale = 1, high = Percentile(80), low = Percentile(20), thinning_algorithm = NonmaximaSuppression(threshold = low))

    detect_edges([T,] img, f::Canny)
    detect_edges!([out,] img, f::Canny)
    detect_subpixel_edges([T₁, T₂] img, f::Canny)
    detect_subpixel_edges!(out₁, out₂, img, f::Canny)
```

Returns a binary image depicting the edges of the input image.

# Details

TODO

# Options

Various options for the parameters of the `detect_edges` function and `Canny` type are described in more detail below.

# Choices for img

The `detect_edges` function can handle a variety of input types.
By default the type of the returned image matches the type of the
input image.

For colored images, the input is converted to grayscale.

# Choices for `spatial_scale` in `Canny`.

The `spatial_scale` determines the radius (σ) of the Gaussian filter. It must
be a positive real number.

# Choices for `high` and `low` in `Canny`.

The hysteresis thresholds `high` and `low` (`high` > `low`) can be specified as
positive numbers, or as `Percentiles`. If left unspecified, a default value of
`high = Percentile(80)` and `low = Percentile(20)` is assumed.

# Choices for `thinning_algorithm` in `Canny`.
You can specify an [`AbstractEdgeThinningAlgorithm`](@ref). By default, the
[`NonmaximaSuppression`](@ref) algorithm is used which suppresses non-maxima up
to pixel-level accuracy. For subpixel precision specify the
[`SubpixelNonmaximaSuppression`](@ref) algorithm.

# Example

```julia

using TestImages, FileIO, ImageView

img =  testimage("mandril_gray")
img_edges = detect_edges(img, Canny(spatial_scale = 1.4))

imshow(img)
imshow(img_edges)
```

# References
J. Canny, "A Computational Approach to Edge Detection," in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. PAMI-8, no. 6, pp. 679-698, Nov. 1986, doi: 10.1109/TPAMI.1986.4767851.

"""
@with_kw struct Canny{T₁ <: Union{Real,AbstractGray},
                      T₂ <: Union{Real,AbstractGray, Percentile},
                      T₃ <: Union{Real,AbstractGray, Percentile},
                      T₄ <: AbstractEdgeThinningAlgorithm} <: AbstractEdgeDetectionAlgorithm
    spatial_scale::T₁ = 1
    high::T₂ = Percentile(80)
    low::T₃ = Percentile(20)
    thinning_algorithm::T₄ = NonmaximaSuppression(threshold = low)
end

# Handles pixel-level precision.
function (f::Canny)(out::GenericGrayImage, img::GenericGrayImage)
    @unpack spatial_scale, high, low = f
    @unpack thinning_algorithm = f
    σ = spatial_scale

    # Smooth the image with a Gaussian filter of width σ, which specifies the
    # scale level of the edge detector.
    kernel =  KernelFactors.gaussian((σ,σ))
    imgf = imfilter(img, kernel, NA())

    # Calculate the gradient vector at each position of the filtered image.
    # The derivatives are taken with respect to the first and second dimension
    # (rows first, then columns).
    g₁, g₂ = imgradients(imgf, KernelFactors.scharr)
    # Gradient magnitude
    mag = hypot.(g₁, g₂)

    # In StatsBase quantiles are undefined in the presence of NaNs
    # hence we need to keep only valid magnitudes before we can determine
    # the percentiles.
    valid_indices = map(x-> !isnan(x), mag)
    valid_mag = view(mag, valid_indices)

    low_threshold =  typeof(low) <: Percentile ? StatsBase.percentile(vec(valid_mag), low.p) : low
    high_threshold =  typeof(high) <: Percentile ? StatsBase.percentile(vec(valid_mag), high.p) : high

    thinning_algorithm = @set thinning_algorithm.threshold = low_threshold

    # Isolate local maxima of gradient magnitude by “non-maximum suppression”
    # along the local gradient direction.
    nms = thin_edges(mag, g₁, g₂, thinning_algorithm)

    # Collect sets of connected edge pixels from the local maxima by applying
    # “hysteresis thresholding”. Edge pixels whose gradient magnitude is below
    # the low threshold are removed; edge pixels whose gradient magnitudes are
    # above (or equal to) the high threshold are retained, and edge pixels whose
    # gradient magnituded are between the low and high threshold are only
    # retained if they are connected to edge pixels whose gradient magnitudes
    # are above or equal to the high threshold.
    edges = zeros(Bool, axes(img))
    @inbounds for i in CartesianIndices(nms)
        if nms[i] >= high_threshold && edges[i] == 0
            trace_and_threshold!(edges, nms, i, low_threshold)
        end
    end
    out .= edges
end

# Handles subpixel precision.
function (f::Canny)(out₁::GenericGrayImage, out₂::AbstractArray{<:StaticVector}, img::GenericGrayImage)
    @unpack spatial_scale, high, low = f
    @unpack thinning_algorithm = f
    σ = spatial_scale

    # Smooth the image with a Gaussian filter of width σ, which specifies the
    # scale level of the edge detector.
    kernel = KernelFactors.gaussian((σ,σ))
    imgf = imfilter(img, kernel, NA())

    # Calculate the gradient vector at each position of the filtered image.
    # The derivatives are taken with respect to the first and second dimension
    # (rows first, then columns).
    g₁, g₂ = imgradients(imgf, KernelFactors.scharr)
    # Gradient magnitude
    mag = hypot.(g₁, g₂)

    # In StatsBase quantiles are undefined in the presence of NaNs
    # hence we need to keep only valid magnitudes before we can determine
    # the percentiles.
    valid_indices = map(x-> !isnan(x), mag)
    valid_mag = view(mag, valid_indices)

    low_threshold =  typeof(low) <: Percentile ? StatsBase.percentile(vec(valid_mag), low.p) : low
    high_threshold =  typeof(high) <: Percentile ? StatsBase.percentile(vec(valid_mag), high.p) : high

    thinning_algorithm = @set thinning_algorithm.threshold = low_threshold

    # Isolate local maxima of gradient magnitude by “non-maximum suppression”
    # along the local gradient direction.
    nms, subpixel_offsets = thin_subpixel_edges(mag, g₁, g₂, thinning_algorithm)

    # Collect sets of connected edge pixels from the local maxima by applying
    # “hysteresis thresholding”. Edge pixels whose gradient magnitude is below
    # the low threshold are removed; edge pixels whose gradient magnitudes are
    # above (or equal to) the high threshold are retained, and edge pixels whose
    # gradient magnituded are between the low and high threshold are only
    # retained if they are connected to edge pixels whose gradient magnitudes
    # are above or equal to the high threshold.
    edges = zeros(Bool, axes(img))
    @inbounds for i in CartesianIndices(nms)
        if nms[i] >= high_threshold && edges[i] == 0
            trace_and_threshold!(edges, nms, i, low_threshold)
        end
    end
    out₁ .= edges
    out₂ .= subpixel_offsets

    return out₁, out₂
end

function (f::Canny)(out::AbstractArray{<:Color3}, img::AbstractArray{<:Color3})
    T = eltype(img)
    out_temp = zeros(Gray{eltype(T)}, axes(out))
    # NB This will be refactored in future version once a proper color-based
    # edge detection algorithm is implemented. At the moment we just call the
    # default "grayscale" edge detection algorithm.
    out .= convert.(T, f(out_temp, Gray.(img)))
end

function (f::Canny)(out₁::AbstractArray{<:Color3}, out₂::AbstractArray{<:StaticVector}, img::AbstractArray{<:Color3})
    T = eltype(img)
    out_temp = zeros(Gray{eltype(T)}, axes(out₁))
    # NB This will be refactored in future version once a proper color-based
    # edge detection algorithm is implemented. At the moment we just call the
    # default "grayscale" edge detection algorithm.
    out_temp, offsets = f(out_temp, out₂, Gray.(img))

    out₁ .= convert.(T, out_temp)
    return out₁, offsets
end

(f::Canny)(out::GenericGrayImage, img::AbstractArray{<:Color3}) =
    f(out, of_eltype(Gray, img))

(f::Canny)(out₁::GenericGrayImage, out₂::AbstractArray{<:StaticVector}, img::AbstractArray{<:Color3}) =
    f(out₁, out₂, of_eltype(Gray, img))

"""
```
    trace_and_threshold!(out::AbstractArray,  mag::AbstractArray, i₀::CartesianIndex, low::Number)
```

 Marks all pixels of an edge that are 8-connected to i₀ and exhibit a gradient magnitude above `low`.

"""
function trace_and_threshold!(out::AbstractArray, mag::AbstractArray, i₀::CartesianIndex, low::Number)
    stack = Stack{CartesianIndex{2}}()
    push!(stack, i₀)
    # Trace all the pixels that are reachable from the current edge pixel.
    rows, cols = axes(out)
    while !isempty(stack)
        i = pop!(stack)

        # Mark the pixel as visited.
        out[i] = oneunit(eltype(out))
        rᵢ, cᵢ = i.I

        # Ensure we don't exceed the image bounds
        r₀ = (rᵢ > firstindex(rows)) ? rᵢ - 1 : firstindex(rows)
        r₁ = (rᵢ < lastindex(rows)) ? rᵢ + 1 : lastindex(rows)
        c₀ = (cᵢ > firstindex(cols)) ? cᵢ - 1 : firstindex(cols)
        c₁ = (cᵢ < lastindex(cols)) ? cᵢ + 1 : lastindex(cols)

        # Search the neighbourhood for any connected pixels which exceed
        # the minimum edge magnitude threshold `low`.
        @inbounds for r = r₀:r₁
            for c = c₀:c₁
                j = CartesianIndex(r,c)
                if out[j] == zero(eltype(out)) && mag[j] >= low
                    push!(stack, j)
                end
            end
        end
    end
end


using Test
import LinearAlgebra.norm
include("../src/SSSTensor.jl")

 n = 3
 ord = 5
 tol = 1e-9
 nnz = 4

function set_up(n,ord,nnz)
    x = rand(n)
    indices = rand(1:n,nnz,ord)
    for i = 1:nnz
        indices[i,:] = indices[i,sortperm(indices[i, :])]
    end
    vals = rand(nnz)
    x, indices, vals
end

@testset "Contraction Tests" begin

  @testset "COOTen contraction tests" begin
    x, indices, vals = set_up(n,ord,nnz)

    DICTen_A = ssten.SSSTensor([(indices[i,:],vals[i]) for i in 1:nnz],n)
    COOTen_A  = ssten.COOTen(indices,vals,n)

    DICTen_contract = ssten.contract(DICTen_A,x,ord-1)
    COOTen_contract  = ssten.contract_k_1(COOTen_A,x)

    println(DICTen_contract)
    println(COOTen_contract)

    @test norm(DICTen_contract - COOTen_contract)/norm(COOTen_contract) < tol

    COOTen_contract = zeros(n)
    ssten.contract_k_1!(indices,vals,x,COOTen_contract)

    @test norm(DICTen_contract - COOTen_contract)/norm(COOTen_contract)< tol
  end

  @testset "inner product tests" begin
    _, indices, vals = set_up(n,ord,nnz)
    indices = [1 2 3; 2 3 4; 1 3 4; 1 2 4; 2 3 5; 1 2 5]
    vals = [.5,2.3,3.2,3.2,4.3,4.3]

    COOTen_A  = ssten.COOTen(indices,vals)

    test_vals = copy(vals)
    test_vals = test_vals.^2
    test_vals .*= 6

    @test ssten.inner_product(COOTen_A ,COOTen_A) == sum(test_vals)

  end
  #=
  @testset "in place low mem contraction tests" begin
    x = rand(n)
    indices = rand(1:n,nnz,ord)
    for i =1:nnz
        indices[i,:] = indices[i,sortperm(indices[i, :])]
    end
    vals = rand(nnz)

    A = ssten.SSSTensor([(indices[i,:],vals[i]) for i in 1:nnz])

    dictTen_contract = ssten.contract(A,x,ord-1)
    matTen_contract = zeros(n)
    ssten.contract_k_1!(indices,vals,x,matTen_contract)

    @test norm(dictTen_contract - matTen_contract)/norm(matTen_contract)< tol
  end
  =#
end


using Plots
using Statistics
using ProgressBars
L=100
function BoundaryCondition(i)
    if i==L+1
        return 1
    elseif i==0
        return L
    else
        return i
    end
end
function height_cal(i,arr) #calculating the height of the layer in a column
    i=BoundaryCondition(i)
    row=L #starting from the top layer
    height=L
    for j in 1:L
        if arr[row,i]==0.0 #when the entry is empty
            height-=1 #decreasing height by one
            row-=1
        else
            break #exit the loop for the next column
        end
    end
    return height
end
function neighbor_checking(arr, col)
    h=[]
    for i in (col-1):1:(col+1)
        height= height_cal(i,arr)
        if i==col
            push!(h, (height+1))
        else
            push!(h, height)
        end
    end
    return maximum(h)
end
function mean_calculater(arr,L)
    h=[]
    for i in 1:L
        i=BoundaryCondition(i)
        height= height_cal(i,arr)
        push!(h, height)
    end
    return mean(h)
end
function deposing(L,n)
    arr=zeros((L, L))
    meanList=[]
    color=1
    finalP=n
    anim = Animation()
    for particle in ProgressBar(1:n)
        col=rand(1:L)
        height=neighbor_checking(arr, col)
        if height>=L
            finalP=particle
            break
        end
        arr[height, col]=color
        if particle%(10*L*color)==0
            color+=1
        end
        #meanNum=mean_calculater(arr,L)
        #push!(meanList, meanNum)
        p = heatmap(hcat(arr), c=cgrad(:roma, 10, categorical = true, scale = :exp), xlabel="L")
        frame(anim, p)
    end
    gif(anim,"C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter3\\Fig\\3.3.gif",fps=70)
    return arr, meanList, finalP
end
arr,meanList, finalP=deposing(L,15000)
heatmap(hcat(arr), c=cgrad(:roma, 10, categorical = true, scale = :exp), xlabel="L")
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter3\\Fig\\3.3_1.png")
scatter(1:finalP-1,meanList, xlabel="time", ylabel="mean height of the layer")
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter3\\Fig\\3.3_2.png")


import Base: (+), (-), (*), (/), (//), (^), divrem

export monic

# Addition

function (+)(p::SimplePolynomial, q::SimplePolynomial)
    n = max(degree(p), degree(q))
    T = typeof(p(1) + q(1))
    c = zeros(T, n + 1)
    for j = 0:n
        c[j+1] = p[j] + q[j]
    end
    return SimplePolynomial(c)
end

# polynomial + number
function (+)(p::SimplePolynomial, a::T) where {T<:CoefX}
    return p + SimplePolynomial(a)
end

function (+)(a::T, p::SimplePolynomial) where {T<:CoefX}
    return p + SimplePolynomial(a)
end

# Subtraction

function (-)(p::SimplePolynomial)
    coefs = [-x for x in p.data]
    return SimplePolynomial(coefs)
end

(-)(p::SimplePolynomial, q::SimplePolynomial) = p + (-q)
(-)(p::SimplePolynomial, a::T) where {T<:CoefX} = p + (-a)
(-)(a::T, p::SimplePolynomial) where {T<:CoefX} = a + (-p)

# Multiplication

function (*)(a::T, p::SimplePolynomial) where {T<:CoefX}
    coefs = [a * x for x in p.data]
    return SimplePolynomial(coefs)
end

(*)(p::SimplePolynomial, a::T) where {T<:CoefX} = a * p

function (*)(p::SimplePolynomial, q::SimplePolynomial)
    n = degree(p) + degree(q)

    if p == 0 || q == 0
        return SimplePolynomial(0)
    end

    coefs = [p[0] * q[0] for i = 0:n]

    for k = 1:n
        coefs[k+1] = 0 * coefs[k+1]  # clear it out
        for j = 0:k
            coefs[k+1] += p[j] * q[k-j]
        end
    end
    return SimplePolynomial(coefs)
end

# Integer exponentiation

function (^)(p::SimplePolynomial, k::S) where {S<:Integer}

    if k < 0
        f = inv(p)
        return f^(-k)
    end

    if k == 0
        return SimplePolynomial(Int(1))
    end

    if k == 1
        return p
    end

    if k == 2
        return p * p
    end

    if k % 2 == 0  # even exponent
        j = k ÷ 2
        q = p^j
        return q * q
    end

    j = k ÷ 2
    q = p^j
    return q * q * p
end

# Division

function (/)(p::SimplePolynomial, a::T) where {T<:CoefX}
    coefs = p.data .// a
    return SimplePolynomial(coefs)
end
(//)(p::SimplePolynomial, a::T) where {T<:CoefX} = p / a

"""
`monic(p::SimplePolynomial)` returns a new polynomial formed by
dividing all coefficients by the leading coefficient, thereby
yielding a monic polynomial. If `p` is zero, a zero polynomial
is returned.
"""
function monic(p::SimplePolynomial)::SimplePolynomial
    if p == 0
        return p
    end
    return p / lead(p)
end

function divrem(a::SimplePolynomial, b::SimplePolynomial)
    if b == 0
        DivideError()
    end

    #ST = typeof((a[0]+b[0])//1)
    ST = Int
    if eltype(a) <: AbstractMod || eltype(b) <: AbstractMod
        ST = eltype(a)
    else
        ST = typeof((lead(a) + lead(b)) // 1)
    end

    da = degree(a)
    db = degree(b)

    if da < db
        return SimplePolynomial(), a
    end

    leader = a.data[end] // b.data[end]

    coefs = zeros(ST, da - db + 1)
    coefs[end] = leader

    q = SimplePolynomial(coefs)
    aa = a - b * q
    qq, r = divrem(aa, b)

    return q + qq, r
end


"""
	vertCongruence(V::Lar.Array{Float64,2}; ϵ=1e-6)::Tuple{Lar.Points, Array{Array{Int,1},1}}

Evaluates the Vertex Congruence for 3D-points ``V ∈ ℳ(3,n)``.

The function determines the points of `V` closer than `ϵ` and
builds a new Vertex Set made of the representative of each point cluster.

The method returns:
 - the new Vertex Set
 - a map that, for every new vertex, identifies the old vertices it is made of
"""
function vertCongruence(V; ϵ=1e-6)
	Vcls    = []
	let visited = [],  kdtree = NearestNeighbors.KDTree(V)
		for vidx = 1 : size(V, 2)  if !(vidx in visited)
			nearvs = NearestNeighbors.inrange(kdtree, V[:, vidx], ϵ)
			push!(Vcls, nearvs)
			push!(visited, nearvs...)
		end  end
	end

	V = hcat([sum(V[:, cl], dims=2)/length(cl) for cl in Vcls]...)
	return V, Vcls
end


@inline Base.@propagate_inbounds Base.getindex(a::AbstractVec,I::Integer) = getfield(a,I)
Base.IndexStyle(a::Type{<:AbstractVec}) = Base.IndexLinear()

@inline xpos(a::AbstractVec) = a.x
@inline ypos(a::AbstractVec) = a.y
@inline zpos(a::AbstractVec) = a.z

#pos of a node returns a tuple with the positions
pos(a::AbstractVec) = (xpos(a),ypos(a),zpos(a))

Base.norm(a::AbstractVec) = sqrt(muladd(xpos(a), xpos(a), muladd(ypos(a), ypos(a), zpos(a)^2)))
distance(a::AbstractVec,b::AbstractVec) = sqrt((xpos(b)-xpos(a))^2 + (ypos(b)-ypos(a))^2 + (zpos(b)-zpos(a))^2)

Base.:+(a::AbstractVec,b::AbstractVec) = Vec(xpos(a)+xpos(b), ypos(a)+ypos(b), zpos(a)+zpos(b))
Base.:-(a::AbstractVec,b::AbstractVec) = Vec(xpos(a)-xpos(b), ypos(a)-ypos(b), zpos(a)-zpos(b))
Base.:-(a::AbstractVec) = Vec(-xpos(a), -ypos(a), -zpos(a))
Base.:*(a::Number,b::AbstractVec) = Vec(a*xpos(b), a*ypos(b), a*zpos(b))
Base.:*(b::AbstractVec,a::Number) = Vec(a*xpos(b), a*ypos(b), a*zpos(b))
Base.:/(b::AbstractVec,a::Number) = Vec(xpos(b)/a, ypos(b)/a, zpos(b)/a)

Base.dot(a::AbstractVec,b::AbstractVec) = muladd(xpos(a), xpos(b), muladd(ypos(a), ypos(b), zpos(a)*zpos(b)))

Base.cross(a::AbstractVec,b::AbstractVec) = Vec(ypos(a)*zpos(b) - zpos(a)*ypos(b), zpos(a)*xpos(b) - xpos(a)*zpos(b), xpos(a)*ypos(b) - ypos(a)*xpos(b))

struct Vec{T<:Number} <: AbstractVec{T}
    x::T
    y::T
    z::T
end

Base.size(a::Vec) = (3,)
Base.length(a::Vec) = 3
Base.linearindices(a::Vec) = Base.OneTo(3)
Base.indices(a::Vec) = (Base.OneTo(3),)
Base.zero(a::Type{Vec{T}}) where {T} = Vec{T}(zero(T),zero(T),zero(T))
Vec(x,y,z) = Vec(promote(x,y,z)...)

struct Vec2D{T<:Number} <: AbstractVec{T}
    x::T
    y::T
end

Base.size(a::Vec2D) = (2,)
Base.length(a::Vec2D) = 2
Base.linearindices(a::Vec2D) = Base.OneTo(2)
Base.indices(a::Vec2D) = (Base.OneTo(2),)
Base.zero(a::Type{Vec2D{T}}) where {T} = Vec2D{T}(zero(T),zero(T))

Vec2D(x,y) = Vec2D(promote(x,y)...)

@inline zpos(a::Vec2D{T}) where {T} = zero(T)

Base.:+(a::Vec2D{T},b::Vec2D{T2}) where {T,T2} = Vec2D{promote_type(T,T2)}(xpos(a)+xpos(b), ypos(a)+ypos(b))
Base.:-(a::Vec2D{T},b::Vec2D{T2}) where {T,T2} = Vec2D{promote_type(T,T2)}(xpos(a)-xpos(b), ypos(a)-ypos(b))
Base.:-(a::Vec2D{T}) where {T} = Vec2D{T}(-xpos(a), -ypos(a))
Base.:*(a::T,b::Vec2D{T2}) where {T<:Number,T2} = Vec2D{promote_type(T,T2)}(a*xpos(b), a*ypos(b))
Base.:*(b::Vec2D{T},a::T2) where {T,T2<:Number} = Vec2D{promote_type(T,T2)}(a*xpos(b), a*ypos(b))
Base.:/(b::Vec2D{T},a::T2) where {T,T2<:Number} = Vec2D{promote_type(T,T2)}(xpos(b)/a, ypos(b)/a)

Base.dot(a::Vec2D,b::Vec2D) = muladd(xpos(a), xpos(b), ypos(a)*ypos(b))
Base.norm(a::Vec2D) = sqrt(muladd(xpos(a), xpos(a), ypos(a)^2))
Base.cross(a::Vec2D,b::Vec2D) = xpos(a)*ypos(b) - ypos(a)*xpos(b)

struct Vec1D{T<:Number} <: AbstractVec{T}
    x::T
end

Base.size(a::Vec1D) = (1,)
Base.length(a::Vec1D) = 1
Base.linearindices(a::Vec1D) = Base.OneTo(1)
Base.indices(a::Vec1D) = (Base.OneTo(1),)
Base.zero(a::Type{Vec1D{T}}) where {T} = Vec1D{T}(zero(T))

@inline ypos(a::Vec1D{T}) where {T} = zero(T)
@inline zpos(a::Vec1D{T}) where {T} = zero(T)

Base.:+(a::Vec1D{T},b::Vec1D{T2}) where {T,T2} = Vec1D{promote_type(T,T2)}(xpos(a)+xpos(b))
Base.:-(a::Vec1D{T},b::Vec1D{T2}) where {T,T2} = Vec1D{promote_type(T,T2)}(xpos(a)-xpos(b))
Base.:-(a::Vec1D{T}) where {T} = Vec1D{T}(-xpos(a))
Base.:*(a::T,b::Vec1D{T2}) where {T<:Number,T2} = Vec1D{promote_type(T,T2)}(a*xpos(b))
Base.:*(b::Vec1D{T},a::T2) where {T,T2<:Number} = Vec1D{promote_type(T,T2)}(a*xpos(b))
Base.:/(b::Vec1D{T},a::T2) where {T,T2<:Number} = Vec1D{promote_type(T,T2)}(xpos(b)/a)

Base.dot(a::Vec1D,b::Vec1D) = xpos(a)*xpos(b)
Base.norm(a::Vec1D) = abs(xpos(a))
Base.cross(a::Vec1D,b::Vec1D) = 0


#=
testODE:
- Julia version:
- Author: ymocquar
- Date: 2019-11-13
=#
include("../src/interface.jl")
using LinearAlgebra
using Plots
using Plots.PlotMeasures
function fctMain(n_tau)

    #   u0 =[big"0.12345678", big"0.1209182736", big"0.1290582671", big"0.1239681094" ]
    tab_eps = zeros(BigFloat, 7)
    epsilon = big"0.15"
    for i = 1:size(tab_eps, 1)
        tab_eps[i] = epsilon
        epsilon /= 10
    end
    u0 = BigFloat.([-34 // 100, 78 // 100, 67 // 100, -56 // 10])
    B =
        BigFloat.([
            12//100 -78//100 91//100 34//100
            -45//100 56//100 3//100 54//100
            -67//100 09//100 18//100 89//100
            -91//100 -56//100 11//100 -56//100
        ])
    alpha = BigFloat.([12 // 100, -98 // 100, 45 // 100, 26 // 100])
    beta = BigFloat.([-4 // 100, 48 // 100, 23 // 100, -87 // 100])
    fct = (u, p, t) -> B * u + t * p[1] + p[2]
    t_max = big"1.0"
    nbMaxTest = 12
    order = 6
    y = ones(Float64, nbMaxTest, size(tab_eps, 1))
    x = zeros(Float64, nbMaxTest)
    ind = 1
    A = [0 0 1 0; 0 0 0 0; -1 0 0 0; 0 0 0 0]
    for epsilon in tab_eps
        prob = HiOscODEProblem(fct, u0, (big"0.0", t_max), (alpha, beta), A, epsilon, B)
        eps_v = convert(Float32, epsilon)
        println("epsilon = $eps_v")
        nb = 100
        indc = 1
        labels = Array{String,2}(undef, 1, ind)
        while indc <= nbMaxTest
            res = solve(prob, nb_tau = n_tau, order = order, nb_t = nb, dense = false)
            sol = res[end]
            solref = getexactsol(res.par_u0.parphi, u0, t_max)
            println("solref=$solref")
            println("nb=$nb sol=$sol")
            diff = solref - sol
            x[indc] = t_max / nb
            println("nb=$nb dt=$(1.0/nb) normInf=$(norm(diff,Inf)) norm2=$(norm(diff))")
            ni = norm(diff, Inf)
            y[indc, ind] = (ni < 1) ? ni : NaN
            println("epsilon=$epsilon result=$y")
            println("epsilon=$epsilon reslog2=$(log2.(y))")
            nb *= 2
            indc += 1
        end
        for i = 1:ind
            labels[1, i] = " ε = $(convert(Float32,tab_eps[i])) "
        end
        p = Plots.plot(
            x,
            view(y, :, 1:ind),
            xlabel = "Δt",
            xaxis = :log,
            ylabel = "error",
            yaxis = :log,
            legend = :bottomright,
            label = labels,
            marker = 2,
        )
        pp = Plots.plot(
            x,
            view(y, :, 1:ind),
            xlabel = "Δt",
            xaxis = :log,
            ylabel = "error",
            yaxis = :log,
            legend = :bottomright,
            label = labels,
            marker = 2,
            bottom_margin = 30px,
            left_margin = 60px,
        )
        prec_v = precision(BigFloat)
        Plots.savefig(p, "out/r10p_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_epsilon.pdf")
        Plots.savefig(p, "out/r10p_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_epsilon.png")
        Plots.savefig(pp, "out/r10pp_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_epsilon.pdf")
        Plots.savefig(pp, "out/r10pp_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_epsilon.png")
        ind += 1
    end
end

# testODESolverEps()

# for i=3:9
#     fctMain(2^i)
# end
# setprecision(512)
fctMain(32)


using myJuliaUtils
using Test
using LinearAlgebra
using DelimitedFiles
using Statistics

path = dirname(@__FILE__)
# path = "/Users/zymantas/TVPVARPkg/test/testAcf"

y = readdlm("$path/y.txt", ',');
acf2match = readdlm("$path/acf.txt", ',');

@test acf(y[:,1], 0:40) ≈ acf2match[:,1]

@test acf(y, 0:40) ≈ acf2match

for i=1:40
    @test acf(y, i) ≈ acf2match[i+1,:]'
end


a{T<:Real}(x::T,t::T)=one(x)*one(t)
b{T<:Real}(x::T,t::T)=zero(x)*zero(t)
c{T<:Real}(x::T,t::T)=zero(x)*zero(t)

s{T<:Real}(t::T)=exp(t)
sp{T<:Real}(t::T)=exp(t)
u{T<:Real}(x::T,t::T)=exp(-pi^2*t)*cos(pi*x)
ux{T<:Real}(x::T,t::T)=pi*exp(-pi^2*t)*sin(pi*x)
ut{T<:Real}(x::T,t::T)=-pi^2*exp(-pi^2*t)*sin(pi*x)

γf{T<:Real}(x::T,t::T)=one(x)*one(t)


@doc """Ericsson Model
freq: signal frequency(range from 100 to 2000Mhz)
tyArea: area type (1-rural, 2-suburban e 3-urban).
cityKind : cyte type(1-small, 2-medium e 3-large).
hb: base station's height
hm: mobile's height
"""
type EricssonModel<:Model
  freq::Float64
  txH::Float64
  rxH::Float64
  cityKind::Int
  checkFreqRange::Bool
  EricssonModel() = new(800,50,1.5,CityKind.Medium,true)
end
@doc "Calculate Path loss for distance dist(km) with the Ericsson Model"
function pathloss_op(model::EricssonModel,dist::AbstractFloat)
    # frequency
  f::Float64  = model.freq;
  #Bts and MS Heights
  hm::Float64 = model.rxH
  hb::Float64 = model.txH
   # Checking the frequency range
   if (model.checkFreqRange)
     if (f < 100 || f > 2000)
      error("The frequency range for Ericcson Model is 500MHz-1500Mhz")
     end
   end
   # Pathloss
   g = 44.49*log10(f)-4.78*(log10(f))^2
   a2= 12
   a3= 0.1
  if (model.cityKind==CityKind.Large)
    a0 = 36.2
    a1 = 30.2;
  elseif (model.cityKind==CityKind.Medium)
    a0 = 43.2
    a1 = 68.9
  else
    a0 = 45.9
    a1 = 100.6
  end
  PL=a0+a1*log10(dist)+a2*log10(hb)+a3*(log10(hb))*(log10(dist))-3.2*log10((11.75*hm)^2)+g
  return PL
end


using QuantLib

## TYPES ##
type ReplicationError
  maturity::Float64
  payoff::PlainVanillaPayoff
  s0::Float64
  sigma::Float64
  r::Float64
  vega::Float64
end

function ReplicationError(optionType::OptionType, maturity::Float64, strike::Float64, s0::Float64, sigma::Float64, r::Float64)
  rDiscount = exp(-r * maturity)
  qDiscount = 1.0
  forward = s0 * rDiscount / rDiscount
  stdDev = sqrt(sigma * sigma * maturity)
  payoff = PlainVanillaPayoff(optionType, strike)

  black = BlackCalculator(payoff, forward, stdDev, rDiscount)

  vega_ = vega(black, maturity)

  return ReplicationError(maturity, payoff, s0, sigma, r, vega_)
end

type ReplicationPathPricer{OT <: OptionType} <: QuantLib.AbstractPathPricer
  optionType::OT
  strike::Float64
  r::Float64
  maturity::Float64
  sigma::Float64
end

## Type Methods ##

function call(rpp::ReplicationPathPricer, path::Path)
  n = length(path)
  # discrete hedging interval
  dt = rpp.maturity / n
  # we assume the stock pays no dividend
  stockDividendYield = 0.0
  # starting...
  t = 0
  stock = path[1] # stock at t=0
  money_account = 0.0 # money_account at t=0

  ### Initial Deal ###
  # Option fair price (black-scholes) at t=0
  rDiscount = exp(-rpp.r * rpp.maturity)
  qDiscount = exp(-stockDividendYield * rpp.maturity)
  forward = stock * qDiscount / rDiscount
  stdDev = sqrt(rpp.sigma * rpp.sigma * rpp.maturity)

  payoff = PlainVanillaPayoff(rpp.optionType, rpp.strike)
  black = BlackCalculator(payoff, forward, stdDev, rDiscount)
  # sell the option, cash in its premium
  money_account += value(black)
  # compute delta
  delta_ = delta(black, stock)
  # delta-hedge the option buying stock
  stockAmount = delta_
  money_account -= stockAmount * stock

  ## Heding during option life ##
  for step = 1:n-1
    # time flows
    t += dt
    # accruing on the money account
    money_account *= exp(rpp.r * dt)

    # stock growth
    stock = path[step + 1]

    # recalculate the option value at the current stock value, and the current time to maturity
    rDiscount = exp(-rpp.r * (rpp.maturity - t))
    qDiscount = exp(-stockDividendYield * (rpp.maturity - t))
    forward = stock * qDiscount / rDiscount
    stdDev = sqrt(rpp.sigma * rpp.sigma * (rpp.maturity - t))
    black = BlackCalculator(payoff, forward, stdDev, rDiscount)
    # recalculate delta
    delta_ = delta(black, stock)
    # re-hedging
    money_account -= (delta_ - stockAmount) * stock
    stockAmount = delta_
  end

  ### Option expiration ###
  # last accrual on my money account
  money_account *= exp(rpp.r * dt)
  # last stock growth
  stock = path[n]
  # the hedger delivers the option payoff to the option holder
  optionPayoff = PlainVanillaPayoff(rpp.optionType, rpp.strike)(stock)
  money_account -= optionPayoff
  # and unwinds the hedge selling his stock position
  money_account += stockAmount * stock
  # final PnL
  return money_account
end

function compute(rp::ReplicationError, nTimeSteps::Int, nSamples::Int)
  cal = QuantLib.Time.TargetCalendar()
  tdy = Dates.today()

  dc = QuantLib.Time.Actual365()

  stateVar = Quote(rp.s0)
  riskFreeRate = FlatForwardTermStructure(tdy, rp.r, dc)
  dividendYield = FlatForwardTermStructure(tdy, 0.0, dc)
  vol = BlackConstantVol(tdy, cal, rp.sigma, dc)
  diffuse = BlackScholesMertonProcess(stateVar, riskFreeRate, dividendYield, vol)

  # Black scholes equation rules the path generator
  # At each step of the log of the stock will ahve drift and sigma^2 variance
  rsg = QuantLib.Math.InverseCumulativeRSG(0, nTimeSteps)
  brownianBridge = false
  myPathGenerator = PathGenerator(diffuse, rp.maturity, nTimeSteps, rsg, brownianBridge)

  myPathPricer = ReplicationPathPricer(rp.payoff.optionType, rp.payoff.strike, rp.r, rp.maturity, rp.sigma)
  statsAccumulator = QuantLib.Math.gen_RiskStatistics()

  mcSim = MonteCarloModel(myPathGenerator, myPathPricer, statsAccumulator, false)

  add_samples!(mcSim, nSamples)

  PLMean = QuantLib.Math.stats_mean(mcSim.sampleAccumulator)
  PLStdDev = QuantLib.Math.stats_std_deviation(mcSim.sampleAccumulator)
  PLSkew = QuantLib.Math.stats_skewness(mcSim.sampleAccumulator)
  PLKurtosis = QuantLib.Math.stats_kurtosis(mcSim.sampleAccumulator)

  println("P&L Mean: ", PLMean)
  println("P&L Standard Deviation: ", PLStdDev)
  println("P&L Skew: ", PLSkew)
  println("P&L Kurtosis: ", PLKurtosis)
end

function main()
  maturity = 1.0 / 12.0 # 1 month
  strike = 100.0
  underlying = 100.0
  vol = 0.20 # 20%
  riskFreeRate = 0.05 # 5%
  rp = ReplicationError(Call(), maturity, strike, underlying, vol, riskFreeRate)

  scenarios = 50000
  hedgesNum = 21
  compute(rp, hedgesNum, scenarios)
  hedgesNum = 84
  compute(rp, hedgesNum, scenarios)
end


using Petkovsek
using SymPy

@syms n y

@test algpoly([3, n, n-1], 0*n, n) == 0

@test alghyper([2*n*(n+1), -(n^2 +3*n-2), n-1], n) == [n+1, 2]


@testset "Monomial compression test" begin

    R, (v1, v2, v3, v4) = Nemo.PolynomialRing(Nemo.QQ, ["v1", "v2", "v3", "v4"])
    tests = [
        v1 + v2 - 2,
        v4 + v4 * v1 - 3 * v2 * v4,
        (v1 + v2 + v2^2) * (v3 + v4),
        (v1 + v2^2) * (v3^3 + v4) - 7 * (v1 - 3 - v2) * (v3 - v4^2 - v3^2)
    ]

    for t in tests
        a, b = monomial_compress(t, [v1, v2])
        s = 0
        for (x, y) in zip(a, b)
            s += parent_ring_change(x, parent(t)) * parent_ring_change(y, parent(t))
        end
        @test s == t
    end

end


@diffusion_process FitzHughDiffusionConjugAuxLin begin
    :dimensions
    process --> 2
    wiener --> 1

    :parameters
    (ϵ, s, γ, β, σ, t, T) --> Float64
    (u, v) --> ℝ{2}

    :additional
    constdiff --> true
    linear --> true
end

function B(t, P::FitzHughDiffusionConjugAuxLin)
    @SMatrix [
        0.0  1.0;
        (P.ϵ-P.γ-3.0*P.ϵ*P.v[1]^2-6*P.ϵ*P.v[1]*P.v[2]) (P.ϵ-1.0-3.0*P.ϵ*P.v[1]^2)
    ]
end

function β(t, P::FitzHughDiffusionConjugAuxLin)
    ℝ{2}(
        0.0,
        2*P.ϵ*P.v[1]^3+P.s-P.β+6*P.ϵ*P.v[1]^2*P.v[2]
    )
end

σ(t, P::FitzHughDiffusionConjugAuxLin) = ℝ{2}(0.0, P.σ)


# sample function, its gradient & derivative definintion

foo(x) = sum(x, 2)
foo_grad(dzdy, x) = repmat(dzdy, 1, size(x, 2))
@specialdiff (y = foo(x)) (dz!dx = foo_grad(dz!dy, x))

function test_special(u, v)
    x = u .+ v
    y = foo(x)
    z = sum(1.0 .* y) # note: don't remove 1.0
end

test_compare(test_special; u=randn(3, 3), v=randn(3, 3))


function solve()
    n, d = [parse(Int, x) for x in split(readline())]
    ret = 0
    for _=1:n
        x, y = [parse(Int, x) for x in split(readline())]
        if sqrt(x*x + y*y) ≤ d
            ret += 1
        end
    end
    ret
end

println(solve())


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/FEMQuad.jl/blob/master/LICENSE

using FEMQuad, Test
using FEMQuad: integrate_2d, get_rule

@testset "Gauss-Legendre quadratures in triangles" begin
    rules = (:GLTRI1, :GLTRI3, :GLTRI4, :GLTRI6, :GLTRI7, :GLTRI12)
    f(i) = x -> sum(sum(x)^j for j=0:i)
    r(i::Int) = get_rule(i, rules...)

    @test isapprox(integrate_2d(f(0), r(0)), 1/2)
    @test isapprox(integrate_2d(f(1), r(1)), 5/6)
    @test isapprox(integrate_2d(f(2), r(2)), 13/12)
    @test isapprox(integrate_2d(f(3), r(3)), 77/60)
    @test isapprox(integrate_2d(f(4), r(4)), 29/20)
    @test isapprox(integrate_2d(f(5), r(5)), 223/140)
    @test isapprox(integrate_2d(f(6), r(6)), 481/280)
    #@test isapprox(integrate_2d(f(7), r(7)), 4609/2520)
    #@test isapprox(integrate_2d(f(8), r(8)), 4861/2520)
    #@test isapprox(integrate_2d(f(9), r(9)), 55991/27720)
end

@testset "Gauss-Legendre quadratures in triangles B" begin
    rules = (:GLTRI1, :GLTRI3B, :GLTRI4B, :GLTRI6, :GLTRI7, :GLTRI12)
    f(i) = x -> sum(sum(x)^j for j=0:i)
    r(i::Int) = get_rule(i, rules...)

    @test isapprox(integrate_2d(f(2), r(2)), 13/12)
    @test isapprox(integrate_2d(f(3), r(3)), 77/60)
end


using Metatheory

comm_monoid = @theory begin
    a * b => b * a
    a * 1 => a
    a * (b * c) => (a * b) * c
    a::Number * b::Number |> a*b
end

G = EGraph(:(3 * 4))
@testset "Basic Constant Folding Example - Commutative Monoid" begin
    @test (true == @areequalg G comm_monoid 3 * 4 12)
    @test (true == @areequalg G comm_monoid 3 * 4 12 4*3  6*2)
end

@testset "Basic Constant Folding Example 2 - Commutative Monoid" begin
    ex = :(a * 3 * b * 4)
    G = EGraph(ex)
    @test (true == @areequalg G comm_monoid (3 * a) * (4 * b) (12*a)*b ((6*2)*b)*a)
end


matrixdata =
"""
"random symmetric matrix"
function randsym{T}(::Type{T}, n)
 A = zeros(T, n, n)
  for j = 1:n
      for i = j:n
          A[i,j] = randn()
      end
  end
  A = A + tril(A, -1)'
  return A
end
randsym(n) = randsym(Float64, n)
include_generator(FunctionName, "randsym", randsym)
include_generator(Group, "random", randsym)
include_generator(Group, "symmetric", randsym)
"""
user_dir = joinpath(dirname(@__FILE__), "..", "myMatrixDepot")

open(string(user_dir, "/generator.jl"), "w") do f
    write(f, matrixdata)
end
workspace()
using MatrixDepot
using Base.Test

n = rand(1:8)
A = matrixdepot("randsym", n)
matrixdepot("randsym")
@test "randsym" in matrixdepot("random")
@test "randsym" in matrixdepot("symmetric")

user_dir = joinpath(dirname(@__FILE__), "..", "myMatrixDepot")
rm(user_dir, recursive = true)
workspace()
using MatrixDepot
using Base.Test


module MiraculousNN

export fit

using CUDA
using ProgressMeter

include("metric.jl")
include("forprop.jl")
include("backprop.jl")
include("loss.jl")
include("utils.jl")

using .metric
using .forprop
using .backprop
using .loss
using .utils

CUDA.allowscalar(false)

fit(x, y, epochs::Int; params...) = fit(x, y; epochs = epochs, params...)

function fit(
    x,
    y;
    epochs = 10,
    batch_size = 16,
    learning_rate = 0.1,
    wd = 0.01,
    verbose = true,
    use_gpu = true,
)
    input_size = size(x, 1)
    output_size = size(y, 1)

    x, y = if use_gpu && CUDA.functional()
        CuArray(x), CuArray(y)
    else
        Array(x), Array(y)
    end

    nn_params = _init_layers(typeof(x), [input_size, 200, 200, output_size])

    nlayers = get_nlayers(nn_params)
    num_batches = div(size(x, 2), batch_size) - 1

    train_accuracy = 0
    nn_values = Dict()
    grads = Dict()

    for i = 1:epochs
        p = Progress(
            num_batches;
            desc = "Epoch $i",
            barglyphs = BarGlyphs("|-> |"),
            enabled = verbose,
        )

        for b_idx = 1:num_batches
            b = b_idx * batch_size
            x_batch = view(x, :, b:(b + batch_size - 1))
            y_batch = view(y, :, b:(b + batch_size - 1))

            nn_values = _forward(x_batch, nn_params)

            grads = _backward(nn_params, nn_values, x_batch, y_batch)
            _update_weights!(nn_params, grads, learning_rate, wd)

            nn_values = _forward(x, nn_params)
            pred_proba = nn_values["A$nlayers"]

            cost = _compute_cost(y, pred_proba, wd, nn_params)
            train_accuracy = metric.accuracy(y, pred_proba)

            ProgressMeter.next!(
                p;
                showvalues = [(:cost, cost), (:train_accuracy, train_accuracy)],
            )
        end
    end
end

function _init_layers(T::Type, layer_sizes)::Dict{String,T}
    params = Dict()

    for i = 2:length(layer_sizes)
        W = rand(Float64, layer_sizes[i], layer_sizes[i - 1]) * 0.01
        B = zeros(Float64, layer_sizes[i], 1)
        params["W$(i - 1)"] = T(W)
        params["B$(i - 1)"] = T(B)
    end
    return params
end

_init_layers(layer_sizes) = _init_layers(Matrix{Float64}, layer_sizes)

function _forward(x, params::Dict{String,T})::Dict where {T}
    nlayers = get_nlayers(params)
    nn_values = Dict{String,T}()

    last_output = x

    for i = 1:nlayers
        nn_values["Z$i"] = params["W$i"] * last_output .+ params["B$i"]

        nn_values["A$i"] = if i == nlayers
            forprop.softmax(nn_values["Z$i"])
        else
            forprop.relu.(nn_values["Z$i"])
        end

        last_output = nn_values["A$i"]
    end
    return nn_values
end

function _backward(
    params::Dict{String,T},
    nn_values::Dict{String,T},
    x,
    y,
)::Dict{String,T} where {T}
    nlayers = get_nlayers(params)
    num_samples = size(y, 2)
    grads = Dict()

    dA = nn_values["A$nlayers"]
    dZ = backprop.softmax.(dA, y)

    for i = nlayers:-1:1
        if i != nlayers
            dA = transpose(params["W$(i + 1)"]) * dZ
            dZ = backprop.relu.(dA, nn_values["A$i"])
        end

        next_output = i == 1 ? x : nn_values["A$(i - 1)"]

        grads["W$i"] = dZ * transpose(next_output) / num_samples
        grads["B$i"] = sum(dZ, dims = 2) / num_samples
    end

    return grads
end

get_nlayers(params::Dict)::Int = (keys(params) |> length) / 2

function _update_weights!(
    params::Dict{String,T},
    grads::Dict{String,T},
    learning_rate,
    wd,
)::Nothing where {T}
    nlayers = get_nlayers(params)

    for i = 1:nlayers
        params["W$i"] .-= learning_rate * (grads["W$i"] + 2 * wd .* params["W$i"])
        params["B$i"] .-= learning_rate * grads["B$i"]
    end
end

function _compute_cost(y, pred_proba, wd, params)
    weight_penalty = sum(sum(params[k] .^ 2) for k in keys(params) if k[1] == 'W')
    num_samples = size(y, 2)

    return (sum(loss.logistic.(y, pred_proba)) + wd * weight_penalty) / num_samples
end

end


# elimat(m) returns the elimination matrix Lm
# The elimination matrix Lm is for any matrix F, Vech(F)=Lm Vec(F)
function elimat(m::Int64)
    A = eye(m^2)
    L = A[1:m,:]
    for n in 2:m
        S = A[m*(n-1)+1:n*m,:]
        S = S[n:end,:]
        L = [L;S]
    end
    return L
end


# S is X,Y
# T is maxpasses
# p: # of features
# n: # of data points
# k: size of minibatch
function pegasos(X::SparseOrFullMat,
	             Y::Vector;
	             k::Integer = 5,
	             lambda::Real = 0.1,
	             T::Integer = 100)
	# p features, n observations
	p, n = size(X)

	# Initialize weights so norm(w) <= 1 / sqrt(lambda)
	w = randn(p)
	sqrtlambda = sqrt(lambda)
	normalizer = sqrtlambda * norm(w)
	for j in 1:p
		w[j] /= normalizer
	end

	# Allocate storage for repeated used arrays
	deltaw = Array(Float64, p)
	w_tmp = Array(Float64, p)

	# Loop
	for t in 1:T
		# Calculate stepsize parameters
		alpha = 1.0 / t
		eta_t = 1.0 / (lambda * t)

		# Calculate scaled sum over misclassified examples
		# Subgradient over minibatch of size k
		fill!(deltaw, 0.0)
		for i in 1:k
			# Select a random item from X
			# This is one element of At of S
			index = rand(1:n)

			# Test if prediction isn't sufficiently good
			# If so, current item is element of At+
			pred = Y[index] * inner_prod(w, X[:, index])
			if pred < 1.0
				# Update subgradient
				add_vec!(deltaw, X[:, index], e -> Y[index] * e)
			end
		end

		# Rescale subgradient
		for j in 1:p
			deltaw[j] *= (eta_t / k)
		end

		# Calculate tentative weight-update
		for j in 1:p
			w_tmp[j] = (1.0 - alpha) * w[j] + deltaw[j]
		end

		# Find projection of weights into L2 ball
		proj = min(1.0, 1.0 / (sqrtlambda * norm(w_tmp)))
		for j in 1:p
			w[j] = proj * w_tmp[j]
		end
	end

	return SVMFit(w, T, true)
end


"""
Ordinary differential equation in radial direction for the
hyperboloidally compactified Teukolsky equation.
"""
module RadialODE

export eig_vals_vecs_c

include("CustomTypes.jl")
include("Chebyshev.jl")
using .CustomTypes
import .Chebyshev as CH

#import IterativeSolvers: invpowm
using LinearAlgebra
using GenericSchur
using SparseArrays
using Printf: @printf

using ApproxFun

"""
    radial_discretized_eqn(
      nr::myI,
      s::myI,
      m::myI,
      a::myF,
      bhm::myF,
      om::myC,
      rmin::myF,
      rmax::myF
    )
"""
function radial_discretized_eqn(
      nr::myI,
      s::myI,
      m::myI,
      a::myF,
      bhm::myF,
      om::myC,
      rmin::myF,
      rmax::myF
)
   ## Using position space method
   #=
   D1 = CH.mat_D1(rmin,rmax,nr)
   D2 = D1*D1

   Id = sparse(I,nr,nr)

   X1 = CH.mat_X(rmin,rmax,nr)
   X2 = X1*X1
   X3 = X1*X2
   X4 = X1*X3
   =#

   ## Using Ultraspherical polynomial method
   X1 = ApproxFun.Fun(rmin..rmax);
   X2 = X1^2
   X3 = X1^3
   X4 = X1^4
   D1 = ApproxFun.Derivative()
   D2 = D1^2

   A = (
        tomyC(2*im)*om*I
        -
        tomyF(2)*(tomyF(1) + s)*X1
        +
        tomyF(2)*(
             im*om*((a^2) - tomyF(8)*(bhm^2))
             +
             im*m*a
             +
             (s + tomyF(3))*bhm
         )*X2
        +
        tomyF(4)*(tomyF(2)*im*om*bhm - tomyF(1))*(a^2)*X3
   )
   B = (
     (
      ((a^2) - tomyF(16)*(bhm^2))*(om^2)
      +
      tomyF(2)*(m*a + tomyF(2)*im*s*bhm)*om
     )*I
     +
     tomyF(2)*(
      tomyF(4)*((a^2) - tomyF(4)*(bhm^2))*bhm*(om^2)
      +
      (tomyF(4)*m*a*bhm - tomyF(4)*im*(s + tomyF(2))*(bhm^2) + im*(a^2))*om
      +
      im*m*a
      +
      (s + tomyF(1))*bhm
     )*X1
     +
     tomyF(2)*(
          tomyF(8)*(bhm^2)*(om^2)
          +
          tomyF(6)*im*bhm*om
          -
          tomyF(1)
     )*(a^2)*X2
   )

   return (
      -
      (X2 - tomyF(2)*bhm*X3 + (a^2)*X4)*D2
      +
      A*D1
      +
      B
   ) : ApproxFun.space(X1)
end

"""
    radial_discretized_eqn_p(
      np::myI,
      s::myI,
      m::myI,
      a::myF,
      bhm::myF,
      om::myC
    )
"""
function radial_discretized_eqn_p(
      np::myI,
      s::myI,
      m::myI,
      a::myF,
      bhm::myF,
      om::myC
)
   X = Vector{myI}()
   Y = Vector{myI}()
   V = Vector{myC}()

   A0 = tomyF(2)*im*om
   A1 = -tomyF(2)*(tomyF(1)+s)
   A2 = tomyF(2)*(im*om*((a^2) - tomyF(8)*(bhm^2)) + im*m*a + (s+tomyF(3))*bhm)
   A3 = tomyF(4)*(tomyF(2)*im*om*bhm - tomyF(1))*(a^2)

   B0 = ((a^2) - tomyF(16)*(bhm^2))*(om^2) + tomyF(2)*(m*a + tomyF(2)*im*s*bhm)*om
   B1 = tomyF(2)*(
           tomyF(4)*((a^2) - tomyF(4)*(bhm^2))*bhm*(om^2)
           +
           (tomyF(4)*m*a*bhm - tomyF(4)*im*(s+2)*(bhm^2) + im*(a^2))*om
           +
           im*m*a
           +
           (s + tomyF(1))*bhm
          )
   B2 = tomyF(2)*(tomyF(8)*((bhm*om)^2) + tomyF(6)*im*om*bhm - tomyF(1))*(a^2)

   push!(X,1)
   push!(Y,1)
   push!(V,B0)

   push!(X,1)
   push!(Y,2)
   push!(V,A0)

   push!(X,2)
   push!(Y,1)
   push!(V,B1)

   push!(X,2)
   push!(Y,2)
   push!(V,A1+B0)

   push!(X,2)
   push!(Y,3)
   push!(V,2*A0)

   for i=3:(np-1)
      n = i-1
      push!(X,i)
      push!(Y,i-2)
      push!(V,-(n-2)*(n-3)*(a^2) + (n-2)*A3 + B2)

      push!(X,i)
      push!(Y,i-1)
      push!(V,2*(n-1)*(n-2)*bhm + (n-1)*A2 + B1)

      push!(X,i)
      push!(Y,i)
      push!(V,-n*(n-1) + n*A1 + B0)

      push!(X,i)
      push!(Y,i+1)
      push!(V,(n+1)*A0)
   end

   n = np-1
   push!(X,np)
   push!(Y,np-2)
   push!(V,-(n-2)*(n-3)*(a^2) + (n-2)*A3 + B2)

   push!(X,np)
   push!(Y,np-1);
   push!(V,2*(n-1)*(n-2)*bhm + (n-1)*A2 + B1)

   push!(X,np)
   push!(Y,np)
   push!(V,-n*(n-1) + n*A1 + B0)

   mat = dropzeros(sparse(X,Y,V))
   for i=1:np
      mat[:,i] .*= 2^(i-1)
   end

   mat = dropzeros(sparse(X,Y,V))

   #for i=1:np
   #   mat[:,i] .*= 2^(i-1)
   #end

   return mat
end

"""
    eig_vals_vecs_c(
      nr::myI,
      s::myI,
      m::myI,
      a::myF,
      om::myC
    )

Compute eigenvectors and eigenvalues for the radial equation
using a pseudospectral Chebyshev polynomial method.
The black hole mass is always one.
"""
function eig_vals_vecs_c(
      nr::myI,
      s::myI,
      m::myI,
      a::myF,
      om::myC
   )
   bhm  = tomyF(1) ## always have unit black hole mass
   rmin = tomyF(0)
   rmax = tomyF(abs(a)>0 ? (bhm/(a^2))*(tomyF(1) - sqrt(tomyF(1)- ((a/bhm)^2))) : tomyF(0.5)/bhm)

   L = radial_discretized_eqn(nr,s,m,a,bhm,om,rmin,rmax)
   t = ApproxFun.eigs(L,nr,tolerance=1e-12)

   mini = argmin(abs.(t[1]))

   #nMat = radial_discretized_eqn_p(nr,s,m,a,bhm,om)
   #nt = eigen(Matrix(nMat), permute=true, scale=true, sortby=abs)
   #println(nMat)

   return -t[1][mini], t[2][mini], CH.cheb_pts(rmin,rmax,nr)
end

end # module


const N = 256
const N2 = N * div(N - 1, 2)
const step = 0.01
const xval_table = zeros(Float64, N)
const tsin_table = zeros(Float64, N)
const tcos_table = zeros(Float64, N)
const ttan_table = zeros(Float64, N)
const rsin_cache = Dict{Float64, Float64}()
const rcos_cache = Dict{Float64, Float64}()
const rtan_cache = Dict{Float64, Float64}()

function rho(x, y, rhocache, i, n)
    if n < 0
        return 0.0
    elseif n == 0
        return y[i+1]
    end
    idx = (N - 1 - n) * div(N - n, 2) + i
    if !haskey(rhocache, idx)
        rhocache[idx] = (x[i+1] - x[i + n+1]) / (rho(x, y, rhocache, i, n - 1) -
            rho(x, y, rhocache, i + 1, n - 1)) + rho(x, y, rhocache, i + 1, n - 2)
    end
    rhocache[idx]
end

function thiele(x, y, r, xin, n)
    if n > N - 1
        return 1.0
    end
    rho(x, y, r, 0, n) - rho(x, y, r, 0, n - 2) + (xin - x[n + 1]) / thiele(x, y, r, xin, n + 1)
end

function thiele_tables()
    for i in 1:N
        xval_table[i] = (i-1) * step
        tsin_table[i] = sin(xval_table[i])
        tcos_table[i] = cos(xval_table[i])
        ttan_table[i] = tsin_table[i] / tcos_table[i]
    end
    println(6 * thiele(tsin_table, xval_table, rsin_cache, 0.5, 0))
    println(3 * thiele(tcos_table, xval_table, rcos_cache, 0.5, 0))
    println(4 * thiele(ttan_table, xval_table, rtan_cache, 1.0, 0))
end

thiele_tables()


using Gridap
using Test
n = 10
model = CartesianDiscreteModel(domain=(0.0,1.0,0.0,1.0), partition=(n,n))
labels = FaceLabels(model)
add_tag_from_tags!(labels,"diri1",[6,])
add_tag_from_tags!(labels,"diri0",[1,2,3,4,5,7,8])
D = 2
order = 2
fespace1 = FESpace(
  reffe=:Lagrangian, conformity=:H1, valuetype=VectorValue{D,Float64},
  model=model, labels=labels, order=order, diritags=["diri0","diri1"])
fespace2 = FESpace(
  reffe=:PLagrangian, conformity=:L2, valuetype=Float64,
  model=model, order=order-1, constraint=:zeromean)
uD0(x) = VectorValue(0.0,0.0)
uD1(x) = VectorValue(1.0,0.0)

V = TestFESpace(fespace1)
Q = TestFESpace(fespace2)
Y = [V, Q]

U = TrialFESpace(fespace1,[uD0,uD1])
P = TrialFESpace(fespace2)
X = [U, P]
const Re = 10.0
@law conv(x,u,∇u) = Re*(∇u')*u
@law dconv(x,du,∇du,u,∇u) = conv(x,u,∇du)+conv(x,du,∇u)

function a(y,x)
  u, p = x
  v, q = y
  inner(∇(v),∇(u)) - inner(div(v),p) + inner(q,div(u))
end

c(v,u) = inner(v,conv(u,∇(u)))
dc(v,du,u) = inner(v,dconv(du,∇(du),u,∇(u)))

function res(x,y)
  u, p = x
  v, q = y
  a(y,x) + c(v,u)
end

function jac(x,y,dx)
  u, p = x
  v, q = y
  du, dp = dx
  a(y,dx)+ dc(v,du,u)
end
trian = Triangulation(model)
quad = CellQuadrature(trian,degree=(order-1)*2)
t_Ω = NonLinearFETerm(res,jac,trian,quad)
op = NonLinearFEOperator(Y,X,t_Ω)
using LineSearches: BackTracking
nls = NLSolver(
  show_trace=false, method=:newton, linesearch=BackTracking())
solver = NonLinearFESolver(nls)
uh, ph = solve(solver,op)
#writevtk(trian,"ins-results",cellfields=["uh"=>uh,"ph"=>ph])

meanval = sum( integrate(ph,trian,quad))
@test meanval + 1 ≈ 1


"""
    InhomogPoisson(λ)

Inhomogenous Poisson process with intensity function `λ(t)`.
See also `ThinningAlg`.
"""
struct InhomogPoisson{T<:Function}
    λ::T
end

abstract type PoissonSampler
end

"""
    ThinningAlg(λmax)

Sampling method for `InhomogPoisson` by the 'thinning' algorithm.

#### Examples:

```
sample(ThinningAlg(λmax), T, InhomogPoisson(λ))
```
"""
struct ThinningAlg <: PoissonSampler
    λmax::Float64
end

"""
    sample(::Thinning, T, P::InhomogPoisson) -> tt
"""
function sample(method::ThinningAlg, T, P::InhomogPoisson)
    λmax = method.λmax
    t = 0.0
    tt = zeros(0)
    while t <= T
        t = t - log(rand())/λmax
        if rand() ≤ P.λ(t)/λmax
            push!(tt, t)
        end
    end
    tt
end


# Common graduated values classification
struct EqualIntervals <:  AbstractGraduatedMapClassificator
    k::Int
    EqualIntervals(k::Int = 5) = return new(k);
end

struct Quantiles <: AbstractGraduatedMapClassificator
    k::Int
    Quantiles(k::Int = 5) = return new(k);
end

struct NaturalBreaks <: AbstractGraduatedMapClassificator
    k::Int
    NaturalBreaks(k::Int = 5) = return new(k);
end

struct CustomBreaks <: AbstractGraduatedMapClassificator
    k::Int
    bins::Union{Vector{Int}, Vector{Float64}}
    CustomBreaks(bins::Union{Vector{Int}, Vector{Float64}}) = return new(length(bins) + 1, bins);
end

# Bounds
function mapclassifybounds(mcr::EqualIntervals, x::Vector{T} where T<:Real)::Tuple{Vector{Float64}, Vector{Float64}}
    k = mcr.k
    minx = minimum(x)
    maxx = maximum(x)
    stepinc = (maxx - minx) / k

    lbound = zeros(k)
    ubound = zeros(k)

    for i in 1:k
        lbound[i] = minx + (i - 1) * stepinc
        ubound[i] = minx + i * stepinc
    end

    lbound, ubound
end

function mapclassifybounds(mcr::Quantiles, x::Vector{T} where T<:Real)::Tuple{Vector{Float64}, Vector{Float64}}
    k = mcr.k
    qx = quantile(x, range(1/k, 1, length = k))

    lbound = zeros(k)
    lbound[1] = minimum(x)
    lbound[2:k] = qx[1:k-1]

    ubound = qx

    lbound, ubound
end

function mapclassifybounds(mcr::CustomBreaks, x::Vector{T} where T<:Real)::Tuple{Vector{Float64}, Vector{Float64}}
    bins = mcr.bins

    lbound = vcat(minimum(x), bins)
    ubound = vcat(bins, maximum(x))

    lbound, ubound
end

function mapclassifybounds(mcr::NaturalBreaks, x::Vector{T} where T<:Real)::Tuple{Vector{Float64}, Vector{Float64}}
    k = mcr.k
    n = length(x)
    xs = sort(x)

    (k < n) || throw(ArgumentError("number of classes should be lower than number of observations"))

    # Create matrices and set initial values
    mat1 = zeros(Int64,   n, k) # Lower class limits
    mat2 = zeros(Float64, n, k) # Variance combinations

    mat1[1, :]     .= 1
    mat2[2:end, :] .= Inf   # Set to the most larger possible value

    # Loop through each observation
    v = 0.0
    @inbounds for l in 2:n
        s1 = 0.0 # sum
        s2 = 0.0 # sum of squares
        w = 0 # Number of observations considered

        # Loop through observations till l in reverse order
        for m in reverse(1:l)
            val = xs[m]

            # Increase sum of sqaures and observations
            s1 = s1 + val
            s2 = s2 + val * val
            w = w + 1

            # Variance
            v = s2 - (s1 * s1) / w

            # Update lower bound and variance if newer is lower
            prev = m - 1
            if prev != 0
                for j in 2:k
                    if mat2[l, j] >= (v + mat2[prev, j - 1])
                        mat1[l, j] = m
                        mat2[l, j] = v + mat2[prev, j - 1]
                    end
                end
            end
        end

        # Store lower class limits and variance
        mat1[l, 1] = 1
        mat2[l, 1] = v

    end

    # Get class bounds
    kclass = zeros(k + 1)
    kclass[end] = xs[end]
    kclass[1] = xs[1]

    # Get class bounds
    lbound = zeros(k)
    ubound = zeros(k)

    pivot = n
    for ki in reverse(2:k)
        pivot = mat1[pivot, ki] - 1
        lbound[ki] = xs[pivot]
    end

    lbound[1] = minimum(xs)
    ubound[1:k-1] = lbound[2:k]
    ubound[k] = maximum(xs)

    lbound, ubound
end

# Structure for Graduated  Map Classification
struct GraduatedMapClassification <: AbstractMapClassification
    mcr::AbstractMapClassificator
    k::Int
    group::Vector{Int}
    ngroup::Vector{Int}
    lbound::Vector{Float64}
    ubound::Vector{Float64}
    lower::Symbol
    upper::Symbol
end

function mapclassify(mcr::AbstractGraduatedMapClassificator, x::Vector{T} where T<:Real;
    lower::Symbol = :open, upper::Symbol = :closed)::GraduatedMapClassification

    ((lower == :open) || (lower == :closed )) || throw(ArgumentError("lower bound must be :open or :closed"))
    ((upper == :open) || (upper == :closed )) || throw(ArgumentError("upper bound must be :open or :closed"))
    (lower !== upper) || throw(ArgumentError("lower and upper bounds cannot be both :open or :closed"))

    lbound, ubound = mapclassifybounds(mcr, x)

    k = mcr.k
    group = zeros(Int, length(x))
    ngroup = zeros(Int, k)

    for i in 1:k
        if i == 1
            # First group
            if upper == :open
                subset = (x .>= lbound[i]) .& (x .< ubound[i])
            elseif upper == :closed
                subset = (x .>= lbound[i]) .& (x .<= ubound[i])
            end
        elseif i == k
            # Last group
            if lower == :open
                subset = (x .> lbound[i]) .& (x .<= ubound[i])
            else
                subset = (x .>= lbound[i]) .& (x .<= ubound[i])
            end
        else i < k
            # Intermediate groups
            if lower == :open && upper == :closed
                subset = (x .> lbound[i]) .& (x .<= ubound[i])
            elseif lower == :closed && upper == :open
                subset = (x .>= lbound[i]) .& (x .< ubound[i])
            end
        end

        group[subset] .= i
        ngroup[i] = sum(subset)
    end

    return GraduatedMapClassification(mcr, k, group, ngroup, lbound, ubound, lower, upper)
end

function maplabels(mc::GraduatedMapClassification; digits::Int = 2, sep::String = ", ", counts::Bool = true)::Vector{String}
    k = mc.k
    ngroup = mc.ngroup
    lbound = mc.lbound
    ubound = mc.ubound
    lower = mc.lower
    upper = mc.upper

    labels = Vector{String}(undef, k)

    # Label for each group in the classification
    for i in 1:k
        labeli = ""

        # Lower interval Symbol
        if i == 1
            labeli = labeli * "["
        else
            if lower == :open
                labeli = labeli * "("
            elseif lower == :closed
                labeli = labeli * "["
            end
        end

        # Range
        labeli = labeli * string(round(lbound[i], digits = digits)) *
            sep *
            string(round(ubound[i], digits = digits))

        # Upper interval symbol
        if i == k
            labeli = labeli * "]"
        else
            if upper == :open
                labeli = labeli * ")"
            elseif upper == :closed
                labeli = labeli * "]"
            end
        end

        # Counts
        if counts
            labeli = labeli * " (" * string(ngroup[i]) * ")"
        end

        labels[i] = labeli
    end

    labels
end

bounds(mc::GraduatedMapClassification)::Tuple{Vector{Float64}, Vector{Float64}} = return mc.lbound, mc.ubound;

levels(mc::GraduatedMapClassification)::Vector{String} = return string.(1:mc.k);


using ITensors
using ITensorsGateEvolution

function main()

N = 10

osRand = ProductOps()
for n in 1:N
  osRand *= ("rand", n)
end

osX = ProductOps()
for n in 1:N
  osX *= ("X", n)
end

osZ = ProductOps()
for n in 1:N
  osZ *= ("Z", n)
end

osSw = ProductOps()
for n in 1:N-1
  osSw *= ("Sw", n, n+1)
end

osCx = ProductOps()
for n in 1:N-1
  osCx *= ("Cx", n, n+1)
end

osRand = ProductOps()
for n in 1:N-1
  osRand *= ("rand", n, n+1)
end

osT = ProductOps()
for n in 1:N-4
  osT *= ("T", n, n+2, n+4)
end

os = osRand * osX * osSw * osZ * osCx * osT

@show os

s = siteinds("qubit", N)
gates = ops(s, os)

ψ0 = productMPS(s, "0")

# Apply the gates
ψ = apply(gates, ψ0; cutoff = 1e-15, maxdim = 100)
@show dim(s[1])^(N ÷ 2)
@show maxlinkdim(ψ)

prodψ = apply(gates, prod(ψ0))
@show prod(ψ) ≈ prodψ
@show norm(prod(ψ) - prodψ) / norm(prodψ)

return ψ

end

ψ = main();


2+1.2*3-4^3/5             # -7.200000000000001
3*π                       # 3π，Julia 支持 Unicode
log(MathConstants.e)      # 1
2im^2                     # -4+0im，对复数的支持，im 是虚数单位


"""
      Rocket3D
"""

struct Rocket3D{I, T} <: Model{I, T}
      n::Int
      m::Int
      d::Int

      mass          # mass
      inertia       # inertia matrix
      inertia_inv   # inertia matrix inverse
      gravity       # gravity
      length        # length (com to thruster)
end

function f(model::Rocket3D, z, u, w)
      # states
      x = view(z,1:3)
      r = view(z,4:6)
      v = view(z,7:9)
      ω = view(z,10:12)

      # force in body frame
      F = view(u, 1:3)

      # torque in body frame
      τ = @SVector [model.length * u[2],
                    -model.length * u[1],
                    0.0]

      SVector{12}([v;
                   0.25 * ((1.0 - r' * r) * ω - 2.0 * cross(ω, r) + 2.0*(ω' * r) * r);
                   model.gravity + (1.0 / model.mass) * MRP(r[1], r[2], r[3]) * F;
                   model.inertia_inv * (τ - cross(ω, model.inertia * ω))])
end

n, m, d = 12, 3, 0

mass = 1.0
len = 1.0
inertia = Diagonal(@SVector[1.0 / 12.0 * mass * len^2.0, 1.0 / 12.0 * mass * len^2.0, 1.0e-5])
inertia_inv = Diagonal(@SVector[1.0 / (1.0 / 12.0 * mass * len^2.0), 1.0 / (1.0 / 12.0 * mass * len^2.0), 1.0 / (1.0e-5)])

model = Rocket3D{Midpoint, FixedTime}(n, m, d,
                  mass,
                  inertia,
                  inertia_inv,
                  @SVector[0.0, 0.0, -9.81],
                  len)

function visualize!(vis, p::Rocket3D, q; Δt = 0.1, mesh = true, T_off = length(q))
	default_background!(vis)

	if mesh
		obj_rocket = joinpath(pwd(), "models/starship/Starship.obj")
		mtl_rocket = joinpath(pwd(), "models/starship/Starship.mtl")
		ctm = ModifiedMeshFileObject(obj_rocket, mtl_rocket, scale=1.0)
		setobject!(vis["rocket"]["starship"], ctm)

		settransform!(vis["rocket"]["starship"],
			compose(Translation(0.0, 0.0, -p.length),
				LinearMap(0.25 * RotY(0.0) * RotZ(0.5 * π) * RotX(0.5 * π))))

        body = Cylinder(Point3f0(0.0, 0.0, -1.25),
          Point3f0(0.0, 0.0, 0.5),
          convert(Float32, 0.125))

        setobject!(vis["rocket"]["body"], body,
          MeshPhongMaterial(color = RGBA(1.0, 0.0, 0.0, 1.0)))
	else
		body = Cylinder(Point3f0(0.0, 0.0, -1.0 * model.length),
			Point3f0(0.0, 0.0, 1.0 * model.length),
			convert(Float32, 0.15))

		setobject!(vis["rocket"], body,
			MeshPhongMaterial(color = RGBA(0.0, 0.0, 0.0, 1.0)))
			anim = MeshCat.Animation(convert(Int, floor(1.0 / Δt)))
	end

	anim = MeshCat.Animation(convert(Int, floor(1.0 / Δt)))

	for t = 1:length(q)
	    MeshCat.atframe(anim, t) do
            if t >= T_off
				setvisible!(vis["rocket"]["body"], false)
			else
				setvisible!(vis["rocket"]["body"], true)
			end
	        settransform!(vis["rocket"],
	              compose(Translation(q[t][1:3]),
	                    LinearMap(MRP(q[t][4:6]...) * RotX(0.0))))
	    end
	end

	# settransform!(vis["/Cameras/default"], compose(Translation(0.0, 0.0, 0.0),
	# LinearMap(RotZ(pi/2))))
	MeshCat.setanimation!(vis, anim)
end

# include(joinpath(pwd(), "models/visualize.jl"))
#
# vis = Visualizer()
# render(vis)
# q0 = zeros(model.n)
#
# # simulation test
# u = [0.0; 0.0; 1.0]
# T = 10
# h = 0.1
# x_hist = [q0]
#
# for t = 1:T
# 	push!(x_hist, fd(model, x_hist[end], u, zeros(model.d), h, t))
# end
#
# visualize!(vis, model, x_hist, Δt = 0.1)