type Kahan{T <: AbstractFloat}
	sum::T
	c::T
end

Kahan{T <: AbstractFloat}(::Type{T}) = Kahan{T}(convert(T, 0), convert(T, 0))

#import NumericExtensions.add!
function add!{T <: AbstractFloat}(k::Kahan{T}, x::T)
	y = x - k.c
	t = k.sum + y
	k.c = (t - k.sum) - y
	k.sum = t
end

import Base.sum
sum(k::Kahan) = k.sum

type MeanCounter{T <: AbstractFloat}
	n::Int64
	mean::Kahan{T}
end

MeanCounter{T <: AbstractFloat}(::Type{T}) = MeanCounter{T}(0, Kahan(T))

function add!{T <: AbstractFloat}(m::MeanCounter{T}, x::T)
	m.n += 1
	add!(m.mean, (x - sum(m.mean)) / m.n)
	return m.mean
end

import Base.mean

mean(m::MeanCounter) = sum(m.mean)

export Kahan, MeanCounter, add!, sum, mean


# Various mathematical utils for cryptography processes.
# Copyright (C): Jaka Smrekar (vinctux) <vinctux@outlook.com>, 2016

using Primes
import Primes: isprime

# Get integer bitsize
function bitsize{T<:Integer}(N::T)
    b = BigInt(0)
    while N >> 1 > 0 b += 1 end
    return b
end

# Get reversed bit-order of x
# See: https://graphics.stanford.edu/~seander/bithacks.html
function rev{T<:Integer}(x::T)
    x = (((x & 0xAAAAAAAA) >> 1) | ((x & 0x55555555) << 1))
    x = (((x & 0xCCCCCCCC) >> 2) | ((x & 0x33333333) << 2))
    x = (((x & 0xF0F0F0F0) >> 4) | ((x & 0x0F0F0F0F) << 4))
    x = (((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8))
    return ((x >> 16) | (x << 16))
end

# Get reversed bit-order for every N in X
function rev{T<:Integer}(X::Array{T, 1})
    A = Array{T, 1}()
    for N in X append!(A, rev(N)) end
    return A
end

# Cyclic bit-shift (rotation) to left = ROL
function rotl{T<:Integer}(u::T, c::Int)
    m = 8 * sizeof(typeof(u)) - 1
    c &= m
    return (u << c) | (u >> (m & (-c)))
end

# Cyclic bit-shift (rotation) to right = ROR
function rotr{T<:Integer}(u::T, c::Int)
    m = 8 * sizeof(typeof(u)) - 1
    c &= m
    return (u >> c) | (u << (m & (-c)))
end

# Flatten an nested array
# See http://codegolf.stackexchange.com/a/80140
flat(x, y = vcat(x...)) = x == y ? x : flat(y)

# Get first power of E, greater than N
function gpow{T<:Integer}(N::T, E::Integer = 2)
    if N <= 0 return 0 end
    x, I = N, 0
    while x <= N x *= E; I += 1 end
    return (x, I)
end

# Produce cryptographically-secure octet array of length b
function csrand{T<:Integer}(b::T)
    R, O = RandomDevice(), Array{UInt8, 1}()
    for i in 1:b append!(O, rand(R, 0o0:0o255)) end
    return O
end
csrand{T<:Integer}(t::UnitRange{T}) = rand(RandomDevice(), t)

function csrandbit{T<:Integer}(b::T)
    O = "0b"
    for i in 1:b O *= csrand(0:1) == 0 ? "0" : "1" end
    return parse(BigInt, O)
end
function randbit{T<:Integer}(b::T)
    O = "0b"
    for i in 1:b O *= rand(0:1) == 0 ? "0" : "1" end
    return parse(BigInt, O)
end

# Sieve of Atkin - fast prime generator
# Author of this piece of code: @VicDrastik
# For reference:
#   - https://en.wikipedia.org/wiki/Sieve_of_Atkin
#   - https://github.com/JuliaLang/julia/issues/11594#issuecomment-109753437
function e5(s::BitArray{1})
    n = length(s)
    s[:] = false
    s[2] = true
    s[3] = true
    s[5:6:n] = true
    s[7:6:n] = true
    for p = 5:2:isqrt(n) if s[p] s[p*p:2p:n] = false end end
end

# Generates a strong prime, according to 10.1.1.17.2713
function csprime{T<:Integer}(b::T, ERR::Int8 = Int8(75))
    sieve_base = falses(10^8)
    e5(sieve_base)
    X = csrand((div(1414213562373095 * 2^BigInt(b - 1), 10^15)):2^BigInt(b) - 1)
    y1, y2 = csrandbit(101), csrandbit(101)
    P = Dict(y1 => BigInt(0), y2 => BigInt(0))
    F = falses(5 * length(sieve_base))
    _S = [BigInt(i) for i in 1:length(sieve_base) if sieve_base[i]]
    for y in [y1, y2]
        # FIXME: Optimize with native zip - don't compute twice
        S = [(y % i, i) for i in 1:length(sieve_base) if sieve_base[i]]
        for i in 1:length(S) for j in (y + S[i][1] - S[i][2]):S[i][2]:length(F) F[j] = true end end
        for i in 1:length(F)
            if F[i] continue end
            t = y + i
            if isprime(t, ERR) P[y] = t; break end
        end
    end
    R = invmod(P[y2], P[y1]) * P[y2] - invmod(P[y1], P[y2]) * P[y1]
    Q = P[y1] * P[y2]
    Y0 = X + (R - (X % Q))
    while true
        pp = true
        for p in _S if Y0 % p == 0 pp = false; break end end
        if pp && isprime(Y0, ERR) break end
        Y0 += Q
    end
    return Y0
end
csprime(b::Integer) = csprime(b, Int8(75))

# Euler's totient function
# C++ reference:      https://gist.github.com/cslarsen/1635288
# Pythonic reference: https://stackoverflow.com/a/18114286/6489126
function phi{T<:Integer}(n::T, ERR::Int8 = Int8(75))
    if n < 0 n = -n end
    if n == 0 return 0
    elseif 0 < n <= 2 return 1 end
    if isprime(BigInt(n), ERR) return n - 1 end
    if n & 1 == 0 m = n >> 1; return ~(m & 1) == 0 ? phi(m) << 1 : phi(m) end
    O = 0
    for i in 1:n if gcd(n, i) == 1 O += 1 end end
    return O
end

# GDB function
# Pythonic reference: https://github.com/amintos/PyECC/blob/master/ecc/elliptic.py#L237-L244
function gdb{T<:Integer}(n::T)
    i = 1
    if n <= 0 return 0 end
    while n % i > 0 i <<= 1 end
    return i >> 2
end

# Transform n into a binary representation having signed bits.
# Pythonic reference: https://github.com/amintos/PyECC/blob/master/ecc/elliptic.py#L254-L271
function signbin{T<:Integer}(n::T)
    r = Array{Int8, 1}()
    while n > 1
        if n & 1 == 0w
            if gbd(n + 1) > gbd(n - 1) append!(r, -1); n += 1
            else append!(r, 1); n -= 1 end
        else append!(r, 0) end
        n >>= 1
    end
    r.append(n)
    return reverse(r)
end

# Find first prime greater or equal to N
function gprime(N::Integer)
    if iseven(N) N += 1 end
    while !isprime(N, 75) N += 2 end
    return N
end

# Find first prime greater or equal to N, while also N % M == R
function gprimemod(N::Integer, M::Integer, R::Integer = 1)
    while N % M != R && !isprime(N, 75) N = gprime(N + 2) end
    return N
end


# ##
# Surface Quasi-Geostrophy:
# This code solves an exmaple problem from Held et al JFM 1995
# Imposed condition is the 'surface buoyancy' given by a smooth elliptic vortex.
# This equations are Eq.(2) of the paper.

using FourierFlows, Plots, Statistics, Printf, Random

using FFTW: irfft, rfft
using Statistics: mean
using Random: seed!

using LinearAlgebra: mul!, ldiv!
using FourierFlows: parsevalsum

using CUDA
#    Reexport

#nothingfunction(args...) = nothing

# parameters
#
# Let's define a struct which contain the
# main parameters of the problem
# struct Params{T} <: AbstractParams
#      ν :: T          # hyperviscosity coefficient
#     nν :: Int       # Order of the hyperviscosity
# end
# nothing #hide

struct Params{T} <: AbstractParams
    ν :: T         # Hyperviscosity coefficient
   nν :: Int       # Order of the hyperviscous operator
end
nothing #hide

# variables
# conatins all the problem variables in physical
# and Fourier (transformed) space :  Aphys, Atrans
struct Vars{Aphys, Atrans} <: AbstractVars
    b  :: Aphys
    u  :: Aphys
    v  :: Aphys
    bh :: Atrans
    uh :: Atrans
    vh :: Atrans
end
nothing #hide

# constructor creates the size of the variables
# based on the 'grids'
function Vars(::Dev, grid) where Dev
    T = eltype(grid)
    @devzeros Dev T (grid.nx, grid.ny) b u v
    @devzeros Dev Complex{T} (grid.nkr, grid.nl) bh uh vh
    return Vars(b, u, v, bh, uh, vh)
end
nothing #hide

# constructing the equation:
# here the linear part is described ny hyperviscosity part,
# and the nonlinear part is computed by the 'calcN!'
# the equations are in Fourier space
function Equation(paramas::Params, grid::AbstractGrid)
    #T = eltype(grid)
    L = @. -paramas.ν*grid.Krsq^paramas.nν
    CUDA.@allowscalar L[1,1] = 0
    return FourierFlows.Equation(L, calcN!, grid)
end
nothing #hide

# constructing the nonlinear part of the problem
# ofcourse in Fourier space.
function calcN!( N, sol, t, clock, vars, params, grid )
    @. vars.bh = sol
    @. vars.uh =   im * grid.l  * sqrt( grid.invKrsq ) * sol
    @. vars.vh = - im * grid.kr * sqrt( grid.invKrsq ) * sol

    # doing inverse-fft (I suppose!!)
    ldiv!( vars.u, grid.rfftplan, vars.uh )
    ldiv!( vars.v, grid.rfftplan, vars.vh )
    ldiv!( vars.b, grid.rfftplan, vars.bh )

    ub, ubh = vars.u, vars.uh
    vb, vbh = vars.v, vars.vh

    @. ub *= vars.b
    @. vb *= vars.b

    # compute the advection term: u*bx + v*by
    mul!( ubh, grid.rfftplan, ub )
    mul!( vbh, grid.rfftplan, vb )

    @. N = - im * grid.kr * ubh - im * grid.l * vbh
    return nothing
end
nothing #hide

# All are ready to go, my master!
# Hold on to the moment my apprentice, let me create some helpers to do thing smoothly.

# function which updates the variables in current time!
function updatevars!(prob)
    vars, grid, sol = prob.vars, prob.grid, prob.sol

    @. vars.bh = sol
    @. vars.uh =   im * grid.l  * sqrt( grid.invKrsq ) * sol
    @. vars.vh = - im * grid.kr * sqrt( grid.invKrsq ) * sol

    # doing inverse-fft
    ldiv!( vars.u, grid.rfftplan, deepcopy(vars.uh) )
    ldiv!( vars.v, grid.rfftplan, deepcopy(vars.vh) )
    ldiv!( vars.b, grid.rfftplan, deepcopy(vars.bh) )

    return nothing
end

# set buoyancy as the solution and upadate all other variables.
function set_b!(prob, b)
    mul!( prob.sol, prob.grid.rfftplan, b )
    CUDA.@allowscalar prob.sol[1,1] = 0  # domain average gives zero

    updatevars!(prob)

    return nothing
end

# calculating surface-averaged kinetic energy.
# In SQG formulation, this is exactly half of the buoyancy variance.
@inline function kinetic_energy(prob)

    vars, grid, sol = prob.vars, prob.grid, prob.sol

    @. vars.uh =   im * grid.l  * sqrt( grid.invKrsq ) * sol
    @. vars.vh = - im * grid.kr * sqrt( grid.invKrsq ) * sol

    ke_h = vars.uh

    @. ke_h = 0.5*( abs2(vars.uh) + abs2(vars.vh) )

    return 1/(grid.Lx * grid.Ly) * parsevalsum( ke_h, grid )
end

# ## Choosing a device: CPU or GPU
dev = CPU()    # Device (CPU/GPU)
nothing # hide

# numerical parameters
     nx = 512                           # grid resolution
     ny = 512
stepper = "FilteredRK4"                 # time-stepping scheme
     dt = 0.01                          # timesep (in seconds)
     Tf = 80.                           # total time of the simulation
 nsteps = Int( Tf/dt )                  # total iteration of the simulation
  nplot = 10 #round( Int, nsteps/100 )      # step to save data
nothing # hide

# physical parameters
     Lx = 2π                # domain size
     Ly = 2π
     nν = 4
      ν = 1.e-18
nothing # hide

# creating the grids struct
grid = TwoDGrid( dev, nx, Lx, ny, Ly; T=Float64)

# creating parameters struct
params = Params(ν, nν)

vars = Vars(dev, grid)
equation = Equation(params, grid)
prob = FourierFlows.Problem(equation, stepper, dt, grid, vars, params, dev)

# Let's create some shortcuts.
sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid
x, y = prob.grid.x, prob.grid.y
nothing # hide

# Here comes the initial ccondition, and set to fly.
X, Y = gridpoints(grid)
b0 = @. exp( -(X^2 + 4*Y^2) )

set_b!(prob, b0)
nothing #hide

#### plot the initial condition
heatmap(x, y, prob.vars.b',
    aspectratio = 1,
             c  = :deep,
           clim = [0, 1],
           xlim = ( -grid.Lx/2, grid.Lx/2 ),
           ylim = ( -grid.Ly/2, grid.Ly/2 ),
         xticks = -3:3,
         yticks = -3:3,
         xlabel = "x",
         ylabel = "y",
          title = "Initial distributon of buoyancy",
     framestyle = :box)

# creaing diganosis
KE = Diagnostic(kinetic_energy, prob; nsteps=nsteps)
diags = KE
nothing #

### ouput
base_filename = string( "surfaceqg_Held_", nx )
datapath = "./"
plotpath = "./"

dataname = joinpath(datapath, base_filename)
plotname = joinpath(datapath, base_filename)
nothing #hide

if !isdir(plotpath); mkdir(plotpath); end
if !isdir(datapath); mkdir(datapath); end
nothing #hide

# get the output
get_sol(prob) = sol  # extract the fft-solution
get_u(prob) = irfft( im * grid.l  .* sqrt( grid.invKrsq ) .* sol, grid.nx )
out = Output(prob, dataname, (:sol, get_sol), (:u, get_u))
nothing # hide

### Visulation
function plot_output(prob)
    br = prob.vars.b
    ur = prob.vars.u
    vr = prob.vars.v

    pb = heatmap(x, y, br',
         aspectratio = 1,
                   c = :Reds,
                clim = (0, 1),
               xlims = (-grid.Lx/2, grid.Lx/2),
               ylims = (-grid.Ly/2, grid.Ly/2),
              xticks = -3:3,
              yticks = -3:3,
              xlabel = "x",
              ylabel = "y",
               title = "buoyancy",
          framestyle = :box)

    # pKE = plot(1,
    #            label = "kinetic energy",
    #        linewidth = 2,
    #           legend = :bottomright,
    #            alpha = 0.7,
    #            xlims = (0, Tf),
    #            ylims = (0, 1e-2),
    #           xlabel = "t")

    # pb² = plot(1,
    #            label = "buoyancy variance ∫bₛ²dxdy/L²",
    #        linecolor = :red,
    #           legend = :bottomright,
    #        linewidth = 2,
    #            alpha = 0.7,
    #            xlims = (0, tf),
    #            ylims = (0, 2e-2),
    #           xlabel = "t")

    # layout = @layout [a{0.5w} Plots.grid(2, 1)]
    # layout = @layout grid(2, 1)
    p = plot(pb, size = (500, 500), dpi=350)

    return p
end
nothing # hide

# Time-stepping forward in time

startwalltime = time()

p = plot_output(prob)

anim = @animate for j=0:Int(nsteps/nplot)

    # CFL condition
    cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy])

    #if j%(500/nplot)==0
    if j%nplot==0
        log1 = @sprintf("step: %04d, t: %.1f, cfl: %.3f, walltime: %.2f min",
            clock.step, clock.t, cfl, (time()-startwalltime)/60)

        log2 = @sprintf("kinetic energy: %.2e", KE.data[KE.i])

        println(log1)
        println(log2)
    end

    p[1][1][:z] = vars.b
    p[1][:title] = "buoyancy, t="*@sprintf("%.2f", clock.t)
    #push!(p[2][1], KE.t[KE.i], KE.data[KE.i])
    #push!(p[3][1], B.t[B.i], B.data[B.i])

    stepforward!(prob, diags, nplot)
    updatevars!(prob)

end

mp4(anim, "SQG_Held1995.mp4", fps=8)


using Plots, DifferentialEquations
gr()

function f′(du, u, p, t)
    du[1] = p.input(t) - p.α*u[1]
    du[2] = -p.β * u[2]
    du[3] = p.β*(p.input(t) - u[3])
end

spike_condition(u,t,integrator) = u[1]-integrator.p.δ

function spike!(integrator)
    integrator.u[1] = 0.0
    integrator.u[2] += 1.0
    push!(integrator.p.spiketimes, integrator.t)
end

u₀ = [0.0,1.0,1.0]
tspan = (0.0,10.0)
p = (
    input=t-> 0.5*(sin(t)+1) + 0.3*(cos(0.7+1.5*t)+1),
    α = 0.5,
    β = 0.5,
    δ = 0.2,
    spiketimes = Float64[0.0]
)

cb = ContinuousCallback(spike_condition, spike!, nothing)
sol = solve(ODEProblem(f′, u₀, tspan, p); callback=cb)

function make_analytical_solution(spike_times; α=1.0, δ=0.2, basis=:sample_and_hold)
    return function analytical_reconstruction(t)
        i = searchsortedfirst(spike_times,t)
        return if i > length(spike_times)
            0.0
        else
            t₁ = spike_times[i]
            t₀ = i<=1 ? -Inf : spike_times[i-1]
            dt = t₁-t₀
            norm = √((1.0-exp(-2.0α*dt))/(2.0α))
            f = δ/norm # fourier coefficient

            if basis == :sample_and_hold
                1.0/√(dt) * f
            else#if basis == :truncated_exponential
                exp(-α*(t₁-t))/norm * f
            end
        end
    end
end

ŝ = make_analytical_solution(p.spiketimes; α=p.α, δ=p.δ, basis=:sample_and_hold)
mean_dt = (tspan[2]-tspan[1])/length(p.spiketimes)
mean_scale = 2*p.δ*p.β/(1+exp(-p.α*mean_dt))

begin
p1=vline(p.spiketimes, linewidth=2, color="#babdb6ff", label="spikes ", xlims=(0,10.5), ylims=(0,1.7))
plot!(p.input, tspan..., linewidth=2, color="#729fcfff", label="input ")
plot!(t->sol(t,idxs=3), tspan..., linewidth=2, color="#fcaf3eff", linestyle=:dash, label="filtered input  ")
plot!(ŝ, tspan..., linewidth=2, color="#729fcfff", linestyle=:dash, label="acausal rec.  ")
plot!(t->sol(t,idxs=2)*mean_scale+exp(-p.β*t)*(1-mean_scale), tspan..., linewidth=2, color="#fcaf3eff", label="causal LTI rec.  ")
plot!(xlabel="time", legend=false)

idx = searchsortedlast(p.spiketimes, 5.0)
(t₀,t₁,t₂) = p.spiketimes[idx:idx+2]
xlims = (t₀-0.1, t₂+0.1)

p2 = vline(p.spiketimes, linewidth=2, color="#babdb6ff", xlims=xlims, ylims=(0,1.0), label="spikes ", legend=false)
plot!(p.input, xlims..., linewidth=2, color="#729fcfff", label="input ")
plot!(t->sol(t,idxs=3), tspan..., linewidth=2, color="#fcaf3eff", linestyle=:dash, label="filtered input ")
plot!(t->sol(t,idxs=1), xlims..., linewidth=2, color="#ad7fa8ff", label="state ")
hline!([p.δ], color="#ef2929ff", linewidth=2, linestyle=:dash, label="threshold ")
plot!(t->sol(t,idxs=2)*mean_scale+exp(-p.β*t)*(1-mean_scale), xlims..., linewidth=2, color="#fcaf3eff", label="causal LTI rec.")
plot!(t->sol(t,idxs=2)*mean_scale+exp(-p.β*t)*(1-mean_scale)-exp(-p.β*(t-t₁))*(t>t₁)*mean_scale, t₁, xlims[end], linestyle=:dot, linewidth=2, color="#fcaf3eff", label="")

p3 = plot(showaxis=false, framestyle=:none, showgrid=false)
plot!([],[], linewidth=2, color="#babdb6ff", label="spikes ")
plot!([],[], linewidth=2, color="#729fcfff", label="input ")
plot!([],[], linewidth=2, color="#fcaf3eff", linestyle=:dash, label="filtered input ")
plot!([],[], linewidth=2, color="#ad7fa8ff", label="state ")
plot!([],[], linewidth=2, color="#ef2929ff", linestyle=:dash, label="threshold ")
plot!([],[], linewidth=2, color="#729fcfff", linestyle=:dash, label="acausal rec.  ")
plot!([],[], linewidth=2, color="#fcaf3eff", label="causal LTI rec. ")
plot!(legend=(0.0, 0.9))

l = @layout [   a{0.7w} b
c{0.5h}     ]

plot(p2,p3,p1, layout=l)
end

savefig("figures/reconstruction_lif.pdf")
savefig("figures/reconstruction_lif.svg")


@def Ax_max begin #(U)

	# maximum longitudinal acceleration for given speed
	Ax_max = Array(Float64,(length(U),1))
	for i in eachindex(U)
		Ax_max[i,1] = AXC[1]*U[i]^3 + AXC[2]*U[i]^2 + AXC[3]*U[i] + AXC[4]
	end
	Ax_max
end


#=Copyright (c) 2017 Gabriel Goh

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.=#

using ConicIP

set_default_solver(ConicIPSolver(optTol = 1e-10))

b = 0.5*ones(10)

function Ψ(λ)
  x = Variable(15)
  p = minimize( 0.5*sumsquares(A*x - b) + λ*norm(y,1) )
  solve!(p)
  s = y.value
  s[abs(s) .< 1e-8] = 0
  return s
end

x = 0.5*ones(10)
W = randn(10,15)
I = eye(10)
ϵ = 0.001

K = !(Ψ(x) .== 0)
ystar = Ψ(x)
inv(W[:,K]'W[:,K])*(W[:,K]'x - sign(ystar[K]))
Ψ(x)

(Ψ(x + ϵ*I[:,1]) - Ψ(x - ϵ*I[:,1]))/(2ϵ)

inv(W[:,K]'W[:,K])*(W[:,K]')


module SYM

using BenchmarkTools: push!
using BenchmarkTools

import Base:+,-,*,/,^,sin,cos,tan,exp # methods for ordinary numbers are needed, so we have to import explicitly (since we have changed their methods)
import Base:convert, show

export Sym, show
export @def, def_from_vec
export sub!, lambdify, lambdify_fast

#####################################################################
########## Types, Promotion Rules, and Convert Start Here ###########
#####################################################################
"""
An instance of Symbolic variable can be realized by
	`Sym{T}(expr)`, where T is the SubType of the Real domain.
Here I confine the variable in real just for convenience of the promotion rule with complex numbers since Complex{T}(x) is defined only for x of the
	subtype of Real, while Complex{Sym} is illegal.
"""
abstract type Symbolic{T} end # <: Number

struct Sym{T<:Number} <: Symbolic{T}
	expr # of Type{Symbol} or Type{Expr}
end

"Claim realization of the instance for other types"
Sym{T}(x::String) where {T<:Number} = Sym{T}(Meta.parse(x))
Sym{T}(x::Sym) where {T<:Number} = Sym{T}(x.expr) # change the symtype, or keep unchanged for repeated invokation
Sym(x) = Sym{Real}(x) # add a method for default type of Real
Sym(list::Vector) = [Sym{Real}(i) for i in list] # add a method for generating a list of symbols
#= for example,
	x = Sym{Real}(:x) # creat one instance of Real Symbolic variable
	y = Sym{Real}("y")
	z = Sym(x)
	x = Sym{Real}(gensym())	# creat one instance for an anonymous variable
=#

"Define a new type-check function by adding methods"
function symtype end
symtype(::Sym{T}) where {T} = T
symtype(x) = typeof(x) # for other known Types of x like Number, Symbol, and Expr etc.

"""
Define promote rules for Type{Sym} and Type{Number}.
This is necessary since basic binary operations always act in the way of, for example, +(x::Number, y::Number) = +(promote(x,y)...)
See https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Promotion
"""
Base.promote_rule(x::Type{Sym{T}}, y::Type{S}) where {T, S<:Real} = Sym{Union{T, S}}
# for example, promote_rule(Sym{Float64}, Int64) = Sym{Float64}

"""
Add methods for basic binary operations +,-,*,/,^
Here we distinguish real and complex numbers
"""
+(x::Sym{T}, y::Number) where {T} = Sym{T}(eval(:(:($($x.expr)+$$y))))
+(y::Number, x::Sym{T}) where {T} = Sym{T}(eval(:(:($($x.expr)+$$y))))
+(x::Sym{T}, y::Sym{S}) where {T,S} = Sym{Union{T,S}}(eval(:(:($($x.expr)+$($y.expr)))))
-(x::Sym{T}, y::Number) where {T} = Sym{T}(eval(:(:($($x.expr)-$$y))))
-(y::Number, x::Sym{T}) where {T} = Sym{T}(eval(:(:($($x.expr)-$$y))))
-(x::Sym{T}, y::Sym{S}) where {T,S} = Sym{Union{T,S}}(eval(:(:($($x.expr)-$($y.expr)))))
*(x::Sym{T}, y::Number) where {T} = Sym{T}(eval(:(:($($x.expr)*$$y))))
*(y::Number, x::Sym{T}) where {T} = Sym{T}(eval(:(:($($x.expr)*$$y))))
*(x::Sym{T}, y::Sym{S}) where {T,S} = Sym{Union{T,S}}(eval(:(:($($x.expr)*$($y.expr)))))
/(x::Sym{T}, y::Number) where {T} = Sym{T}(eval(:(:($($x.expr)/$$y))))
/(y::Number, x::Sym{T}) where {T} = Sym{T}(eval(:(:($$y/$($x.expr)))))
/(x::Sym{T}, y::Sym{S}) where {T,S} = Sym{Union{T,S}}(eval(:(:($($x.expr)/$($y.expr)))))
^(x::Sym{T}, y::Number) where {T} = Sym{T}(eval(:(:($($x.expr)^$$y))))
^(y::Number, x::Sym{T}) where {T} = Sym{T}(eval(:(:($$y^$($x.expr)))))
^(x::Sym{T}, y::Sym{S}) where {T,S} = Sym{Union{T,S}}(eval(:(:($($x.expr)^$($y.expr)))))

# define the zero unit for abelian groups
Base.one(s::Sym) = one(symtype(s))
Base.zero(s::Sym) = zero(symtype(s))

"""
Add methods for basic single-variable mathematical functions exp, sin, cos, tan (to be continue...)
"""
exp(x::Sym{T}) where {T} = Sym{T}(eval(:(:(exp($($x.expr))))))
exp(x::Array{Sym{T}}) where {T} = Sym{T}(:(exp($x)))
sin(x::Sym{T}) where {T} = Sym{T}(eval(:(:(sin($($x.expr))))))
sin(x::Array{Sym{T}}) where {T} = Sym{T}(:(sin($x)))
cos(x::Sym{T}) where {T} = Sym{T}(eval(:(:(cos($($x.expr))))))
cos(x::Array{Sym{T}}) where {T} = Sym{T}(:(cos($x)))
tan(x::Sym{T}) where {T} = Sym{T}(eval(:(:(tan($($x.expr))))))
tan(x::Array{Sym{T}}) where {T} = Sym{T}(:(tan($x)))

"""
Convert methods
	motivated by https://github.com/symengine/SymEngine.jl/blob/master/src/subs.jl
"""
function convert(::Type{Expr}, x::Sym{T}) where {T}
	return x.expr
end

function convert(::Type{Expr}, v::Vector{Sym{T}}) where {T}# slow for large array
    Expr(:vcat, map(x->x.expr, v)...) # map .expr to the vector v. Such Vectorization treatment is much faster.
end

function convert(::Type{Expr}, m::Matrix{Sym{T}}) where {T} # slow for large array
    col_args = []
    for i = 1:size(m, 1)
        row_args = []
        for j = 1:size(m, 2)
            push!(row_args, m[i,j].expr)
        end
        row = Expr(:hcat, row_args...)
        push!(col_args, row)
    end
    Expr(:vcat, col_args...)
end

"Change the show methods for Type{Sym} (default looks like Sym{T}(:x); here we now display its Expr representation"
function Base.show(io::IO, x::Sym)
	print(io, "$(x.expr)")
end

#####################################################################
######################### Macros Start Here #########################
#####################################################################
"""
Claim (real) symbolic variables (with the Type{Sym{Real}}) by its Symbol representations
"""
macro def(x...) # accept multiple arguments
	q = Expr(:block)
	for s in x
		if s isa String
			s = Meta.parse(s) # convert to Symbol or Expr
			push!(q.args, Expr(:(=), esc(s), Expr(:call, :Sym, Expr(:quote, s)))) # Expr for claiming symbolic variables
		elseif s isa Symbol
			push!(q.args, Expr(:(=), esc(s), Expr(:call, :Sym, Expr(:quote, s)))) # Expr for claiming symbolic variables
		else
			error("Symbolic variables cannot be constructed: Not implemented!")
		end
	end
	push!(q.args, Expr(:vect, map(esc, x)...)) # the last argument of q is a vector of all variables.
	return q # automatically evaluate the Exprs so that all variables are claimed globally. And the last argument be evaluated and displayed.
end
#=
"Claim (real) symbolic variables (with the Type{Sym{Real}}) by its Symbol representations"
function def_from_vec(x::Vector) # accept multiple arguments
	q = []
	for s in x
		if isa(s, Symbol)
			eval(Expr(:(=), s, Expr(:call, :Sym, Expr(:quote, s)))) # claim symbolic variables
			push!(q, s)
		elseif isa(s, String)
			s = Meta.parse(s)
			eval(Expr(:(=), s, Expr(:call, :Sym, Expr(:quote, s)))) # claim symbolic variables
			push!(q, s)
		end
	end
	return q
end=#

#####################################################################
########### Substitution and Lambdification Start Here ##############
#####################################################################
const binary_op_list = [:+, :-, :*, :/, :^]

function AST_iterate_search_replacement(ex, rules::Tuple) # Slow serial tree search, wait to be optimized
	if ex isa Expr
		if ex.head == :call
			for (i,s) in enumerate(ex.args)
				if s isa Symbol && !(s in binary_op_list)
					for r in rules
						if s == r.first.expr
							if r.second isa Sym
								ex.args[i] = r.second.expr
							elseif r.second isa Union{Number, Symbol}
								ex.args[i] = r.second
							else
								error("Illegal replacement for the second arguments!")
							end
						end
					end
				elseif s isa Expr
					AST_iterate_search_replacement(s, rules)
				end
			end
		else
			error("Illegal expression: not implemented!")
		end
	elseif ex isa Symbol
		for r in rules
			if ex == r.first.expr
				if r.second isa Sym
					ex = r.second.expr
				elseif r.second isa Union{Number, Symbol}
					ex = r.second
				else
					error("Illegal replacement for the second arguments!")
				end
			end
		end
	end
	return ex
end

function sub!(sym::Sym, rules::Tuple) # motivated by the discussion of https://discourse.julialang.org/t/substitute-symbols-in-expression/23099
	ex = sym.expr
	ex = AST_iterate_search_replacement(ex, rules)
	return ex # fortunately, `eval(x::Sym)=x`.
end

function sub!(ex, rules::Tuple) # ex isa Symbol or Expr is OK
	return AST_iterate_search_replacement(ex, rules)
end

function sub!(m::Array, rules::Tuple) # add the method for substituting Array{Sym} of any dimension
	return sub!.(m, Ref(rules))
end

"""
Lambdify a muti-variable function with arbitrary vector of Symbols
	Symbolic lambdification (with variables not given enough, or extra variables given) is also supported!
"""
function lambdify(x::Sym, vars::Vector{Symbol})
	return eval(Expr(:function, Expr(:call, gensym(), vars...), x.expr))
	# only Symbol can be legal function arguments, so we have to `convert(Expr, x)`, which is simply done by `x.expr`.
end

function lambdify(m::Array, vars::Vector{Symbol}) # add a method for lambdifying Vector{Sym} or Matrix{Sym}
	return eval(Expr(:function, Expr(:call, gensym(), vars...), convert(Expr, m))) # such convert is slow for large arrays. Wait to be optimized.
end

"""
Fast lambdification (with less memory cost) by direct substitution
	x accept both Type{Sym} and Type{Array{Sym}} of any dimensions.
"""
function lambdify_fast(x, vars::Vector{Sym{T}}) where {T}
	function func(args::Number...)
		eval.(sub!(x, Tuple(Pair.(vars, args))))
		# Vectorized evaluation
	end
	return func
end

#####################################################################
########################## Tests Start Here #########################
#####################################################################

end


#=

   From BLOG example/simple-aircraft.blog

   Summary Statistics
   parameters      mean       std   naive_se      mcse        ess       rhat   ess_per_sec
       Symbol   Float64   Float64    Float64   Float64   Float64?   Float64?      Float64?

  numAircraft    1.4000    0.5831     0.0058    0.0519    57.7156     1.0166       18.9791
   numBlip[1]    2.4750    0.8212     0.0082    0.0722    61.7070     1.0233       20.2917
     numBlip1    2.4750    0.8212     0.0082    0.0722    61.7070     1.0233       20.2917
   numBlip[2]    1.3214    0.6577     0.0111    0.1033    missing    missing       missing
   numBlip[3]    1.2500    0.4334     0.0194    0.1936    missing    missing       missing

  Distributions of variable numAircraft (num:0)
  1.00000 =>    6500  (0.650000)
  2.00000 =>    3000  (0.300000)
  3.00000 =>     500  (0.050000)

  Distributions of variable numBlip1 (num:0)
  3.00000 =>    6750  (0.675000)
  1.00000 =>    1750  (0.175000)
  2.00000 =>    1375  (0.137500)
  0.00000 =>     125  (0.012500)

  Cf ~/webppl/simple_aircraft.wppl

=#
using Turing, StatsPlots, Distributions
include("jl_utils.jl")

@model function simple_aircraft(sumBlip=3)
    numAircraft ~ Poisson(5)

    # each Aircraft has a blip
    numBlip ~ filldist(Poisson(4), numAircraft)

    # We observe that there are in all 3 blips.
    sumBlip ~ Dirac(sum(numBlip))

    numBlip1 ~ Dirac(numBlip[1])

end

model = simple_aircraft()

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
# chns = sample(model, PG(5), 10)
chns = sample(model, SMC(), 10_000)
# chns = sample(model, IS(), 10_000)

display(chns)
# display(plot(chns))

show_var_dist_pct(chns, :numAircraft)
show_var_dist_pct(chns, :sumBlip)
show_var_dist_pct(chns, :numBlip1)


module FieldArraysOperationsTests

using Gridap.Arrays
using Gridap.Fields
using Gridap.TensorValues
using FillArrays

using Test
# using BenchmarkTools

np = 4
ncells = 10
d = 2
p = Point(1.0,2.0)
x = fill(p,np)
cell_to_x = Fill(x,ncells)

# ConstantField

cell_to_a = rand(ncells)
cell_to_f = lazy_map(ConstantField,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,map(i->fill(i,np),cell_to_a))

#print_op_tree(cell_to_fx)

#using BenchmarkTools
#c = array_cache(cell_to_fx)
#@btime getindex!($c,$cell_to_fx,1)

# linear_combination -> Field

avals = [1.0,0.5,1.2,3.9]
a = MockField.(avals)
b = VectorValue{2,Float64}[(1,1),(4,2),(3,5),(1,2)]
f = linear_combination(b,a)

cell_to_a = Fill(a,ncells)
cell_to_b = fill(b,ncells)
cell_to_r = fill(f(x),ncells)

cell_to_f = lazy_map(linear_combination,cell_to_b,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
@test isa(cell_to_fx.maps.value,Fields.LinearCombinationMap)
test_array(cell_to_fx,cell_to_r)

cell_to_∇r = fill(∇(f)(x),ncells)
cell_to_∇f = lazy_map(∇,cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
@test isa(cell_to_∇fx.maps.value,Fields.LinearCombinationMap)
test_array(cell_to_∇fx,cell_to_∇r)

#using BenchmarkTools
#c = array_cache(cell_to_fx)
#@btime getindex!($c,$cell_to_fx,1)

# linear_combination -> Field array

avals = [1.0,0.5,1.2,3.9]
a = MockField.(avals)
b = rand(length(avals),5)
f = linear_combination(b,a)

cell_to_a = Fill(a,ncells)
cell_to_b = fill(b,ncells)
cell_to_r = fill( evaluate(linear_combination(b,a),x),ncells)

cell_to_f = lazy_map(linear_combination,cell_to_b,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
@test isa(cell_to_fx.maps.value,Fields.LinearCombinationMap)
test_array(cell_to_fx,cell_to_r)

cell_to_∇r = fill(evaluate(Broadcasting(∇)(f),x),ncells)
cell_to_∇f = lazy_map(Broadcasting(∇),cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
@test isa(cell_to_∇fx.maps.value,Fields.LinearCombinationMap)
test_array(cell_to_∇fx,cell_to_∇r)

#using BenchmarkTools
#c = array_cache(cell_to_fx)
#@btime getindex!($c,$cell_to_fx,1)

# transpose of an array of fields

avals = [1.0,0.5,1.2,3.9]
a = MockField.(avals)
b = rand(length(avals),5)
c = linear_combination(b,a)

cell_to_a = Fill(a,ncells)
cell_to_b = fill(b,ncells)
cell_to_c = lazy_map(linear_combination,cell_to_b,cell_to_a)

cell_to_f = lazy_map(transpose,cell_to_c)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
@test isa(cell_to_fx.maps.value,Fields.TransposeMap)
cell_to_r = fill(evaluate(transpose(c),x),ncells)
test_array(cell_to_fx,cell_to_r)

cell_to_∇f = lazy_map(Broadcasting(∇),cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
@test isa(cell_to_fx.maps.value,Fields.TransposeMap)
cell_to_∇r = fill(evaluate(transpose(∇.(c)),x),ncells)
test_array(cell_to_∇fx,cell_to_∇r)

#using BenchmarkTools
#c = array_cache(cell_to_fx)
#@btime getindex!($c,$cell_to_fx,1)

# Composition between fields

mfun(g) = 3*g[1]
gfun(x) = 2*x
ffun(x) = mfun(gfun(x))

m = GenericField(mfun)
g = GenericField(gfun)
f = m∘g

cell_to_m = Fill(m,ncells)
cell_to_g = fill(g,ncells)
cell_to_f = lazy_map(∘,cell_to_m,cell_to_g)
cell_to_r = fill(f(x),ncells)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,cell_to_r)

cell_to_∇f = lazy_map(∇,cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
cell_to_∇r = fill(evaluate(∇(f),x),ncells)
test_array(cell_to_∇fx,cell_to_∇r)

#using BenchmarkTools
#c = array_cache(cell_to_fx)
#@btime getindex!($c,$cell_to_fx,1)

# Composition between field array and field

afun(x) = 2*x
bfun(x) = x[1] + 3
a = GenericField(afun)
b = fill(GenericField(bfun),4)
f = Broadcasting(∘)(b,a)

cell_to_a = fill(a,ncells)
cell_to_b = Fill(b,ncells)

cell_to_f = lazy_map(Broadcasting(∘),cell_to_b,cell_to_a)
cell_to_r = fill(evaluate(f,x),ncells)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,cell_to_r)

cell_to_∇f = lazy_map(Broadcasting(∇),cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
cell_to_∇r = fill(evaluate(∇.(f),x),ncells)
test_array(cell_to_∇fx,cell_to_∇r)

#using BenchmarkTools
#c = array_cache(cell_to_fx)
#@btime getindex!($c,$cell_to_fx,1)

# Operations

afun(x) = x[2]
bfun(x) = x[1] + 3
a = GenericField(afun)
b = GenericField(bfun)
f = Operation(+)(a,b)

cell_to_a = fill(a,ncells)
cell_to_b = Fill(b,ncells)
cell_to_r = fill(evaluate(f,x),ncells)
cell_to_∇r = fill(evaluate(∇(f),x),ncells)

cell_to_f = lazy_map(Operation(+),cell_to_b,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,cell_to_r)
@test cell_to_fx.maps.value == Broadcasting(+)

cell_to_∇f = lazy_map(∇,cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
test_array(cell_to_∇fx,cell_to_∇r)
@test cell_to_fx.maps.value == Broadcasting(+)

T = GenericField{Nothing}
cell_to_f = lazy_map(Operation(+),T,cell_to_b,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,cell_to_r)
@test cell_to_fx.maps.value == Broadcasting(+)

cell_to_∇f = lazy_map(∇,cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
test_array(cell_to_∇fx,cell_to_∇r)
@test cell_to_fx.maps.value == Broadcasting(+)

#using BenchmarkTools
#c = array_cache(cell_to_fx)
#@btime getindex!($c,$cell_to_fx,1)

# Operations with Broadcasting

afun(x) = x[2]
bfun(x) = x[1] + 3
a = GenericField(afun)
b = fill(GenericField(bfun),4)
f = Broadcasting(Operation(+))(a,b)

cell_to_a = fill(a,ncells)
cell_to_b = Fill(b,ncells)
cell_to_r = fill(evaluate(f,x),ncells)
cell_to_∇r = fill(evaluate(Broadcasting(∇)(f),x),ncells)

cell_to_f = lazy_map(Broadcasting(Operation(+)),cell_to_b,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,cell_to_r)
#@test cell_to_fx.maps.value == Broadcasting(+)
@test cell_to_fx.maps.value == Fields.BroadcastingFieldOpMap(+)

cell_to_∇f = lazy_map(Broadcasting(∇),cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
test_array(cell_to_∇fx,cell_to_∇r)
#@test cell_to_∇fx.maps.value == Broadcasting(+)
@test cell_to_∇fx.maps.value == Fields.BroadcastingFieldOpMap(+)

T = GenericField{Nothing}
cell_to_f = lazy_map(Broadcasting(Operation(+)),T,cell_to_b,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,cell_to_r)
#@test cell_to_fx.maps.value == Broadcasting(+)
@test cell_to_fx.maps.value == Fields.BroadcastingFieldOpMap(+)

# Operations with Broadcasting (product)

afun(x) = x[2]
bfun(x) = x[1] + 3
a = GenericField(afun)
b = fill(GenericField(bfun),4)
f = Broadcasting(Operation(*))(a,b)

cell_to_a = fill(a,ncells)
cell_to_b = Fill(b,ncells)
cell_to_r = fill(evaluate(f,x),ncells)
cell_to_∇r = fill(evaluate(Broadcasting(∇)(f),x),ncells)

cell_to_f = lazy_map(Broadcasting(Operation(*)),cell_to_b,cell_to_a)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
test_array(cell_to_fx,cell_to_r)

cell_to_∇f = lazy_map(Broadcasting(∇),cell_to_f)
cell_to_∇fx = lazy_map(evaluate,cell_to_∇f,cell_to_x)
test_array(cell_to_∇fx,cell_to_∇r)

# Integration

avals = [1.0,0.5,1.2,3.9]
a = MockField.(avals)
b = rand(length(avals),5)
d = linear_combination(b,a)
c = Broadcasting(Operation(*))(d,transpose(d))
w = rand(np)
fx = integrate(c,x,w)

cell_to_a = Fill(a,ncells)
cell_to_b = fill(b,ncells)
cell_to_w = Fill(w,ncells)
cell_to_d = lazy_map(linear_combination,cell_to_b,cell_to_a)
cell_to_dt = lazy_map(transpose,cell_to_d)
cell_to_c = lazy_map(Broadcasting(Operation(*)),cell_to_d,cell_to_dt)
cell_to_fx = lazy_map(integrate,cell_to_c,cell_to_x,cell_to_w)
cell_to_r = fill(fx,ncells)
test_array(cell_to_fx,cell_to_r)
@test isa(cell_to_fx.maps.value,IntegrationMap)

ϕ = GenericField(x->2*x)
fx = integrate(c,x,w,∇(ϕ))

cell_to_ϕ = fill(ϕ,ncells)
cell_to_J = lazy_map(∇,cell_to_ϕ)

cell_to_fx = lazy_map(integrate,cell_to_c,cell_to_x,cell_to_w,cell_to_J)
cell_to_r = fill(fx,ncells)
test_array(cell_to_fx,cell_to_r)
@test isa(cell_to_fx.maps.value,IntegrationMap)

# Reindex

avals = [1.0,0.5,1.2,3.9]
a = MockField.(avals)
b = VectorValue{2,Float64}[(1,1),(4,2),(3,5),(1,2)]
f = linear_combination(b,a)

cell_to_a = Fill(a,ncells)
cell_to_b = fill(b,ncells)
cell_to_c = lazy_map(linear_combination,cell_to_b,cell_to_a)

j_to_cell = [2,3,2,2,1]
j_to_c = lazy_map(Reindex(cell_to_c),j_to_cell)
j_to_x = Fill(x,length(j_to_cell))

j_to_cx = lazy_map(evaluate,j_to_c,j_to_x)
j_to_r = fill( f(x), length(j_to_cell) )
@test isa(j_to_cx.maps.value,Fields.LinearCombinationMap)
test_array(j_to_cx,j_to_r)

j_to_∇c = lazy_map(Broadcasting(∇),j_to_c)
j_to_∇cx = lazy_map(evaluate,j_to_∇c,j_to_x)
@test isa(j_to_∇cx.maps.value,Fields.LinearCombinationMap)
j_to_∇r = fill( ∇(f)(x), length(j_to_cell) )
test_array(j_to_∇cx,j_to_∇r)

# PosNegReindex

aval = 1.0
a = MockField(aval)
ipos_to_cell = [2,5,1]
cell_to_i = PosNegPartition(ipos_to_cell,ncells)
npos = length(cell_to_i.ipos_to_i)
nneg = length(cell_to_i.ineg_to_i)

ipos_to_a = fill(a,npos)
ineg_to_a = fill(0*a,nneg)

T = Union{eltype(ipos_to_a),eltype(ineg_to_a)}
cell_to_f = lazy_map(PosNegReindex(ipos_to_a,ineg_to_a),T,cell_to_i)
cell_to_fx = lazy_map(evaluate,cell_to_f,cell_to_x)
cell_to_r = [ zeros(np) for cell in 1:ncells]
cell_to_r[ipos_to_cell] = [ fill(aval,np) for cell in 1:npos]
#print_op_tree(cell_to_fx)

@test isa(cell_to_fx.maps.value,PosNegReindex)
test_array(cell_to_fx,cell_to_r)

#npp = np #(np,np)
#
#v = VectorValue{d}(1.0,1.0)
#f = MockField{d}(v)
#fx = fill(v,np)
#
#nf = 3
#nff = nf #(nf,nf)
#
#fa = fill(f,nf)
#
## Gradients
#
#∇fa = Broadcasting(∇)(fa)
#c = return_cache(∇fa,x)
## @santiagobadia : Not sure we can make it allocation-free
#evaluate!(c,∇fa,x)
## @btime evaluate!($c,$∇fa,$x)
#
#_∇fa = gradient.(fa)
#c = return_cache(∇fa,x)
## @santiagobadia : Whereas this one is allocation-free...
## @btime evaluate!($c,$_∇fa,$x)
#
#@test evaluate!(c,∇fa,x) == evaluate!(c,_∇fa,x)
#
## Transpose
#
#c = return_cache(fa,p)
## @btime evaluate!($c,$fa,$p);
#
#c = return_cache(fa,x)
## @btime evaluate!($c,$fa,$x);
#
#tfa = transpose(fa)
#c = return_cache(tfa,p)
## @btime evaluate!($c,$tfa,$p);
#tr = evaluate!(c,tfa,p)
#r = evaluate!(c,fa,p)
#@test transpose(tr) == r
#
#c = return_cache(tfa,x)
## @btime evaluate!($c,$tfa,$x);
#tr = evaluate!(c,tfa,x)
#r = evaluate!(c,fa,x)
#@test size(tr) == (size(r,1),1,size(r,2))
#@test reshape(permutedims(tr,[1,3,2]),size(r,1),size(r,2)) == r
#
## Broadcasted operations
#
#op = +
#b = Broadcasting(Operation(op))
#bpfa = b(fa,fa)
#@test bpfa == op(fa,fa)
#
#fa isa AbstractArray{<:Field}
#fa+fa
#+(fa,fa)
#
#c = return_cache(bpfa,x)
## @btime evaluate!($c,$bpfa,$x);
#
#op = ⋅
#b = Broadcasting(Operation(op))
#bpfa = b(fa,fa)
#@test bpfa == op.(fa,fa)
#
#c = return_cache(bpfa,x)
## @btime evaluate!($c,$bpfa,$x);
#
## transpose
#
#tfa = transpose(fa)
#tr = evaluate(tfa,x)
#r = evaluate(fa,x)
#
#c = return_cache(tfa,x)
## @btime evaluate!($c,$tfa,$x);
#
## field + field array
#
#op = ⋅
#b = Broadcasting(Operation(op))
#bpfa = b(f,fa)
#@test evaluate(bpfa,x) == broadcast(⋅,evaluate(f,x),evaluate(fa,x))
#
#c = return_cache(bpfa,x)
## @btime evaluate!(c,bpfa,x)
#
## column vector * row vector -> field array
#
#op = ⋅
#b = Broadcasting(Operation(op))
#bpfa = b(fa,tfa)
#evaluate(bpfa,x) == broadcast(⋅,evaluate(fa,x),evaluate(tfa,x))
#@test all(bpfa .== broadcast(⋅,fa,tfa))
#
#c = return_cache(bpfa,x)
## @btime evaluate!(c,bpfa,x)
#
## row vector * column vector -> field
#
#dopa = tfa*fa
#c = return_cache(dopa,x)
#evaluate!(c,dopa,x)
#@test evaluate(dopa,x)[1] == evaluate(tfa,p)⋅evaluate(fa,p)
## @btime evaluate!(c,dopa,x);
#
#dopa = fa⋅fa
#c = return_cache(dopa,x)
#evaluate!(c,dopa,x)
#@test evaluate(dopa,x)[1] == evaluate(tfa,p)⋅evaluate(fa,p)
## @btime evaluate!(c,dopa,x);
#
## linear combination
#
#va = [1.0,2.0,3.0]
#dopa = fa⋅va
#@test evaluate(va⋅fa,x) == evaluate(fa⋅va,x)
#c = return_cache(dopa,x)
## @btime evaluate!(c,dopa,x);
#
#@test linear_combination(fa,va) == linear_combination(va,fa)
#lc = linear_combination(fa,va)
#fax = evaluate(fa,x)
#lcx = evaluate(lc,x)
#for i in 1:length(x)
#  @test lcx[i] == fax[i,:]⋅va
#end
#
#c  =return_cache(lc,x)
## @btime evaluate!($c,$lc,$x);
#
#vb = [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0]
#@test linear_combination(fa,vb) == linear_combination(vb,fa)
#lc = linear_combination(fa,vb)
#fax = evaluate(fa,x)
#lcx = evaluate(lc,x)
#for i in 1:length(x)
#  @test lcx[i,:] == transpose(transpose(fax[i,:])*vb)
#end
#
#c  =return_cache(lc,x)
## @btime evaluate!($c,$lc,$x);
#
#lc = linear_combination(fa,va)
#fap = evaluate(fa,p)
#lcx = evaluate(lc,p)
#@test lcx == fap⋅va
#
#c  =return_cache(lc,p)
## @btime evaluate!($c,$lc,$p);
#
#lc = linear_combination(fa,vb)
#fap = evaluate(fa,p)
#lcp = evaluate(lc,p)
#@test lcp == transpose(transpose(fap) * vb)
#
#c  =return_cache(lc,p)
## @btime evaluate!($c,$lc,$p);
#
## composition
#
#bm = Broadcasting(∘)
#fc = bm(fa,f);
#
#c = return_cache(fc,x)
## @btime evaluate!(c,fc,x)

end # module


using UnPack, OrderedCollections, ForwardDiff

# TODO
# 1. Create model object
# 2. Run code in MATLAB and generate comparison test output for all functions, say at the deterministic steady state
# 3. Test ForwardDiff works properly for all cases, for both in place and non in place models
# 4. Label equations for μ, ξ so creating Γ₅ and Γ₆ makes more sense
#=function RiskAdjustedLinearization(μ::M, Λ::L, Σ::S, ξ::X, ccgf::CF,
                                   z::AbstractVector{T}, y::AbstractVector{T}, Ψ::AbstractMatrix{T},
                                   Nε::Int = -1) where {T <: Number, M <: Function, L <: Function,
                                                        S <: Function, X <: Function, CF <: Function}
end=#
mutable struct WachterDisasterRisk{T <: Real}
    μₐ::T
    σₐ::T
    ν::T
    δ::T
    ρₚ::T
    pp::T
    ϕₚ::T
    ρ::T
    γ::T
    β::T
end

function WachterDisasterRisk(; μₐ::T = .0252 / 4., σₐ::T = .02 / sqrt(4.), ν::T = .3, δ::T = 0., ρₚ::T = .08^(1. / 4.), pp::T = .0355 / 4.,
                             ϕₚ::T = .0114 / 4. / (.02 / sqrt(4.)) / sqrt(.0355 / 4.), ρ::T = 2.0, γ::T = 3.0,
                             β::T = exp(-.012 / 4.)) where {T <: Real}
    return WachterDisasterRisk{T}(μₐ, σₐ, ν, δ, ρₚ, pp, ϕₚ, ρ, γ, β)
end

function inplace_wachter_disaster_risk(m::WachterDisasterRisk{T}) where {T <: Real}
    @unpack μₐ, σₐ, ν, δ, ρₚ, pp, ϕₚ, ρ, γ, β = m

    @assert ρ != 1. # Forcing ρ to be non-unit for this example

    S  = OrderedDict{Symbol, Int}(:p => 1,  :εc => 2, :εξ => 3) # State variables
    J  = OrderedDict{Symbol, Int}(:vc => 1, :xc => 2, :rf => 3) # Jump variables
    SH = OrderedDict{Symbol, Int}(:εₚ => 1, :εc => 2, :εξ => 3) # Exogenous shocks
    Nz = length(S)
    Ny = length(J)
    Nε = length(SH)

    function μ(F, z, y) # the equation labeling could be wrong
        F_type    = eltype(F)
        F[S[:p]]  = (1 - ρₚ) * pp + ρₚ * z[S[:p]]
        F[S[:εc]] = zero(F_type)
        F[S[:εξ]] = zero(F_type)
    end

    function ξ(F, z, y) # the equation labeling could be wrong
        F[J[:vc]] = log(β) - γ * μₐ + γ * ν * z[S[:p]] - (ρ - γ) * y[J[:xc]] + y[J[:rf]]
        F[J[:xc]] = log(1. - β + β * exp((1. - ρ) * y[J[:xc]])) - (1. - ρ) * y[J[:vc]]
        F[J[:rf]] = (1. - γ) * (μₐ - ν * z[S[:p]] - y[J[:xc]])
    end

    Λ = zeros(T, Nz, Ny)

    function Σ(F, z)
        F_type = eltype(F)
        F[SH[:εₚ], SH[:εₚ]] = sqrt(z[S[:p]]) * ϕₚ * σₐ
        F[SH[:εc], SH[:εc]] = one(F_type)
        F[SH[:εξ], SH[:εξ]] = one(F_type)
    end

    function ccgf(F, α, z)
        F .= .5 .* α[:, 1].^2 + .5 * α[:, 2].^2 + (exp.(α[:, 3] + α[:, 3].^2 .* δ^2 ./ 2.) .- 1. - α[:, 3]) * z[S[:p]]
    end

    Γ₅ = zeros(T, Ny, Nz)
    Γ₅[J[:vc], S[:εc]] = (-γ * σₐ)
    Γ₅[J[:vc], S[:εξ]] = (γ * ν)
    Γ₅[J[:rf], S[:εc]] = (1. - γ) * σₐ
    Γ₅[J[:rf], S[:εξ]] = -(1. - γ) * ν

    Γ₆ = zeros(T, Ny, Ny)
    Γ₆[J[:vc], J[:vc]] = (ρ - γ)
    Γ₆[J[:rf], J[:vc]] = (1. - γ)

    z = [pp, 0., 0.]
    xc_sss = log((1. - β) / (exp((1. - ρ) * (ν * pp - μₐ)) - β)) / (1. - ρ)
    vc_sss = xc_sss + ν * pp - μₐ
    y = [vc_sss, xc_sss, -log(β) + γ * (μₐ - ν * pp) - (ρ - γ) * (vc_sss - xc_sss)]
    Ψ = zeros(T, Ny, Nz)
    return RiskAdjustedLinearization(μ, Λ, Σ, ξ, Γ₅, Γ₆, ccgf, z, y, Ψ, Nε)
end

function outofplace_wachter_disaster_risk(m::WachterDisasterRisk{T}) where {T}
    @unpack μₐ, σₐ, ν, δ, ρₚ, pp, ϕₚ, ρ, γ, β = m

    @assert ρ != 1. # Forcing ρ to be non-unit for this example

    S  = OrderedDict{Symbol, Int}(:p => 1,  :εc => 2, :εξ => 3) # State variables
    J  = OrderedDict{Symbol, Int}(:vc => 1, :xc => 2, :rf => 3) # Jump variables
    SH = OrderedDict{Symbol, Int}(:εₚ => 1, :εc => 2, :εξ => 3) # Exogenous shocks
    Nz = length(S)
    Ny = length(J)
    Nε = length(SH)

    function μ(z, y) # the equation labeling could be wrong
        return [(1 - ρₚ) * pp + ρₚ * z[S[:p]], 0., 0.]
    end

    function ξ(z, y) # the equation labeling could be wrong
        F = RiskAdjustedLinearizations.dualvector(y, z)

        F[J[:vc]] = log(β) - γ * μₐ + γ * ν * z[S[:p]] - (ρ - γ) * y[J[:xc]] + y[J[:rf]]
        F[J[:xc]] = log(1. - β + β * exp((1. - ρ) * y[J[:xc]])) - (1. - ρ) * y[J[:vc]]
        F[J[:rf]] = (1. - γ) * (μₐ - ν * z[S[:p]] - y[J[:xc]])
        return F
    end

    Λ = zeros(T, Nz, Nz)

    function Σ(z)
        F = zeros(eltype(z), Nz, Nz)
        F[SH[:εₚ], SH[:εₚ]] = sqrt(z[S[:p]]) * ϕₚ * σₐ
        F[SH[:εc], SH[:εc]] = 1.
        F[SH[:εξ], SH[:εξ]] = 1.
        return F
    end

    ccgf(α, z) = .5 * α[:, 1].^2 + .5 * α[:, 2].^2 + (exp.(α[:, 3] + α[:, 3].^2 * δ.^2 ./ 2.) .- 1. - α[:, 3]) * z[S[:p]]

    Γ₅ = zeros(T, Ny, Nz)
    Γ₅[J[:vc], S[:εc]] = (-γ * σₐ)
    Γ₅[J[:vc], S[:εξ]] = (γ * ν)
    Γ₅[J[:rf], S[:εc]] = (1. - γ) * σₐ
    Γ₅[J[:rf], S[:εξ]] = -(1. - γ) * ν

    Γ₆ = zeros(T, Ny, Ny)
    Γ₆[J[:vc], J[:vc]] = (ρ - γ)
    Γ₆[J[:rf], J[:vc]] = (1. - γ)

    z = [pp, 0., 0.]
    xc_sss = log((1. - β) / (exp((1. - ρ) * (ν * pp - μₐ)) - β)) / (1. - ρ)
    vc_sss = xc_sss + ν * pp - μₐ
    y = [vc_sss, xc_sss, -log(β) + γ * (μₐ - ν * pp) - (ρ - γ) * (vc_sss - xc_sss)]
    Ψ = zeros(T, Ny, Nz)
    return RiskAdjustedLinearization(μ, Λ, Σ, ξ, Γ₅, Γ₆, ccgf, z, y, Ψ, Nε)
end


"""
```
measurement(m::PoolModel{T}) where {T<:AbstractFloat}
```

Assign measurement equation for Dynamic Pools

```
p(y_t) = λ_t * p_1 + (1 - λ_t) * p_2
```

where

```
λ_t = weight assigned to model 1
p_1 = predictive density according to model 1
p_2 = predictive density according to model 2
```

Equal weights fixes λ = 1/2, and static weights computes the likelihood
as if λ is time-invariant. For Bayesian Model Averaging,
we compute the average according to weights computed according to BMA.

The outputs are a function that computes the average of
passed predictive densities and a degenerate measurement error Distribution.
"""
function measurement(m::PoolModel{T}) where {T<:AbstractFloat}
    obs = m.observables
    weight_type = get_setting(m, :weight_type)

    # Assumes λ to be weight on the first model
    F_u = DiscreteUniform(0,0)
    if weight_type == :dynamic
        Ψ_dynamic_pm(x::Vector{Float64}, data::Vector{Float64}) = dot(data, x)
        return Ψ_dynamic_pm, F_u
    elseif weight_type == :equal
        equal_wt = m[:λ].value
        Ψ_equal_pm(x::Vector{Float64}, data::Vector{Float64}) = dot(data, equal_wt .* ones(2))
        return Ψ_equal_pm, F_u
    elseif weight_type == :static
        Ψ_static_pm(x::Vector{Float64}, data::Vector{Float64}) = dot(data, x)
        return Ψ_static_pm, F_u
    elseif weight_type == :bma
        Ψ_bma_pm(x::Vector{Float64}, data::Vector{Float64}) = dot(data, x)
        return Ψ_bma_pm, F_u
    end
end


#=
   SEND+MORE=MONEY any base in Julia + ConstraintSolver.jl

   Number of solutions for base 2:30
   [0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231]

   Model created by Hakan Kjellerstrand, hakank@gmail.com
   See also my Julia page: http://www.hakank.org/julia/

=#

using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

function send_more_money_any_base(base=10,print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,
                                            "all_solutions"=> all_solutions,
                                            # "all_optimal_solutions"=>all_solutions,
                                            "logging"=>[],

                                            "traverse_strategy"=>:BFS,
                                            # "traverse_strategy"=>:DFS,
                                            # "traverse_strategy"=>:DBFS,

                                            # "branch_split"=>:Smallest,
                                            # "branch_split"=>:Biggest,
                                            "branch_split"=>:InHalf,

                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                            # "branch_strategy" => :IMPS, # default
                                            "branch_strategy" => :ABS, # Activity Based Search
                                            # "activity.decay" => 0.999, # default 0.999
                                            # "activity.max_probes" => 100, # default, 10
                                            # "activity.max_confidence_deviation" => 20, # default 20

                                            # "simplify"=>false,
                                            # "simplify"=>true, # default

                                            "time_limit"=>6,

                                            # "backtrack" => false, # default true
                                            # "backtrack_sorting" => false, # default true

                                            # "lp_optimizer" => cbc_optimizer,
                                            # "lp_optimizer" => glpk_optimizer,
                                            # "lp_optimizer" => ipopt_optimizer,
                                            ))

    println("base: $base")
    @variable(model, 0 <= x[1:8] <= base-1, Int)
    s,e,n,d,m,o,r,y = x

    @constraint(model, x in CS.AllDifferentSet())
    @constraint(model, s != 0)
    @constraint(model, m != 0)

    @constraint(model,
                                 (base^3)*s + (base^2)*e + base*n + d
                               + (base^3)*m + (base^2)*o + base*r + e
                 == (base^4)*m + (base^3)*o + (base^2)*n + base*e + y
        )

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    num_sols = 0
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                println("solution #$sol")
                x_val = convert.(Integer,JuMP.value.(x; result=sol))
                println("x:$x_val")
                println()
            end
        end
        println("base:$base num_sols:$num_sols\n")
    else
        println("status:$status")
    end

    return status,num_sols
end

num_sols_a = []
for base in 2:30
    local status
    @time status,num_sols = send_more_money_any_base(base,true,true)
    if status == MOI.TIME_LIMIT
        break
    else
        push!(num_sols_a,num_sols)
    end

end

println("Num solutions: $num_sols_a")


"""
Provides common astrodynamical models through `ModelingToolkit`,
physical attributes for solar system bodies through `SPICE`,
and ephemeris model downloading and interpolation through
`HORIZONS` and `Interpolations`!

!!! tip
    Check out the `ModelingToolkit` docs to learn how to use these systems for orbit
    propagation with `DifferentialEquations`, or see `GeneralAstrodynamics` for some
    convenient orbit propagation wrappers.

# Usage

## Physical Attributes

```julia
naifcode("earth") == 399
naifname("mars") == 499

μₑ = massparameter("earth")
Rₘ = meanradius("mars")
```

## Dynamics

```julia
using ModelingToolkit

length(states(R2BP())) == 6
length(states(NBP(2))) == 12
```

## Ephemeris

!!! warning
    Before usage, see the warning about copyrighted tools in this project's README, or in
    the Extended Help section of this docstring!

```julia
using HORIZONS
ephemeris("earth") isa DataFrame

using Dates
ephemeris("earth", (now()-Year(5), now()+Year(5)), Day(1)) isa DataFrame

using Interpolations: CubicHermite
ContinuousEphemeris("earth") isa ContinuousEphemeris{<:Number, <:Number, <:CubicHermite, <:CubicHermite, <:CubicHermite}
```

# Extended help

## License

!!! warning
    The license in this docstring applies to all code in this package, but note
    that the ephemeris data downloading functionality of this package
    (e.g. the `ephemeris` function) uses copyrighted tools with their
    own usage and code sharing restrictions. These copyrighted tools are
    owned by the Jet Propulsion Laboratory at the California Institute
    of Technology. These copyrighted tools are, in part, located at
    ftp://ssd.jpl.nasa.gov/pub/ssd/SCRIPTS/.

    Open source wrappers for these copyrighted tools exist, e.g.
    `HORIZONS.jl` and AstroPy's `astroquery`. The ephemeris handling
    functionality of this package uses `HORIZONS.jl`.

    It is the responsibility of the user to verify they meet the necessary
    requirements, as specifed by JPL in their scripts (e.g. `vec_tbl`),
    before they share or use the copyrighted tools. This includes the
    ephemeris handling functionality in this package (e.g. `ephemeris`).

$(LICENSE)

## Exports

### Always

* `naifcode`
* `naifname`
* `axialradii`
* `meanradius`
* `massparameter`

### Requires `ModelingToolkit`

* `NBP`
* `R2BP`
* `CR3BP`
* `R2BPFunction`
* `CR3BPFunction`
* `NBPFunction`

### Requires `HORIZONS`

* `ephemeris`

#### Requires `Interpolations` (and `HORIZONS`)

* `ContinuousEphemeris`

## Imports

### Always

* `Requires`, `SPICE`, `LinearAlgebra`, `DocStringExtensions`

### Requires `ModelingToolkit`

* `Memoize`

### Requires `HORIZONS`

* `CSV`, `DataFrames`

"""
module AstrodynamicalModels

import SPICE

using Requires
using LinearAlgebra
using DocStringExtensions

@template (FUNCTIONS, METHODS, MACROS) =
    """
    $(SIGNATURES)

    $(DOCSTRING)
    """

@template (TYPES, CONSTANTS) =
    """
    $(TYPEDEF)

    $(DOCSTRING)
    """

function __init__()
    @require ModelingToolkit="961ee093-0014-501f-94e3-6117800e7a78" begin

        using Memoize: @memoize

        export R2BP, CR3BP, NBP
        export R2BPFunction, CR3BPFunction, NBPFunction

        include(joinpath(@__DIR__, "models", "R2BP.jl"))
        include(joinpath(@__DIR__, "models", "CR3BP.jl"))
        include(joinpath(@__DIR__, "models", "NBP.jl"))
    end

    @require HORIZONS="5a3ac768-beb4-554a-9c98-3342fe3377f5" begin

        using Dates
        using DataFrames

        include(joinpath("ephemeris", "ephemeris.jl"))
        @require Interpolations="a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" include(joinpath("ephemeris", "interpolations.jl"))
    end
end

include(joinpath("properties", "NAIF.jl"))

end # module


include("classic.jl")
using Groebner
using Test
using AbstractAlgebra

using Logging
Logging.global_logger(Logging.ConsoleLogger(Logging.Warn))

###############################################################################

zerobuch()

fs = Groebner.rootn(3)
gb = Groebner.groebner(fs)

sign_gb = groebner_basis(fs)

println(sign_gb)
@test Groebner.isgroebner(sign_gb)
@test Groebner.groebner(sign_gb) == gb
@error "" BUCH_NF BUCH_DISCARDED BUCH_REDUCED

###############################################################################

zerobuch()

fs = Groebner.rootn(4)
gb = Groebner.groebner(fs)

sign_gb = groebner_basis(fs)

println(sign_gb)
@test Groebner.isgroebner(sign_gb)
@test Groebner.groebner(sign_gb) == gb
@error "" BUCH_NF BUCH_DISCARDED BUCH_REDUCED

###############################################################################

zerobuch()

fs = Groebner.change_ordering(Groebner.noonn(3), :degrevlex)
gb = Groebner.groebner(fs)

sign_gb = groebner_basis(fs)

println(sign_gb)
@test Groebner.isgroebner(sign_gb)
@test Groebner.groebner(sign_gb) == gb
@error "" BUCH_NF BUCH_DISCARDED BUCH_REDUCED

###############################################################################

zerobuch()

fs = Groebner.change_ordering(Groebner.noonn(4, ground=GF(2^31-1)), :degrevlex)
gb = Groebner.groebner(fs)

sign_gb = groebner_basis(fs)

println(sign_gb)
@test Groebner.isgroebner(sign_gb)
@test Groebner.groebner(sign_gb) == gb
@error "" BUCH_NF BUCH_DISCARDED BUCH_REDUCED

###############################################################################

zerobuch()

fs = Groebner.change_ordering(Groebner.kinema(ground=GF(2^31-1)), :degrevlex)
gb = Groebner.groebner(fs)

sign_gb = groebner_basis(fs)

println(sign_gb)
@test Groebner.isgroebner(sign_gb)
@test Groebner.groebner(sign_gb) == gb
@error "" BUCH_SPOLYS BUCH_DISCARDED BUCH_SPOLYS-BUCH_DISCARDED

###############################################################################

zerobuch()

fs = Groebner.change_ordering(Groebner.sparse5(), :degrevlex)
gb = Groebner.groebner(fs)

sign_gb = groebner_basis(fs)

println(sign_gb)
# @test Groebner.isgroebner(sign_gb)
@test Groebner.groebner(sign_gb) == gb
@error "" BUCH_NF BUCH_DISCARDED BUCH_REDUCED

###############################################################################

zerobuch()

fs = Groebner.change_ordering(Groebner.eco5(), :degrevlex)
gb = Groebner.groebner(fs)

sign_gb = groebner_basis(fs)

println(sign_gb)
# @test Groebner.isgroebner(sign_gb)
@test Groebner.groebner(sign_gb) == gb
@error "" BUCH_NF BUCH_DISCARDED BUCH_REDUCED


abstract type AbstractSolovevEquilibrium <: AnalyticEquilibrium end

function X(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    R(x,equ) * cos(ϕ(x,equ))
end

function Y(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    R(x,equ) * sin(ϕ(x,equ))
end

function Z(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    x[2] * equ.R₀
end

function R(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    x[1] * equ.R₀
end

function r(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    sqrt(r²(x, equ))
end

function r²(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    (R(x,equ) - equ.R₀)^2 + Z(x,equ)^2
end

function θ(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    atan2(Z(x,equ), R(x,equ) - equ.R₀)
end

function ϕ(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    x[3]
end

function J(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    R(x,equ)
end

function periodicity(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    p = zero(x)
    p[3] = 2π
    return p
end

function A₁(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    + equ.B₀ * equ.R₀ * x[2] / x[1] / 2
end

function A₂(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    - equ.B₀ * equ.R₀ * log(x[1]) / 2
end

function g₁₁(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    one(T)
end

function g₂₂(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    one(T)
end

function g₃₃(x::AbstractArray{T,1}, equ::AbstractSolovevEquilibrium) where {T <: Number}
    R(x, equ)^2
end


abstract type AbstractIFParameter end
@with_kw struct IFParameter{VF} <: AbstractIFParameter
    τm::VF = 20ms
    τe::VF = 5ms
    τi::VF = 10ms
    Vt::VF = -50mV
    Vr::VF = -60mV
    El::VF = Vr
end

abstract type AbstractIF end
@with_kw mutable struct IF{VF,VB} <: AbstractIF
    param::IFParameter = IFParameter()
    N::SNNInt = 100
    v::VF = param.Vr .+ rand(eltype(VF), N) .* (param.Vt - param.Vr)
    ge::VF = zeros(eltype(VF), N)
    gi::VF = zeros(eltype(VF), N)
    fire::VB = zeros(eltype(VB), N)
    I::VF = zeros(eltype(VF), N)
    records::Dict = Dict()
end
IF(x;kwargs...) = IF{Vector{SNNFloat},Vector{Bool}}(;kwargs...)

function integrate!(p::IF, param::IFParameter, dt::SNNFloat)
    @unpack N, v, ge, gi, fire, I = p
    @unpack τm, τe, τi, Vt, Vr, El = param
    @inbounds for i = 1:N
        v[i] += dt * (ge[i] + gi[i] - (v[i] - El) + I[i]) / τm
        ge[i] += dt * -ge[i] / τe
        gi[i] += dt * -gi[i] / τi
    end
    @inbounds for i = 1:N
        fire[i] = v[i] > Vt
        v[i] = ifelse(fire[i], Vr, v[i])
    end
end


using BenchmarkTools
using Printf

benchmark = true

exampleFilename = "src/advent/day12/example.txt"
inputFilename = "src/advent/day12/input.txt"

#### Direction

struct Direction
    name::Char
    heading::Int64
    dx::Int64
    dy::Int64
end

const north = Direction('N', 0, 0, 1)
const east = Direction('E', 90, 1, 0)
const south = Direction('S', 180, 0, -1)
const west = Direction('W', 270, -1, 0)

const directions = [north, east, south, west]
const directionByName = Dict('N' => north, 'E' => east, 'S' => south, 'W' => west)
const directionByHeading = Dict(0 => north, 90 => east, 180 => south, 270 => west)

function turnRight(direction::Direction, degrees::Int64)::Direction
    heading = direction.heading + degrees
    while heading < 0
        heading += 360
    end
    heading = heading % 360
    directionByHeading[heading]
end

turnLeft(direction::Direction, degrees::Int64)::Direction = turnRight(direction, -degrees)

#### Runner

function run(filename, entity; trace = false)
    f = open(filename)
    while !eof(f)
        line = readline(f)
        c, value = line[1], parse(Int64, line[2:length(line)])
        if c == 'F'
            doF(entity, value)
        elseif c == 'L'
            doL(entity, value)
        elseif c == 'R'
            doR(entity, value)
        else
            doDirection(entity, directionByName[c], value)
        end
        trace && @printf("%s -> %s\n", line, toString(entity))
    end
    close(f)
end

#### Point

struct Point
    x::Int64
    y::Int64
end

Base.:+(a::Point, b::Tuple{Int64, Int64}) = Point(a.x + b[1], a.y + b[2])

toString(point::Point) = @sprintf("(%s, %s)", point.x, point.y)

manhattanDistance(point::Point) = abs(point.x) + abs(point.y)

#### part 1

## Vehicle

mutable struct Vehicle
    position::Point
    heading::Direction
end

doDirection(vehicle::Vehicle, direction::Direction, distance::Int64) = vehicle.position += (direction.dx * distance, direction.dy * distance)

doF(vehicle::Vehicle, distance::Int64) = doDirection(vehicle, vehicle.heading, distance)

function doR(vehicle::Vehicle, degrees::Int64)
    vehicle.heading = turnRight(vehicle.heading, degrees)
end

doL(vehicle::Vehicle, degrees::Int64) = doR(vehicle, -degrees)

toString(vehicle::Vehicle) = @sprintf("%s %s", toString(vehicle.position), vehicle.heading.name)

##

function part1(filename; trace=false)
    vehicle = Vehicle(Point(0, 0), east)
    run(filename, vehicle; trace = trace)
    manhattanDistance(vehicle.position)
end

@assert part1(exampleFilename) == 25

@show part1(inputFilename)
benchmark && @btime part1(inputFilename);

#### part 2

## Vehicle2

mutable struct Vehicle2
    position::Point
    waypoint::Point
    heading::Direction
end

doDirection(vehicle::Vehicle2, direction::Direction, distance::Int64) = vehicle.waypoint += (direction.dx * distance, direction.dy * distance)

doF(vehicle::Vehicle2, value::Int64) = vehicle.position += (vehicle.waypoint.x * value, vehicle.waypoint.y * value)

function doR(vehicle::Vehicle2, degrees::Int64)
    radians = Base.Math.deg2rad(degrees)
    vehicle.waypoint = Point(round(vehicle.waypoint.x * cos(radians) + vehicle.waypoint.y * sin(radians)), round(vehicle.waypoint.y * cos(radians) - vehicle.waypoint.x * sin(radians)))
end

doL(vehicle::Vehicle2, degrees::Int64) = doR(vehicle, -degrees)

toString(vehicle::Vehicle2) = @sprintf("%s %s %s", toString(vehicle.position), toString(vehicle.waypoint), vehicle.heading.name)

##

function part2(filename; trace=false)
    vehicle = Vehicle2(Point(0, 0), Point(10, 1), east)
    run(filename, vehicle; trace = trace)
    manhattanDistance(vehicle.position)
end

@assert part2(exampleFilename) == 286

@show part2(inputFilename)
benchmark && @btime part2(inputFilename);


using PolyChaos, Test

outliers = [-1.3, 1.3]

@test_throws DomainError PolyChaos._throwError(3)

densities = [w_legendre,
			 w_uniform01,
			 w_uniform_11,
			 build_w_jacobi(1.2,3.4),
			 build_w_beta(1.2,3.4)
]

@testset "Supports of densities" begin
	for ρ in densities, x in outliers
		@test_throws DomainError ρ(x)
	end
end

@test_throws DomainError w_laguerre(-1)
@test_throws DomainError build_w_gamma(1)(-1)


using DifferentialEquations, thesis

function f′(du, u, p, t)
    du .= -p.α .* u
    du[1] += p.input(t)
    du[2:end] .+= p.α*u[1:end-1]
end

p = (
    input=t-> 0.5*(sin(t)) + 0.3*(cos(0.7+1.5*t)),
    α = 2.0,
    δ = zeros(5),
    spiketimes = [Float64[] for i ∈ 1:5]
)
tspan = (0.0,10.0)
u₀ = copy(p.δ)
cb = CallbackSet((ContinuousCallback((u,t,integrator) -> u[i]-integrator.p.δ[i], integrator->push!(integrator.p.spiketimes[i], integrator.t)) for i ∈ eachindex(p.δ))...)

sol = solve(ODEProblem(f′, u₀, tspan.-(10.0,0), p); callback=cb)

p_in = plot(sol, tspan=tspan, xlims=tspan.+(0,0.2), xlabel=false, ylabel="signal embedding")
plot!(p.input, tspan..., color=:black, label="s(t)")
hline!([p.δ], color=thesis_colors[end], linestyle=:dash, label="threshold")

p_spike = plot(xlabel="time", ylabel="population code")
for (i,spiketrain) ∈ enumerate(p.spiketimes)
    spiketrain = filter(s->tspan[1]<=s<=tspan[2], spiketrain)
    scatter!(p_in, spiketrain, fill(p.δ[i],length(spiketrain)), markersize=3, markercolor=:black, markerstrokecolor=i, markerstrokewidth=2, label="")
    plot!(p_spike, [spiketrain'; spiketrain'], [fill(i,length(spiketrain))';fill(i+0.75,length(spiketrain))'], linewidth=2, color=i, label="")
    scatter!(p_spike, spiketrain, fill(i+0.75,length(spiketrain)), markersize=3,  markercolor=:black, markerstrokecolor=i, markerstrokewidth=2, label="")
end
plot!(p_spike, xlims=tspan.+(0,0.2), ylims=[1.0,6.25], yticks=1:5, label=false)

plot(p_in, p_spike, layout=grid(2,1, heights=[0.7,0.3]))

savefig("figures/embedding.pdf")
savefig("figures/embedding.svg")


"""
Fit Distributions to MC sim data for secondary infections per index case as a function of infection age

# Arguments
- `df::DataFrame` - DataFrame for csv containing MC simulations for daily individual infections.
- `horizon::Int=14` - Number of days in infection age before individual is considered naturally recovered and completely uninfectious.
- `sample_size::Int=50` - Sample size for `infections_path` csv where row entry is average infections for given sample size.
"""
function FitInfectionDistributions(df::DataFrame, horizon::Int=14, sample_size::Int=50)
    Λ = 1_000 # variance scaling parameter
    distributions = Vector{Gamma{Float64}}(undef, horizon)
    for day in 1:horizon
        try
            k, θ = Distributions.params(fit(Gamma, df[!,day]))
            distributions[day] = Gamma(k/(sample_size*Λ), Λ*θ*sample_size)
        catch e
            if e isa DomainError
                distributions[day] = Gamma(1e-100,1e-100)
            else
                throw(e)
            end
        end
    end
    return distributions
end

"""
Proportion of infectious population above some limit of detection from MC simulations

# Arguments
- `df::DataFrame` - DataFrame for csv containing MC simulations for daily individual infections.
- `day::Int` - Infection age (day)
- `LOD::Real` - Limit of detection (Log scale: ``10^x \\rightarrow x``)
"""
function prop_above_LOD(df::DataFrame, day::Int, LOD::Real)::Float64
    sum(df[!,day] .> LOD)/size(df,1)
end

"""
Return distribution resulting from sum of i.i.d RV's characterized by Gamma distribution

# Arguments
- `dist::Gamma` - Distribution characterizing random variable
- `N::Int` - Number of i.i.d RV's summed
"""
function RVsum(dist::Gamma, N::Int)
    k, θ = Distributions.params(dist)
    if k*N > 0
        return Gamma(k*N, θ)
    else
        return Gamma(1e-100, 1e-100)
    end
end


#=
Visual illustration of the law of large numbers.

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

References
----------

Based off the original python file illustrates_lln.py
=#
using PyPlot
using Distributions

n = 100
srand(42)  # reproducible results

# == Arbitrary collection of distributions == #
distributions = {"student's t with 10 degrees of freedom" => TDist(10),
                 "beta(2, 2)" => Beta(2.0, 2.0),
                 "lognormal LN(0, 1/2)" => LogNormal(0.5),
                 "gamma(5, 1/2)" => Gamma(5.0, 2.0),
                 "poisson(4)" => Poisson(4),
                 "exponential with lambda = 1" => Exponential(1)}

# == Create a figure and some axes == #
num_plots = 3
fig, axes = plt.subplots(num_plots, 1, figsize=(10, 10))

bbox = [0., 1.02, 1., .102]
legend_args = {:ncol => 2,
               :bbox_to_anchor => bbox,
               :loc => 3,
               :mode => "expand"}
subplots_adjust(hspace=0.5)

for ax in axes
    dist_names = collect(keys(distributions))
    # == Choose a randomly selected distribution == #
    name = dist_names[rand(1:length(dist_names))]
    dist = pop!(distributions, name)

    # == Generate n draws from the distribution == #
    data = rand(dist, n)

    # == Compute sample mean at each n == #
    sample_mean = Array(Float64, n)
    for i=1:n
        sample_mean[i] = mean(data[1:i])
    end

    # == Plot == #
    ax[:plot](1:n, data, "o", color="grey", alpha=0.5)
    axlabel = LaTeXString("\$\\bar{X}_n\$ for \$X_i \\sim\$ $name")
    ax[:plot](1:n, sample_mean, "g-", lw=3, alpha=0.6, label=axlabel)
    m = mean(dist)
    ax[:plot](1:n, ones(n)*m, "k--", lw=1.5, label=L"$\mu$")
    ax[:vlines](1:n, m, data, lw=0.2)
    ax[:legend](;legend_args...)
end


using JuMP

# Inspired from Joey Huchette's test in ConvexHull.jl
function ex1test(lib::Polyhedra.Library)
    m = Model()
    @variable(m, x)
    @variable(m, y)
    @constraints(m, begin
        12 + 2x - y ≥ 0
        -6 - x + 2y ≥ 0
        -3 + x + y ≥ 0
        1 + x ≥ 0
    end)

    poly = polyhedron(m, lib)

    V = [ 0  3;
         -1  4;
         -1 10]

    R = [1 2;
         2 1]

    generator_fulltest(poly, V, R)
end


OPT = OneClassActiveLearning.QueryStrategies.QuerySynthesisStrategies.Optimization
@testset "query synthesis optimization" begin
    @testset "particle swarm optimization" begin
        for d in [1, 2, 10]
            @testset "$d dim" begin
                Random.seed!(0)
                f(x) = vec(sum(abs.(x), dims=1))
                x_opt, _ = OPT.pso(f, -ones(d), ones(d))
                @test size(x_opt) == (d,)
                @test all(f(reshape(x_opt, 1, d)) .< 0.015 * d)
            end
        end
    end
    @testset "query optimizers" begin
        MAX_ITER = 1
        for opt in [EvolutionaryOptimization(:cmaes, μ=1, λ=2, iterations=MAX_ITER),
                    ParticleSwarmOptimization(swarmsize=3, maxiter=MAX_ITER),
                    BlackBoxOptimization(:dxnes, MaxSteps=MAX_ITER)]
            for d in [1, 2, 10]
                @testset "$(typeof(opt)): $d dim" begin
                    Random.seed!(0)
                    f(x) = -vec(sum(abs.(x), dims=1))
                    x_opt = OPT.query_synthesis_optimize(f, opt, randn(d, 10), fill(:U, 10))
                    @test size(x_opt) == (d, 1)
                end
            end
        end
    end
end


function potnucl!(ptnucl::Bool, nr, r, zn, vn )
    SMALL = 1e-10
    if abs(zn) <= SMALL
        vn[:] .= 0.0
        return
    end
    if ptnucl
        # nucleus is taken to be a point particle
        for ir in 1:nr
            vn[ir] = zn/r[ir]
        end
    else
        # approximate nuclear radius
        rn = radnucl(zn)
        t1 = zn/(2.0*rn^3)
        t2 = 3.0*rn^2
        for ir in 1:nr
          if r[ir] < rn
            vn[ir] = t1*(t2 - r[ir]^2)
          else
            vn[ir] = zn/r[ir]
          end
        end
    end
    return
end


using Dynesty
using Test
using Distributions

@testset "Dynesty.jl" begin
    # Write your tests here.
    # define a distribution
    d = MvNormal(ones(5))

    loglikelihood(x) = logpdf(d, x)

    prior_transform(p) = -5.0 .+ 10.0.*p

    smplr = NestedSampler(5; nlive=1000)
    dysmplr = DynamicNestedSampler(5)
    # sample using dynamic nested sampling with 500 initial live points
    res1 = sample(loglikelihood, prior_transform, smplr; dlogz=0.01, print_progress=false)
    res2 = sample(loglikelihood, prior_transform, smplr; dlogz=0.01, print_progress=false)
    res3 = sample(loglikelihood, prior_transform, smplr; dlogz=0.01, print_progress=false)

    mres = Dynesty.merge(res1, res2, res3)

    res = sample(loglikelihood, prior_transform, dysmplr; nlive_init=200, print_progress=false)

    runplot(res1)
    cornerplot(res1)
    traceplot(res1)
    boundplot(res1, (1,2),idx=10)
    cornerbound(res1, it=100)
    cornerpoints(res1)

    esamples = resample_equal(res3, Int(ceil(2*Dynesty.ess(res1))))

    @test isapprox(mean(getindex.(esamples, 1)), mean(d)[1], atol=1e-1)

end


using ITensors
using KrylovKit
using LinearAlgebra

struct ITensorVector
  A::ITensor
end

# Overloads for ITensorVector
LinearAlgebra.norm(v::ITensorVector) = norm(v.A)
LinearAlgebra.dot(x::ITensorVector, y::ITensorVector) = dot(x.A, y.A)
Base.:*(a::Number, v::ITensorVector) = ITensorVector(a * v.A)
LinearAlgebra.axpy!(a::Number, x::ITensorVector, y::ITensorVector) = (axpy!(a, x.A, y.A); y)
Base.similar(v::ITensorVector) = ITensorVector(similar(v.A))
LinearAlgebra.mul!(x::ITensorVector, y::ITensorVector, a::Number) = (mul!(x.A, y.A, a); x)
LinearAlgebra.rmul!(v::ITensorVector, a::Number) = (rmul!(v.A, a); v)

# Overloads for ITensorMatrix
struct ITensorMatrix
  A::ITensor
end
(A::ITensorMatrix)(v::ITensorVector) = ITensorVector(noprime(A.A * v.A))

i = Index(4, "i")
A = ITensorMatrix(randomITensor(i', dag(i)))
b = ITensorVector(randomITensor(i))
x0 = ITensorVector(randomITensor(i))

x, _ = linsolve(A, b, x0)
err = A(x)
axpy!(-1, b, err)
@show norm(err)


using Test

using AutomaticDifferentiation.Forward
using AutomaticDifferentiation.Backward

@testset "Smoke" begin
    (x, y) = rand(2)
    z_value = x * y + sin(x)

    dx = 1.0
    dy = 1.0
    ∂z_over_∂x = y + cos(x)
    ∂z_over_∂y = x
    dz = ∂z_over_∂x * dx + ∂z_over_∂y * dy

    ## Forward

    let x = ind_var(x), y = ind_var(y)
        z = x * y + sin(x)
        @test z.value ≈ z_value
        @test z.grad ≈ dz
    end

    let x = ind_var(x)
        z = x * y + sin(x)
        @test z.value ≈ z_value
        @test z.grad ≈ ∂z_over_∂x
    end

    let y = ind_var(y)
        z = x * y + sin(x)
        @test z.value ≈ z_value
        @test z.grad ≈ ∂z_over_∂y
    end

    ## Backward

    let x = Var(x), y = Var(y)
        z = x * y + sin(x)
        z.grad = 1.0
        @test grad!(x) ≈ ∂z_over_∂x
        @test grad!(y) ≈ ∂z_over_∂y
    end
end


using Base.Test
using QuantumOptics

b = FockBasis(3)

u = Ket[fockstate(b, 1), fockstate(b, 2)]
v = Ket[fockstate(b, 2), fockstate(b, 1)]

bu = SubspaceBasis(u)
bv = SubspaceBasis(v)

T1 = projector(bu, b)
T2 = projector(bv, b)
T12 = projector(bu, bv)

state = fockstate(b, 2)
state_u = Ket(bu, [0, 1])
state_v = Ket(bv, [1., 0])

@test T1*state == state_u
@test T2*state == state_v

state_v = Ket(bv, [1, -1])
state_u = Ket(bu, [-1, 1])

@test T12*state_v == state_u

u2 = Ket[1.5*fockstate(b, 1), fockstate(b, 1) + fockstate(b, 2)]
bu2_orth = subspace.orthonormalize(SubspaceBasis(u))
@test bu2_orth.basisstates == bu.basisstates


function compute_c_n!(t::Transit_Struct{T}) where {T <: Real}
# Transform the u_n coefficients to c_n, which are coefficients
# of the basis in which the P(G_n) functions are computed.
a_n = zeros(T,t.n+1)
# Pre-allocated memory - just need to zero terms:
a_n[1] = one(T)  # Add in the first constant coefficient term
for i=1:t.n
  # Compute the contribution to a_n*\mu^n
  bcoeff = one(T)
  for j=0:i
#    a_n[j+1] -= t.u_n[i]*binomial(i,j)*(-1)^j
    a_n[j+1] -= t.u_n[i]*bcoeff*(-1)^j
    bcoeff *= (i-j)/(j+1)
#    println("i: ",i," j: ",j," a_i: ",a_n[j+1])
  end
end
# Now, compute the c_n coefficients:
for j=t.n:-1:2
  if j >= t.n-1
    t.c_n[j+1] = a_n[j+1]/(j+2)
  else
    t.c_n[j+1] = a_n[j+1]/(j+2)+t.c_n[j+3]
  end
end
if t.n >= 3
  t.c_n[2] = a_n[2]+3*t.c_n[4]
else
  t.c_n[2] = a_n[2]
end
if t.n >= 2
  t.c_n[1] = a_n[1]+2*t.c_n[3]
else
  t.c_n[1] = a_n[1]
end
return
end

function compute_c_n_grad!(t::Transit_Struct{T}) where {T <: Real}
# Transform the u_n coefficients to c_n, which are coefficients
# of the basis in which the P(G_n) functions are computed.
# Compute the derivatives of the flux with respect to the u coefficients.
# We define c_n with two extra elements which are zero:
a_n = zeros(T,t.n+1)
dadu = zeros(T,t.n+1,t.n)
a_n[1] = one(T)  # Add in the first constant coefficient term
for i=1:t.n
  # Compute the contribution to a_n*\mu^n
  bcoeff = one(T)
  for j=0:i
#    a_n[j+1] -= t.u_n[i]*binomial(i,j)*(-1)^j
    a_n[j+1] -= t.u_n[i]*bcoeff*(-1)^j
#    dadu[j+1,i] -= binomial(i,j)*(-1)^j
    dadu[j+1,i] -= bcoeff*(-1)^j
    bcoeff *= (i-j)/(j+1)
#    println("i: ",i," j: ",j," a_i: ",a_n[j+1])
  end
end
# Now, compute the c_n coefficients and propagate derivatives:
for j=t.n:-1:2
  if j >= t.n-1
    t.c_n[j+1] = a_n[j+1]/(j+2)
    for i=1:t.n
      t.dcdu[j+1,i] = dadu[j+1,i]/(j+2)
    end
  else
    t.c_n[j+1] = a_n[j+1]/(j+2)+t.c_n[j+3]
    for i=1:t.n
      t.dcdu[j+1,i] = dadu[j+1,i]/(j+2) + t.dcdu[j+3,i]
    end
  end
end
if t.n >= 3
  t.c_n[2] = a_n[2]+3*t.c_n[4]
  for i=1:t.n
    t.dcdu[2,i] = dadu[2,i] + 3*t.dcdu[4,i]
  end
else
  t.c_n[2] = a_n[2]
  for i=1:t.n
    t.dcdu[2,i] = dadu[2,i]
  end
end
if t.n >= 2
  t.c_n[1] = a_n[1]+2*t.c_n[3]
  for i=1:t.n
    t.dcdu[1,i] = dadu[1,i] + 2*t.dcdu[3,i]
  end
else
  t.c_n[1] = a_n[1]
  for i=1:t.n
    t.dcdu[1,i] = dadu[1,i]
  end
end
return
end


using Polynomials, Combinatorics, LinearAlgebra, Random, Statistics

"""
The moment (Prony) mapping
"""
𝔐(x,c) = k->sum([c[i]*x[i]^k for i=1:length(x)])

"""
The Fourier mapping
"""
𝔉(x,c) = k->sum([c[i]*exp(im*x[i]*k) for i=1:length(x)])

"""
Given measurement function and an approximation to the nodes,
    solve the linear least squares system.
"""
function solveLinear(f,Ω,x̃)
    V = [exp(im*x*k) for k=0:Ω,x=x̃]
    d = [f(k) for k=0:Ω]
    V\d
end

"""
The inverse moment mapping
"""
function 𝔓(mvec)
    d2 = length(mvec)
    if (isodd(d2))
        error("Length of moment vector should be even")
    end

    d = d2>>1

    # construct the Hankel
    H = [mvec[i+j+1] for i=0:d-1,j=0:d-1]
    rhs = -mvec[d+1:end]
    q = H\rhs
    p = Polynomials.Poly(vcat(q,1))
    xrec=roots(p)
    V = [x^k for k=0:d-1,x=xrec]
    crec = V\mvec[1:d]
    xrec,crec
end

"""
Worst case perturbation function
"""
𝔉ᵪ(x,c,ϵ,s::Array) = begin
    R = length(x)
    x₀ = x[s]
    c₀ = c[s]

    # move to origin
    μ = (minimum(x₀)+maximum(x₀))/2
    y₀ = x₀.-μ

    m₀=[𝔐(y₀,c₀)(j+0.) for j=0:(2*length(s)-1)]
    #m₁ = copy(m₀)
    m₀[end] = m₀[end]+ϵ
    y₁,c₁ = 𝔓(m₀)

    # move back
    x₁ = y₁ .+ μ

    xₙ = copy(vec(x))
    xₙ[s]=real.(x₁)

    cₙ = convert(typeof(c₁),copy(vec(c)))
    cₙ[s] = c₁
    𝔉(xₙ,cₙ)
end

𝔉ᵪ(x,c,ϵ,ℓ::Integer) = 𝔉ᵪ(x,c,ϵ,collect(1:ℓ))
𝔉ᵪ(x,c,ϵ) = 𝔉ᵪ(x,c,ϵ,length(x))
𝔉ᵪ(ℓ) = (x,c,ϵ)->𝔉ᵪ(x,c,ϵ,ℓ)

"""
Random perturbation function
"""
𝔉ᵣ(x,c,ϵ) = k->(𝔉(x,c)(k) + ϵ*((-1+2*rand(MersenneTwister(Int(floor(k)))))+im*(-1+2*rand(MersenneTwister(Int(floor(k)))))))

𝔉ᵣ(Ωₘ::Integer,seed::Integer) = begin
    rng = MersenneTwister(seed)
    seq = -1 .+ rand(rng,Ωₘ+1)

    (x,c,ϵ)->(k->(𝔉(x,c)(k)+ϵ*seq[Int(floor(k+1))]))
end

Hank(F,L,M) = [F(i+j) for i=1:L,j=1:M]

"""
The matrix pencil algorithm for recovery of complex exponentials from equispaced samples

F - function handle for sampling from the model
N - number of equispaced samples
n - the model order
L - the pencil parameter L (default = ceil(N/2))

Returns: array of n estimated frequencies, normalized to [0,2π]
"""
function MatrixPencil(F,N,n,L)
    # check we have enough samples
    if(N < 2*n)
        error("N should be at least 2n")
    end

    if (L < n+1)
        error("L should be at least n+1")
    end

    # construct the matrices H₁ and H₂
    M = N-L
    H = Hank(F,L,M)

    Rₗ = H[2:end,:] # lower part
    Rᵤ = H[1:end-1,:] # upper part

    # compute truncated SVDs
    S₁ = svd(Rᵤ;full=true)
    u₁ = S₁.U[:,1:n]
    Σ₁ = Diagonal(S₁.S[1:n])
    v₁ = S₁.V[:,1:n]

    S₂ = svd(Rₗ;full=true)
    u₂ = S₂.U[:,1:n]
    Σ₂ = Diagonal(S₂.S[1:n])
    v₂ = S₂.V[:,1:n]

    # compute the generalized eigenvalues of the corresponding *regular* pencil
    A = (u₂)'*u₁*Σ₁*(v₁)'*v₂
    B = Σ₂
    z = eigvals(B\A)
    mod.(2π.-angle.(z),2π)

end

MatrixPencil(F,N,n) = MatrixPencil(F,N,n,ceil(N/2))

"""
Amplitude function: all 1's
"""
all1s(ℓ,R) = ones(R)

"""
Amplitude function: i^j
"""
imag1s(ℓ,R) = [(im)^j for j=0:R-1]

"""
Amplitude function: plus minus 1
"""
opposite1s(ℓ,R) = [(-1)^j for j=0:R-1]

"""
Amplitude function: complex amplitudes
"""
mixed1s(ℓ,R) = [1+im*(-1)^j for j=0:R-1]

"""
Creates a clustered node configuration with a single cluster
and other nodes well-separated
(equidistributed in the remaining part of the circle)
"""
Ζ(ℓ,R,Δ,endpoint=π) = begin

    if (ℓ>R)
        error("ℓ should be at most R")
    end

    ρ = (ℓ-1)*Δ
    if (endpoint < ρ)
        error("Δ is too large")
    end

    δ = (endpoint-ρ)/(R-ℓ+1)

    r₁ = collect(range(0,step=Δ,length=ℓ))
    r₂ = collect(range(ρ+δ,step=δ,length=R-ℓ))
    [r₁;r₂].-π/2
end

distCirc(x₀::Number,x₁::Number) = min(mod(x₀-x₁,2π),mod(x₁-x₀,2π))

function housedorff_distance(test_points, candidates)
    n = length(test_points)
    m = length(candidates)
    @assert m==n "Err"
    t = reshape(test_points,(n,1))
    c = reshape(candidates,(n,1))
    return minimum([maximum(abs.([tt[i]-c[i] for i=1:n])) for tt in permutations(t)])
end

"""
Computes the minimal separation between the nodes in an array
"""
function min_sep(X::Array)
    # distance matrix
    D = [distCirc(x,y) for x in X, y in X]
    return minimum(D+diagm(Inf*ones(length(X))))
end

get_separation(X::Array) = [minimum(
            [distCirc(X[j],X[k]) for k in setdiff(1:length(X),j)])
        for j=1:length(X)]

get_separation(X::Array,Y::Array) = [minimum(
            [distCirc(X[j],Y[k]) for k in 1:length(Y)])
        for j=1:length(X)]

get_separation_ext(X::Array,Y::Array) = [findmin(
            [distCirc(X[j],Y[k]) for k in 1:length(Y)])
        for j=1:length(X)]

"""
Test the Matrix Pencil algo: maximal error
"""
testMP(ℓ,R,Δ,Ω,ϵ,clusterFun=Ζ,pertFun=𝔉ᵪ,amplitudeFun = all1s) = begin
    x = clusterFun(ℓ,R,Δ)
    c = amplitudeFun(ℓ,R)
    f = pertFun(x,c,ϵ)
    ϵ₀ = maximum(abs.([𝔉(x,c)(k+0.) - f(k+0.) for k=0:Ω]))
    x̃ = MatrixPencil(f,Ω,R)
    c̃ = solveLinear(f,Ω,x̃)
    Δx = housedorff_distance(exp.(im.*x),exp.(im.*x̃))/ϵ₀
    Δc = housedorff_distance(c,c̃)/ϵ₀

    # need to match the first two
    η = get_separation(x) # distance to closest neighbor
    σ = get_separation(x,x̃) # error for each node

    # success if all errors are smaller than corresponding separations
    succ = reduce((x,y)->x&&y,σ .< η/3)
    [Ω*Δx,Δc,Int(succ),ϵ₀]
end

"""
Test the Matrix Pencil algo: a specific node
"""
testMP_individual(ℓ,R,Δ,Ω,ϵ,node_index,clusterFun=Ζ,pertFun=𝔉ᵪ,amplitudeFun = all1s) = begin
    x = clusterFun(ℓ,R,Δ)
    c = amplitudeFun(ℓ,R)
    f = pertFun(x,c,ϵ)
    ϵ₀ = maximum(abs.([𝔉(x,c)(k+0.) - f(k+0.) for k=0:Ω]))
    x̃ = MatrixPencil(f,Ω,R)
    c̃ = solveLinear(f,Ω,x̃)

    # need to match the first two
    η = get_separation(x) # distance to closest neighbor
    σ̃ = get_separation_ext(x,x̃) # error for each node, including index
    Δx = σ̃[node_index][1]
    idx = σ̃[node_index][2]
    Δc = (abs(c̃[idx] - c[node_index]))

    succ = (σ̃[node_index][1] < η[node_index]/3)

    # success if all errors are smaller than corresponding separations
    [Ω*Δx/ϵ₀,Δc/ϵ₀,Int(succ),ϵ₀]
end

"""
Test the Matrix Pencil algorithm: accuracy for every node
"""
testMP_all(ℓ,R,Δ,Ω,ϵ,clusterFun=Ζ,pertFun=𝔉ᵪ,amplitudeFun = all1s) = begin
    x = clusterFun(ℓ,R,Δ)
    c = amplitudeFun(ℓ,R)
    f = pertFun(x,c,ϵ)
    ϵ₀ = maximum(abs.([𝔉(x,c)(k+0.) - f(k+0.) for k=0:Ω]))
    x̃ = MatrixPencil(f,Ω,R)
    c̃ = solveLinear(f,Ω,x̃)

    # need to match the first two
    η = get_separation(x) # distance to closest neighbor
    σ̃ = get_separation_ext(x,x̃) # error for each node, including index
    Δx = [sep[1] for sep=σ̃]
    Δc = [abs(c̃[σ̃[i][2]]-c[i]) for i=1:R]

    # success if all errors are smaller than corresponding separations
    succ = [Int(σ̃[i][1] < η[i]/3) for i=1:R]

    [Ω*Δx/ϵ₀,Δc/ϵ₀,succ,ϵ₀]
end

NrangeLog(low,high,n=100) = Int.(unique(floor.(10.0.^LinRange(low,high,n))))

"""
Run a single random experiment testing accuracy
"""
runSingle_Acc(ℓ,R,clusterFun, pertFun, amplitudeFun, ϵR,ΩR,ΔR) = begin
    ϵ = ϵR[rand(1:end)]
    Ω = ΩR[rand(1:end)]
    Δ = ΔR[rand(1:end)]

    res = testMP_all(ℓ,R,Δ,Ω,ϵ,clusterFun,pertFun,amplitudeFun)
    [inv(Ω*Δ),res...]
end

"""
Run a single random experiment testing accuracy
"""
runSingle(ℓ,R,clusterFun, pertFun, amplitudeFun, ϵR,ΩR,ΔR) = begin
    ϵ = ϵR[rand(1:end)]
    Ω = ΩR[rand(1:end)]
    Δ = ΔR[rand(1:end)]

    res = testMP(ℓ,R,Δ,Ω,ϵ,clusterFun,pertFun,amplitudeFun)

    [inv(Ω*Δ),res[3:4]...]
end

runSingle_ind(ℓ,R,clusterFun, pertFun, amplitudeFun, ni, ϵR,ΩR,ΔR) = begin
    ϵ = ϵR[rand(1:end)]
    Ω = ΩR[rand(1:end)]
    Δ = ΔR[rand(1:end)]

    res = testMP_individual(ℓ,R,Δ,Ω,ϵ,ni,clusterFun,pertFun,amplitudeFun)

    [inv(Ω*Δ),res[3:4]...]
end

cc(x) = (x==0) ? (:red) : (:blue)
ss(x) = (x==0) ? (:circle) : (:dtriangle)


"""
    near_optimal_robust_violated(bp, x, v; optimizer, [atol])

Returns the set of non near-optimal robust upper-level constraints
"""
function near_optimal_robust_violated(bp::BilevelProblem, x::Vector{Float64}, v::Vector{Float64}, δ; optimizer, atol=10e-4)
    m = Model(optimizer)
    MOI.set(m, MOI.Silent(), true)
    @variable(m, y[1:bp.nl] >= 0)
    @constraint(m, bp.A * x + bp.B * y .≤ bp.b)
    @constraint(m, bp.d ⋅ y ≤ bp.d ⋅ v + δ)
    ks = BitSet()
    for k in 1:bp.mu
        @objective(m, Max, bp.H[k,:] ⋅ y)
        optimize!(m)
        if termination_status(m) == MOI.OPTIMAL || termination_status(m) == MOI.DUAL_INFEASIBLE
            if objective_value(m) > bp.q[k] - bp.G[k,:] ⋅ x - atol
                push!(ks, k)
            end
        end
    end
    return ks
end


abstract MarkovChain

abstract MarkovChainFitParams

type DTFiniteMarkovChainMleFitParams{I<:Integer,D<:Dict} <: MarkovChainFitParams
  counts_initial::AbstractVector{I}
  counts_transition::AbstractMatrix{I}
  alphabetdict::D
end

type DTFiniteMarkovChain{
      A<:Any,F<:AbstractFloat} <: MarkovChain
  initial::AbstractVector{F}
  transition::AbstractMatrix{F}
  alphabet::AbstractVector{A}
end

type DTFiniteMarkovChainWithFit{
    FMC<:DTFiniteMarkovChain,MCFP<:MarkovChainFitParams} <: MarkovChain
  mc::FMC
  fitparams::MCFP
end


"""
    fuzzy_scores(predicted, real; neighborhood, ignored=())

Return a fuzzy score for each cell. Compares each cell of the two arrays.
It finds the shortest distance within the neighborhood where the
value in the `real` array is present in `predict` array.

The longer the distance, the lower score for this cell. Maximum
is 1 and if not present its 0. The weight function is linear.

- `predict`: Array of predicted data
- `real`: Array of real target data

# Keywords

- `neighborhood`: any `Neighborhood` oject, `Window(1)` by default.
- `banlist`: Values in the arrays which will not be evaluated.

Adapted from: https://github.com/johanlahti/urban-lu-model/blob/master/script/Utils.py
"""
function fuzzy_scores(predicted, real; neighborhood=Window{1}(), ignored=0)
    broadcast(real, predicted, CartesianIndices(real)) do r, p, I
        r in ignored && return 0.0

        weight = 0.0
        shortestdist = Inf

        # Get neighborhood values from the `real` values array
        real_hood = _setbuffer(neighborhood, _getwindow(real, Tuple(I)))
        # Search the neighborhood
        for (hood_val, dist) in zip(neighbors(real_hood), distances(real_hood))
            if p === hood_val && dist < shortestdist
                # Store the shortest distance at which we found the value
                shortestdist = dist
                weight = 1 - dist / (length(hood) + 1)
            end
        end
        return weight
    end
end


module GaussLegendre
    using FastGaussQuadrature

    function constract(integtablenum)
        return gausslegendre(integtablenum)
    end

    function gl_integ(f, x1, x2, vh_val)
        xm = 0.5 * (x1 + x2)
        xr = 0.5 * (x2 - x1)

        s = sum(i -> vh_val.w[i] * f(xm + xr * vh_val.x[i]), eachindex(vh_val.x))

        return s * xr
    end
end


@doc raw"""
    LocalLevelExplanatory(y::Vector{Fl}, X::Matrix{Fl}) where Fl

A local level model with explanatory variables is defined by:
```math
\begin{gather*}
    \begin{aligned}
        y_{t} &=  \mu_{t} + X_{1,t} \cdot \beta_{1,t} + \dots + X_{n,t} \cdot \beta_{n,t} + \varepsilon_{t} \quad &\varepsilon_{t} \sim \mathcal{N}(0, \sigma^2_{\varepsilon})\\
        \mu_{t+1} &= \mu_{t} + \xi_{t} &\xi_{t} \sim \mathcal{N}(0, \sigma^2_{\xi})\\
        \beta_{1,t+1} &= \beta_{1,t} &\tau_{1, t} \sim \mathcal{N}(0, \sigma^2_{\tau_{1}})\\
        \dots &= \dots\\
        \beta_{n,t+1} &= \beta_{n,t} &\tau_{n, t} \sim \mathcal{N}(0, \sigma^2_{\tau_{n}})\\\\
    \end{aligned}
\end{gather*}
```

# Example
```jldoctest
julia> model = LocalLevelExplanatory(rand(100), rand(100, 1))
LocalLevelExplanatory
```
"""
mutable struct LocalLevelExplanatory <: StateSpaceModel
    hyperparameters::HyperParameters
    system::LinearUnivariateTimeVariant
    results::Results
    exogenous::Matrix

    function LocalLevelExplanatory(y::Vector{Fl}, X::Matrix{Fl}) where Fl

        @assert length(y) == size(X, 1)

        num_observations = size(X, 1)
        num_exogenous = size(X, 2)
        m = num_exogenous + 1

        # Define system matrices
        Z = [vcat(ones(Fl, 1), X[t, :]) for t in 1:num_observations]
        T = [Matrix{Fl}(I, m, m) for _ in 1:num_observations]
        R = [vcat(one(Fl), zeros(Fl, m-1, 1)) for _ in 1:num_observations]
        d = [zero(Fl) for _ in 1:num_observations]
        c = [zeros(m) for _ in 1:num_observations]
        H = [one(Fl) for _ in 1:num_observations]
        Q = [ones(Fl, 1, 1) for _ in 1:num_observations]

        system = LinearUnivariateTimeVariant{Fl}(y, Z, T, R, d, c, H, Q)

        # Define hyperparameters names
        names = [["sigma2_ε", "sigma2_η"];["β_$i" for i in 1:num_exogenous]]
        hyperparameters = HyperParameters{Fl}(names)

        return new(hyperparameters, system, Results{Fl}(), X)
    end
end

# Obligatory methods
function default_filter(model::LocalLevelExplanatory)
    Fl = typeof_model_elements(model)
    a1 = zeros(Fl, num_states(model))
    P1 = Fl(1e6) .* Matrix{Fl}(I, num_states(model), num_states(model))
    steadystate_tol = Fl(1e-5)
    return UnivariateKalmanFilter(a1, P1, num_states(model), steadystate_tol)
end

function get_beta_name(model::LocalLevelExplanatory, i::Int)
    return model.hyperparameters.names[i + 2]
end

function initial_hyperparameters!(model::LocalLevelExplanatory)
    Fl = typeof_model_elements(model)
    observed_variance = variance_of_valid_observations(model.system.y)
    initial_hyperparameters = Dict{String,Fl}(
        "sigma2_ε" => observed_variance, "sigma2_η" => observed_variance
    )
    # This is an heuristic for a good approximation
    initial_exogenous = model.exogenous \ model.system.y
    for i in axes(model.exogenous, 2)
        initial_hyperparameters[get_beta_name(model, i)] = initial_exogenous[i]
    end
    set_initial_hyperparameters!(model, initial_hyperparameters)
    return model
end

function constrain_hyperparameters!(model::LocalLevelExplanatory)
    for i in axes(model.exogenous, 2)
        constrain_identity!(model, get_beta_name(model, i))
    end
    constrain_variance!(model, "sigma2_ε")
    constrain_variance!(model, "sigma2_η")
    return model
end

function unconstrain_hyperparameters!(model::LocalLevelExplanatory)
    for i in axes(model.exogenous, 2)
        unconstrain_identity!(model, get_beta_name(model, i))
    end
    unconstrain_variance!(model, "sigma2_ε")
    unconstrain_variance!(model, "sigma2_η")
    return model
end

function fill_model_system!(model::LocalLevelExplanatory)
    H = get_constrained_value(model, "sigma2_ε")
    fill_H_in_time(model, H)
    for t in 1:length(model.system.Q)
        model.system.Q[t][1] = get_constrained_value(model, "sigma2_η")
    end
    return model
end

function fill_H_in_time(model::LocalLevelExplanatory, H::Fl) where Fl
    return fill_system_matrice_with_value_in_time(model.system.H, H)
end

function reinstantiate(::LocalLevelExplanatory, y::Vector{Fl}, X::Matrix{Fl}) where Fl
    return LocalLevelExplanatory(y, X)
end

has_exogenous(::LocalLevelExplanatory) = true


@inline function Flux(CellData,s,z,Def)
    """
    Flux Transfer Function
    # Add Ins and Outs
        # Cell Data
        # Frequency Vector
        # Discretisation Locations
        # Electrode Definition
    """

 if Def == "Pos"
    Electrode = CellData.Pos #Electrode Length
 else
    Electrode = CellData.Neg #Electrode Length
 end

CC_A = CellData.Const.CC_A   # Current-collector area [m^2]
as = 3*Electrode.ϵ_s/Electrode.Rs # Specific interfacial surf. area
κ_eff = CellData.Const.κ*Electrode.ϵ_e^Electrode.κ_brug #Effective Electrolyte Conductivity
σ_eff = Electrode.σ*Electrode.ϵ_s^Electrode.σ_brug #Effective Electrode Conductivity

#Defining SOC
θ = CellData.Const.SOC * (Electrode.θ_100-Electrode.θ_0) + Electrode.θ_0

#Beta's
β = @. Electrode.Rs*sqrt(s/Electrode.Ds )

#Prepare for j0
ce0 = CellData.Const.ce0
cs_max = Electrode.cs_max
cs0 = cs_max * θ
α = Electrode.α

#Current Flux Density
if CellData.Const.CellTyp == "Doyle_94"
    κ = Electrode.k_norm/Electrode.cs_max/ce0^(1-α)
    j0 = κ*(ce0*(cs_max-cs0))^(1-α)*cs0^α
else
   j0 = Electrode.k_norm*(ce0*(cs0/cs_max*(1-cs0/cs_max)))^(1-α)
end

#Resistances
Rtot = R*CellData.Const.T /(j0*F^2) + Electrode.RFilm

#∂Uocp_Def
∂Uocp_elc = CellData.Const.∂Uocp(Def,θ)/cs_max

#Condensing Variable
ν = @. Electrode.L*sqrt((as/σ_eff+as/κ_eff)/(Rtot+∂Uocp_elc*(Electrode.Rs/(F*Electrode.Ds))*(tanh(β)/(tanh(β)-β))))
ν_∞ = @. Electrode.L*sqrt(as*((1/κ_eff)+(1/σ_eff))/(Rtot))

#Transfer Function
j_tf = @. ν*(σ_eff*cosh(ν*z)+κ_eff*cosh(ν*(z-1)))/(as*F*Electrode.L*CC_A*(κ_eff+σ_eff)*sinh(ν))
D = @. ν_∞*(σ_eff*cosh(ν_∞*z)+κ_eff*cosh(ν_∞*(z-1)))/(as*F*Electrode.L*CC_A*(κ_eff+σ_eff)*sinh(ν_∞))
D_term = "@. $ν_∞*($σ_eff*cosh($ν_∞*$z)+$κ_eff*cosh($ν_∞*($z-1)))/($as*$F*$(Electrode.L)*$CC_A*($κ_eff+$σ_eff)*sinh($ν_∞))"
zero_tf =ones(size(z,1))*1/(CellData.Const.CC_A*as*F*Electrode.L)
j_tf[:,findall(s.==0)] .= zero_tf[:,findall(s.==0)]
res0 = zeros(length(z))

if Def == "Pos"
    j_tf = -j_tf
    D = -D
    D_term = "@. -$ν_∞*($σ_eff*cosh($ν_∞*$z)+$κ_eff*cosh($ν_∞*($z-1)))/($as*$F*$(Electrode.L)*$CC_A*($κ_eff+$σ_eff)*sinh($ν_∞))"
    if Debug == 1
        println("D:Flux:Pos",D)
        println("z:Flux:Pos",z)
        println("ν_∞:Flux:Pos",ν_∞)
        println("j0:Flux:Pos",j0)
        println("Rtot:Flux:Pos",Rtot)
        println("σ_eff:Flux:Pos",σ_eff)
        println("ν_∞:Flux:Pos",ν_∞)
        println("L:Flux:Pos",Electrode.L)
    end
else
    if Debug == 1
        println("D:Flux:Neg",D)
        println("z:Flux:Neg",z)
        println("ν_∞:Flux:Neg",ν_∞)
        println("D:Flux:Neg",D)
    end
end

return j_tf, D, res0, D_term

end