###################################################
## network/tools/graphdep.jl
## find dependence relations between edges
###################################################

"""
	`findNetworkDependence`: express independent edges as sum of dependent edges
	Args:
		n 			: 	the network
		independent : 	list of independent edges
		dependent   : 	list of the remaining (dependent) edges
		numDeps		: 	optional, sparsity parameter
	Returns:
		dependencies:	a m by n matrix, where m is the number of independent edges and n is the total number of edges
						 each column corresponds to the weights of each independent edge in each edge
		edgeMap		:	dictionary, where keys are (orig, dest) pairs and values are the indices in the sorted list of edges
"""
function findNetworkDependence(n::Network, independent::Vector{Edge}, dependent::Vector{Edge}; numDeps::Int = 3)
	# initialize independent edges
	edgeList = collect(edges(n.graph))
	edgeMap = Dict(edgeList[i] => i for i = eachindex(edgeList))
	dep = zeros(length(independent), length(edgeList))
	dependencies = spzeros(length(independent), length(edgeList))
	#special case of all independent edges
	if length(dependent) == 0
		return speye(length(edgeList)), edgeMap
	end
	# build matrix of dependencies
	A = zeros(length(dependent), length(dependent))
	B = zeros(length(dependent), length(independent))
	for (row, edge) in enumerate(dependent)
		nearEdges = findNearEdges(n, edge)
		nNearEdges = length(nearEdges)
		for edge in nearEdges
			if edge in independent
				B[row, findfirst(isequal(edge), independent)] = 1/nNearEdges
			else
				A[row, findfirst(isequal(edge), dependent)] = 1/nNearEdges
			end
		end
	end
	# perform matrix inversion
	Ainv = (eye(size(A)[1]) - A) ^ (-1)
	# create dependency matrix
	depIndices = [edgeMap[edge] for edge in dependent]
	for (row, edge) in enumerate(independent)
		dep[row, edgeMap[edge]] = 1.0
		dep[row, depIndices] = Ainv * B[:, row]
	end
	# sparsify dependency matrix
	for edge in dependent
		dep[:, edgeMap[edge]] = sparsify(dep[:,edgeMap[edge]], numDeps)
	end
	# Multiply and divide by appropriate distances to transform velocity relations to time relations
	for (row, edge) in enumerate(independent)
		dep[row, depIndices] = dep[row, depIndices] .* 1/n.roads[(src(edge), dst(edge))].distance
	end
	for edge in dependent
		dep[:, edgeMap[edge]] = dep[:,edgeMap[edge]] .* n.roads[(src(edge), dst(edge))].distance
	end
	for i in eachindex(independent), j in eachindex(edgeList)
		if dep[i,j] > 0
			dependencies[i,j] = dep[i,j]
		end
	end
	return dependencies, edgeMap
end

"""
	`sparsify` : given a vector of dependences summing to 1, identify main components, reduce all others to 0 and renormalize
	Args:
		dependency 	:	vector summing to 1
		numDeps	  	:	number of components to keep, > 1
		minSize		: 	minimum value that a component must have (after sparsification) to be kept
	Returns:
		newDependency : vector summing to 1, with only numDeps nonzeros
"""
function sparsify(dependency::Vector{Float64}, numDeps::Int, minSize::Float64 = 0.05)
	# find largest components
	p = reverse(sortperm(dependency))
	newDependency = zeros(length(dependency))
	# renormalize
	total = sum(dependency[p[1:numDeps]])
	for i = 1:numDeps
		newDependency[p[i]] = dependency[p[i]]/total
	end
	# if smallest component is too small, re-sparsify with one less component
	if minimum(newDependency[newDependency .> 0]) < minSize
		return sparsify(dependency, numDeps-1, minSize)
	end
	return newDependency
end

"""
	`simplifyPath`: represent path as weighted sum of independent edges
	Args:
		path 		:	path as (Edge, Float) dictionary
		independent : 	list of independent edges
		dependencies:	matrix returned by findNetworkDependence
		edgeMap		:	dictionary, where keys are edges and values are the indices in the list of edges
	Returns:
		newPath		: 	path as (Edge, Float) dictionary, where the keys are the independent edges and the values are their weights in the path
"""
function simplifyPath(path::Dict{Edge, Float64}, independent::Vector{Edge}, dependencies::AbstractArray{Float64,2}, edgeMap::Dict{Edge, Int})
	newPath = Dict{Edge, Float64}()
	dep = zeros(size(dependencies)[1])
	for edge in keys(path)
		dep += path[edge] * dependencies[:,edgeMap[edge]]
	end
	for (i, weight) in enumerate(dep)
		if weight > 0
			newPath[independent[i]] = weight
		end
	end
	return newPath
end

"""
	`evaluateTime` : given time values for independent edges (times), return time of dependent edge (specified by dependency)
	Args:
		n 				: Network object we are working with
		dependencies	: matrix returned by findNetworkDependence
		times 			: array of edge times
		independent 	: list of independent edges
		edgeMap 		: dictionary, where keys are edges and values are the indices in the list of edges
	Returns:
		newTimes 		: array of edge times, where times of independent edges are the same as in input times, and times of dependent edges are calculated from independent edges
"""
function evaluateTimes(n::Network, dependencies::AbstractArray{Float64,2}, times::AbstractArray{Float64,2}, independent::Vector{Edge}, edgeMap::Dict{Edge, Int})
	edgeList = collect(edges(n.graph))
	newTimes = spzeros(Float64, size(times)[1], size(times)[2])
	for (i, edge) in enumerate(edgeList)
		newTimes[src(edge), dst(edge)] = sum([times[src(ind), dst(ind)] * dependencies[idx,i] for (idx, ind) in enumerate(independent)])
	end
	return newTimes
end

"""
	`updateIndependentEdges`	: given set of paths, update independent set of edges
	Args:
		paths		:	vector of vector of paths as edge=> weight dictionaries
		independent :	list of independent edges
		dependent 	:	list of dependent edges
		numEdges	:	number of edges to remove from independent set
		minIndep	:	minimum number of edges that the independent set must always contain
	Returns:
		newIndependent, newDependent : new sets of independent and dependent edges
"""
function updateIndependentEdges(paths::Vector{Vector{Dict{Edge, Float64}}}, independent::Vector{Edge}, dependent::Vector{Edge}, numEdges::Int = 10, minIndep::Int = 100)
	# check if edges can still be removed
	numToRemove = min(numEdges, length(independent) - minIndep)
	if numToRemove == 0
		return independent, dependent
	end
	indices = Dict(independent[i] => i for i=eachindex(independent))
	totalWeight = zeros(length(independent))
	for pathVector in paths, path in pathVector, edge in keys(path)
		totalWeight[indices[edge]] += path[edge]
	end
	# fgind lowest weighted edges
	p = sortperm(totalWeight)
	newIndependent = independent[p[(numToRemove+1):end]]
	newDependent = dependent
	append!(newDependent, independent[p[1:numToRemove]])
	sort!(newDependent)
	return newIndependent, newDependent
end


using WebStitcher, FileIO
n = 5 # correspondencs (>= 4)
# Else open a GUI to let you choose correspondences

im1 = load(joinpath(@__DIR__, "law1.jpg"))
im2 = load(joinpath(@__DIR__, "law2.jpg"))
(XY1, XY2), plot = WebStitcher.getcorrespondences(im1, im2, n)
#save(pointsPath, "XY1", "XY2")
plot # need to display this in a cell

include("projective_transform.jl")
H21 = projective_transform(XY2[], XY1[]) # 2 --> 1
## Stitch images from perspective 1

I1 = [
    ImageStitcher(copy(im1), eye(3)),
    ImageStitcher(copy(im2), H21)
]

# order = "natural": closet (to chosen persepctive) displayed on top
# order = "reverse": farthest displayed on top
imS1 = stitchImages(I1, order = "reverse")


using CellListMap
using StaticArrays
import Random

#
# In this example we compute the "gravitational potential", assigning to each particle
# has a different mass. In this case, the closure is used to pass the masses to the
# function that computes the potential.
#
function gravitational_potential(;N=100_000,parallel=true,x=nothing)

    # Number of particles, sides and cutoff
    sides = @SVector [250,250,250]
    cutoff = 10.
    box = Box(sides, cutoff)

    # Particle positions
    Random.seed!(321)
    if x === nothing
        x = [ sides .* rand(SVector{3,Float64}) for i in 1:N ]
    end

    # Initialize auxiliary linked lists
    cl = CellList(x, box, parallel=parallel)

    # masses
    mass = [ 5 * x[i][1] for i in 1:N ]

    # Function to be evalulated for each pair: build distance histogram
    function potential(i, j, d2, u, mass)
        d = sqrt(d2)
        u = u - 9.8 * mass[i] * mass[j] / d
        return u
    end

    # Run pairwise computation
    u = map_pairwise(
        (x, y, i, j, d2, u) -> potential(i, j, d2, u, mass),
        0.0,box,cl,
        parallel=parallel
    )

    return u

end


using Knet
import Gym
import AutoGrad
import Random
using Statistics

mutable struct History
    nS::Int
    nA::Int
    γ::Float64
    states::Vector{Float64}
    actions::Vector{Int}
    rewards::Vector{Float64}
end
History(nS, nA, γ) = History(nS, nA, γ, zeros(0),zeros(Int, 0),zeros(0))

function discount(rewards, γ)
    R = similar(rewards)
    R[end] = rewards[end]
    for k = length(rewards)-1:-1:1
        R[k] = γ * R[k+1] + rewards[k]
    end
    # return R
    return (R .- mean(R)) ./ (std(R) + 1e-10) #speeds up training a lot
end

function initweights(hidden, xsize=4, ysize=2)
    w = []
    x = xsize
    for y in [hidden...]
        push!(w, xavier(y, x))
        push!(w, zeros(y, 1))
        x = y
    end
    push!(w, xavier(ysize, x)) # Prob actions
    push!(w, zeros(ysize, 1))
    push!(w, xavier(1, x)) # Value function
    push!(w, zeros(1, 1))

    return w
end

function predict(w, x)
    for i = 1:2:length(w)-4
        x = relu.(w[i] * x .+ w[i+1])
    end
    prob_act = w[end-3] * x .+ w[end-2]
    value = w[end-1] * x .+ w[end]
    return prob_act, value
end

function sample_action(probs)
    @assert size(probs, 2) == 1
    cprobs = cumsum(probs, dims=1)
    sampled = cprobs .> rand()
    return mapslices(argmax, sampled, dims=1)[1]
end

function loss(w, history)
    nS, nA = history.nS, history.nA
    M = length(history.states)÷nS
    states = reshape(history.states, nS, M)
    R = discount(history.rewards, history.γ)

    p, V = predict(w, states)
    V = vec(V)
    A = R .- V   # advantage
    inds = history.actions + nA*(0:M-1)
    lp = logsoftmax(p, dims=1)[inds] # lp is a vector

    return -mean(lp .* AutoGrad.value(A)) + L2Reg(A)
end

L2Reg(x) = mean(x .* x)

function main(;
    hidden = [100], # width inner layers
    lr = 1e-2,
    γ = 0.99, #discount rate
    episodes = 500,
    render = true,
    seed = -1,
    infotime = 50)

    env = Gym.GymEnv("CartPole-v1")
    seed > 0 && (Random.seed!(seed); Gym.seed!(env, seed))
    nS, nA = 4, 2
    w = initweights(hidden, nS, nA)
    opt = [Adam(lr=lr) for _=1:length(w)]

    avgreward = 0
    for episode=1:episodes
        state = Gym.reset!(env)
        episode_rewards = 0
        history = History(nS, nA, γ)
        for t=1:10000
            p, V = predict(w, state)
            p = softmax(p, dims=1)
            action = sample_action(p)

            next_state, reward, done, info = Gym.step!(env, action-1)
            append!(history.states, state)
            push!(history.actions, action)
            push!(history.rewards, reward)
            state = next_state
            episode_rewards += reward

            episode % infotime == 0 && render && Gym.render(env)
            done && break
        end

        avgreward = 0.1 * episode_rewards + avgreward * 0.9
        if episode % infotime == 0
            println("(episode:$episode, avgreward:$avgreward)")
            Gym.close!(env)
        end

        dw = grad(loss)(w, history)
        update!(w, dw, opt)
    end

    return w
end


@testset "pckmeans" begin
    X = [1 0 -1 0 0; 0 1 0 -1 0]
    C = [0 1 0 0 0; 1 0 1 0 0; 0 1 0 1 0; 0 0 1 0 -1; 0 0 0 -1 0]
    W = abs.(C)
    M = [1 0; -1 0]
    mx, nx = size(X)
    mm, km = size(M)

    pcs = pckmeans(X, C, W, M)
    pc = pcs[end]

    @test isa(pcs, Vector{PartitionalClustering})
    @test pc.X == X
    @test size(pc.C) == (nx, nx)
    @test eltype(pc.C) == Int
    @test size(pc.W) == (nx, nx)
    @test eltype(pc.W) == Float64
    @test size(pc.Y) == (km, nx)
    @test all(y -> 0 <= y <= 1, pc.Y)
    @test size(pc.M) == (mm, km)
end


using AlgebraicDynamics.CPortGraphDynam
using AlgebraicDynamics.DWDDynam
using AlgebraicDynamics.CPortGraphDynam: draw, barbell, gridpath, grid, meshpath
using AlgebraicDynamics.DWDDynam: trajectory
using Catlab.WiringDiagrams
using Catlab.WiringDiagrams.CPortGraphs
using Catlab.CategoricalAlgebra
using Catlab.Theories
using Catlab.Graphs
using Catlab

using DelayDiffEq
using LabelledArrays

using Test
using PrettyTables
using Base.Iterators: repeated

function simulate(f::ContinuousMachine{T}, nsteps::Int, h::Real, u0::Vector, xs=T[]) where T
    trajectory(euler_approx(f, h), u0, xs, nothing, nsteps)
end

function printsim(traj, stepfun, indxfun, shape)
    for u in stepfun(traj)
        pretty_table(reshape(indxfun(u), shape), equal_columns_width=true, noheader=true)
    end
end

d = barbell(2)
xs = [
    ContinuousMachine{Float64}(2,2,(u, x, p, t)->[x[1]*u[1], x[2]*u[2]], (u,p,t)->u),
    ContinuousMachine{Float64}(2,2,(u, x, p, t)->[1/x[1]*u[1], -x[2]*u[2]], (u,p,t)->u)
]
h = 0.1
u₀ = ones(Float64, 4)
composite = oapply(d, xs)
fcomp(u,p,t) = eval_dynamics(composite, u, Float64[], p, t)
rcomp(u) = readout(composite,u)
@test rcomp(u₀) == []
u₁ = fcomp(u₀, Float64[], h)
@test u₁ == [1,1, 1, -1]
@test rcomp(u₁) == []
@test fcomp(u₁, [], h) == [1,-1, 1, 1]
# @show simulate(composite, 10, h, u₀)

d₀ = OpenCPortGraph()
add_parts!(d₀, :Box, 1)
d₁ = barbell(2)
F = ACSetTransformation((Box=[2],), d₀, d₁)
G = ACSetTransformation((Box=[1],), d₀, d₁)
# |1| <-> |3| <-> |5|
# |2| <-> |4| <-> |6|
d₂ = apex(pushout(F,G))
Catlab.Theories.id(OpenCPortGraph, n) = begin
    g = OpenCPortGraph()
    add_parts!(g, :Box, 1)
    add_parts!(g, :Port, n, box=1)
    add_parts!(g, :OuterPort, n, con=1:n)
    return g
end
lob(n) = let
    b = barbell(n)
    p = add_parts!(b, :Port, n, box=1)
    add_parts!(b, :OuterPort, n, con=p)
    b
end
d₂′ = ocompose(barbell(2), [id(OpenCPortGraph, 2), lob(2)])
# @show d₂′
β = 0.4
μ = 0.4
α₁ = 0.01
α₂ = 0.01

sirfuncb = (u,x,p,t)->[-β*u[1]*u[2] - α₁*(u[1]-x[1]), # Ṡ
                        β*u[1]*u[2] - μ*u[2] - α₂*(u[2]-x[2]), #İ
                        μ*u[2] # Ṙ
                        ]
sirfuncm = (u,x,p,t)->[-β*u[1]*u[2] - α₁*(u[1]-(x[1]+x[3])/2),
                        β*u[1]*u[2] - μ*u[2] - α₂*(u[2]-(x[2]+x[4])/2),
                        μ*u[2]
                        ]

boundary  = ContinuousMachine{Float64}(2,3,sirfuncb, (u,p,t)->u[1:2])
middle    = ContinuousMachine{Float64}(4,3, sirfuncm, (u,p,t)->u[[1,2,1,2]])
threecity = oapply(d₂, [boundary,middle,boundary])

# println("Simulating 3 city")
traj = simulate(threecity, 100, 0.01, [100,1,0,100,0,0,100,0,0.0] )
# map(traj) do u
#     return (i1=u[2], i2=u[5], i3=u[8])
# end |> pretty_table

gl = @acset OpenCPortGraph begin
    Box = 3
    Port = 7
    Wire = 4
    OuterPort = 3
    box = [1,1,2,2,2,3,3]
    src = [2, 3, 5, 6]
    tgt = [3, 2, 6, 5]
    con = [1,4,7]
end

gm = @acset OpenCPortGraph begin
    Box = 3
    Port = 10
    Wire = 4
    OuterPort = 6
    box = [1,1,1,2,2,2,2,3,3,3]
    src = [2, 4, 6, 8]
    tgt = [4, 2, 8, 6]
    con = [3, 7, 10, 1, 5, 9]
end

gr = @acset OpenCPortGraph begin
    Box = 3
    Port = 7
    Wire = 4
    OuterPort = 3
    box = [1,1,2,2,2,3,3]
    src = [1, 3, 4, 6]
    tgt = [3, 1, 6, 4]
    con = [2,5,7]
end

symedges(g) = g.tables.W[g.tables.W.src .<= g.tables.W.tgt]
sympairs(z) = Iterators.filter(x->x[1] <= x[2], z)
pg2 = ocompose(barbell(3), [gl, gm])
g2 = migrate!(Graph(), pg2)
@test g2[ 9:11, :src] == [1,2,3]
@test g2[ 9:11, :tgt] == [4,5,6]
@test g2[12:14, :src] == [4,5,6]
@test g2[12:14, :tgt] == [1,2,3]

d3 = ocompose(barbell(3), [id(OpenCPortGraph, 3), lob(3)])
pg3 = ocompose(d3, [gl,gm,gr])
g3 = migrate!(Graph(), pg3)
@test g3[19:24, :src] == 1:6
@test g3[19:24, :tgt] == [4,5,6,1,2,3]
@test incident(pg3, 5, :box) == 11:14

α₁ = 1
fm = ContinuousMachine{Float64}(4, 1, (u,x,p,t) -> α₁ * (sum(x) .- u .* length(x)), (u,p,t)->collect(repeated(u[1], 4)))
fl = ContinuousMachine{Float64}(3, 1, (u,x,p,t) -> α₁ * (sum(x) .- u .* length(x)), (u,p,t)->collect(repeated(u[1], 3)))
fr = ContinuousMachine{Float64}(3, 1, (u,x,p,t) -> α₁ * (sum(x) .- u .* length(x)), (u,p,t)->collect(repeated(u[1], 3)))
ft = ContinuousMachine{Float64}(3, 1, (u,x,p,t) -> α₁ * (sum(x) .- u .* length(x)), (u,p,t)->collect(repeated(u[1], 3)))
fb = ContinuousMachine{Float64}(3, 1, (u,x,p,t) -> α₁ * (sum(x) .- u .* length(x)), (u,p,t)->collect(repeated(u[1], 3)))
fc = ContinuousMachine{Float64}(2, 1, (u,x,p,t) -> α₁ * (sum(x) .- u .* length(x)), (u,p,t)->collect(repeated(u[1], 2)))

@test eval_dynamics(ft, ones(1), ones(3), nothing, 0.0) == zeros(1)
f₁ = oapply(gl, [fc, fl, fc])
f₂ = oapply(gm, [ft, fm, fb])
f₃ = oapply(gr, [fc, fr, fc])

F = oapply(pg3, [fc, fl, fc, ft, fm, fb, fc, fr, fc])
@test eval_dynamics(F, ones(Float64, 9), [], nothing, 1.0) == zeros(Float64, 9)
u₀ = zeros(Float64, 9)
u₀[5] = 1.0
@test eval_dynamics(F, u₀, [], nothing, 1.0)[5] < 0
@test eval_dynamics(F, u₀, [], nothing, 1.0)[4] == α₁
@test eval_dynamics(F, u₀, [], nothing, 1.0)[2] == α₁
@test eval_dynamics(F, u₀, [], nothing, 1.0)[3] == 0

d4 = @acset OpenCPortGraph begin
    Box = 3
    Port = 18
    Wire = 12
    OuterPort = 6
    box = Iterators.flatten([repeated(i, 6) for i in 1:3])
    con = [1,2,3,16,17,18]
    src = [4,5,6,7,8,9,10,11,12,13,14,15]
    tgt = [7,8,9,4,5,6,13,14,15,10,11,12]
end
pg3 = ocompose(d4, [gm,gm,gm])
draw(pg3)
# pg3[:, :con]

@testset "Laplacians" begin
@test eval_dynamics(oapply(gm, [ft, fm, fb]), ones(3), ones(6), nothing, 1.0) == zeros(3)
@test eval_dynamics(oapply(gm, [ft, fm, fb]), [1,2,1], ones(6), nothing, 1.0) == [1,-4,1]
@test eval_dynamics(oapply(gm, [ft, fm, fb]), [1,2,0], ones(6), nothing, 1.0) == [1,-5, 4]

F = oapply(d4, oapply(gm, [ft,fm,fb]))
@test nstates(F) == 9
@test ninputs(F) == 6
@test eval_dynamics(F, ones(9), ones(6), nothing, 1.0) == zeros(9)
@test eval_dynamics(F, 2*ones(9), 2*ones(6), nothing, 1.0) == zeros(9)

pg4 = ocompose(d4, [pg3, pg3, pg3])
pg5 = ocompose(d4, [pg4, pg4, pg4])
# draw(pg5)

@test (nstates(F),ninputs(F)) == (9,6)
F2 = oapply(d4, [F, F, F])
@test (nstates(F2),ninputs(F2)) == (27,6)
@test eval_dynamics(F2, ones(Float64, 27), ones(Float64, 6), nothing, 0.0) == zeros(27)
F3 = oapply(d4, [F2,F2,F2])
@test (nstates(F3), ninputs(F3)) == (81, 6)
@test eval_dynamics(F3, ones(Float64, 81), ones(Float64, 6), nothing, 0.0) == zeros(81)
end

@testset "Advection-Diffusion" begin

advecdiffuse(α₁, α₂) = begin
    diffop(u,p,t) = α₁ .* (sum(p) .- u .* length(p))
    advop(u,p,t)  = α₂ .* (p[end] .- u)
    ft = ContinuousMachine{Float64}(3, 1, (u,p,q,t) -> diffop(u,p,u) .+ advop(u,p,t), (u,p,t)->collect(repeated(u[1], 3)))
    fm = ContinuousMachine{Float64}(4, 1, (u,p,q,t) -> diffop(u,p,u) .+ advop(u,p,t), (u,p,t)->collect(repeated(u[1], 4)))
    fb = ContinuousMachine{Float64}(3, 1, (u,p,q,t) -> diffop(u,p,u) .+ advop(u,p,t), (u,p,t)->collect(repeated(u[1], 3)))
    return ft, fm, fb
end

ft, fm, fb = advecdiffuse(1.0,2.0)
eval_dynamics(ft, [1.0], [1,1,1.0], 0.0)
F = oapply(d4, oapply(gm, [ft,fm,fb]))
eval_dynamics(F, zeros(9), ones(6), 0.0)
traj = simulate(F, 48, 0.1, zeros(9), vcat(ones(3), zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,3))

F2 = oapply(d4, [F,F,F])
traj = simulate(F2, 16, 0.1, zeros(27), vcat(ones(3), zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,9))

ft, fm, fb = advecdiffuse(1.0,4.0)
F = oapply(d4, oapply(gm, [ft,fm,fb]))
F2 = oapply(d4, [F,F,F])
traj = simulate(F2, 16, 0.1, zeros(27), vcat(ones(3), zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,9))
i=16
@test traj[i][end-2:end][1] == traj[i][end-2:end][2]
@test traj[i][end-2:end][2] == traj[i][end-2:end][3]

traj = simulate(F2, 16, 0.1, zeros(27), vcat([0,1,0], zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,9))
@test traj[i][end-2:end][1] <= traj[i][end-2:end][2]
@test traj[i][end-2:end][2] >= traj[i][end-2:end][3]
end

@testset "Reaction-Diffusion-Advection" begin

RDA(α₀, α₁, α₂) = begin
    diffop(u,p,t) = α₁ .* (sum(p) .- u .* length(p))
    advop(u,p,t)  = α₂ .* (p[end] .- u)
    ft = ContinuousMachine{Float64}(3, 1, (u,p,q,t) -> α₀ .* u .+ diffop(u,p,u) .+ advop(u,p,t), (u,p,t)->collect(repeated(u[1], 3)))
    fm = ContinuousMachine{Float64}(4, 1, (u,p,q,t) -> α₀ .* u .+ diffop(u,p,u) .+ advop(u,p,t), (u,p,t)->collect(repeated(u[1], 4)))
    fb = ContinuousMachine{Float64}(3, 1, (u,p,q,t) -> α₀ .* u .+ diffop(u,p,u) .+ advop(u,p,t), (u,p,t)->collect(repeated(u[1], 3)))
    return ft, fm, fb
end

ft, fm, fb = RDA(0.1, 1.0,2.0)
eval_dynamics(ft, [1.0], [1,1,1.0], nothing, 0.0)
F = oapply(d4, oapply(gm, [ft,fm,fb]))
eval_dynamics(F, zeros(9), ones(6), nothing, 0.0)
traj = simulate(F, 48, 0.1, zeros(9), vcat(ones(3), zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,3))

F2 = oapply(d4, [F,F,F])
traj = simulate(F2, 16, 0.1, zeros(27), vcat(ones(3), zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,9))

ft, fm, fb = RDA(0.1, 1.0,4.0)
F = oapply(d4, oapply(gm, [ft,fm,fb]))
F2 = oapply(d4, [F,F,F])
traj = simulate(F2, 16, 0.1, zeros(27), vcat(ones(3), zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,9))
i=16
@test traj[i][end-2:end][1] ≈ traj[i][end-2:end][2] atol=1e-3
@test traj[i][end-2:end][2] ≈ traj[i][end-2:end][3] atol=1e-3

traj = simulate(F2, 64, 0.1, zeros(27), vcat([0,1,0], zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,9))
@test traj[i][end-2:end][1] <= traj[i][end-2:end][2]
@test traj[i][end-2:end][2] >= traj[i][end-2:end][3]

ft, fm, fb = RDA(-0.4, 1.0,4.0)
F = oapply(d4, oapply(gm, [ft,fm,fb]))
F2 = oapply(d4, [F,F,F])
traj = simulate(F2, 128, 0.1, zeros(27), vcat([0,1,0], zeros(3)))
# printsim(traj, t->t[end-2:end], identity, (3,9))
end

@testset "Grids" begin
function testgridsize(n,m)
    g = grid(n,m)
    @test nparts(g, :Box) == n*m
    @test nparts(g, :Port) == 6n + 4n*(m-2)
    @test nparts(g, :Wire) == 2*((n-1)*m + (m-1)*n)
end
for (i,j) in Iterators.product(1:6, 2:4)
    testgridsize(i,j)
end
end

@testset "DelayMachine" begin
# ross macdonald
c = @acset OpenCPortGraph begin
    Box = 2
    Port = 2
    Wire = 2
    OuterPort = 0
    box = [1,2]
    src = [1,2]
    tgt = [2,1]
    con = []
end

dzdt_delay = function(u,x,h,p,t)
    Y, Z = u
    Y_delay, Z_delay = h(p, t - p.n)
    X, X_delay = x[1]

    [p.a*p.c*X*(1 - Y - Z) -
        p.a*p.c*X_delay*(1 - Y_delay - Z_delay)*exp(-p.g*p.n) -
        p.g*Y,
    p.a*p.c*X_delay*(1 - Y_delay - Z_delay)*exp(-p.g*p.n) -
        p.g*Z]
end

dxdt_delay = function(u,x,h,p,t)
    X, = u
    Z, _ = x[1]
    [p.m*p.a*p.b*Z*(1 - X) - p.r*X]
end

#-

1 + 1
mosquito_delay_model = DelayMachine{Float64, 2}(
    1, 2, 1, dzdt_delay, (u,h,p,t) -> [[u[2], h(p,t - p.n)[2]]])

human_delay_model = DelayMachine{Float64, 2}(
    1, 1, 1, dxdt_delay, (u,h,p,t) -> [[u[1], h(p, t - p.n)[1]]])
rm_model = oapply(c, [mosquito_delay_model, human_delay_model])

params = LVector(a = 0.3, b = 0.55, c = 0.15,
    g = 0.1, n = 10, r = 1.0/200, m = 0.5)

u0_delay = [0.09, .01, 0.3]
tspan = (0.0, 365.0*5)
hist(p,t) = u0_delay;

prob = DDEProblem(rm_model, u0_delay, [], hist, tspan, params)
alg = MethodOfSteps(Tsit5())
sol = solve(prob, alg)
a, b, c, g, n, r, m = params
R0 = (m*a^2*b*c*exp(-g*n))/(r*g)
@test isapprox(last(sol)[3], (R0 - 1)/(R0 + (a*c)/g), atol = 1e-3)
end


c=0
for n=1:100, k=1:n
  global c
  est = try
    binomial(n,k)
  catch
    2e6
  end
  if est > 1e6
    c+=1
  end
end
println(c)


using BasisFunctions, LinearAlgebra, DomainSets, GridArrays, Test, StaticArrays, FrameFun

@testset begin
    B = (Fourier(11) → -1..1)^2
    Dom = Disk(0.8)
    @test support(dictionary(∂x(random_expansion(extensionframe(B, Dom)))))≈Dom

    # @test SamplingStyle(ExtensionFramePlatform(FrameFun.ProductPlatform(FourierPlatform(),FourierPlatform()),(0.0..0.5)^2)) ==
    #     ProductSamplingStyle(OversamplingStyle(),OversamplingStyle())

    @test dictionary(_ap(Fourier(100),0.0..0.5)) == extensionframe(Fourier(100),0.0..0.5)
    # Test defaults
    @test AZ_A(Fourier(100),0.0..0.5; samplingstyle=OversamplingStyle(),oversamplingfactor=2,normalizedsampling=false)≈
        AZ_A(Fourier(100),0.0..0.5)

    ap0 = _ap(Fourier(100), 0.0..0.5; samplingstyle=OversamplingStyle(), oversamplingfactor=2, normalizedsampling=false)
    A = AZ_A(ap0)
    L = samplingparameter(ap0)
    a1 = evaluation(Fourier(L),interpolation_grid(Fourier(L)))
    a2 = IndexRestriction(GridBasis(interpolation_grid(Fourier(L))),1:200)
    a3 = BasisFunctions.FourierIndexExtension(Fourier(100),Fourier(L))
    @test A≈a2*a1*a3

    ap1 = approximationproblem(Fourier(100), 0.0..0.5; samplingstyle=OversamplingStyle(), oversamplingfactor=2, normalizedsampling=true)
    A = AZ_A(ap1)
    L = samplingparameter(ap1)
    a1 = evaluation(Fourier(L),interpolation_grid(Fourier(L)))
    a2 = IndexRestriction(GridBasis(interpolation_grid(Fourier(L))),1:200)
    a3 = BasisFunctions.FourierIndexExtension(Fourier(100),Fourier(L))
    a4 = ScalingOperator(dest(a2),1/sqrt(L))
    @test A≈a4*a2*a1*a3

    # Test differnence sampling_normalization
    ap2 = approximationproblem(Fourier(100),0.0..0.5;
            samplingstyle=OversamplingStyle(), oversamplingfactor=2, normalizedsampling=false)
    s = svdvals(AZ_A(ap2))
    N = sampling_weights(FrameFun.NormalizedSampling(OversamplingStyle()),approximationproblem(Fourier(100),0.0..0.5))
    L = samplingparameter(ap2)
    @test L == 399
    @test N[1] ≈ 1/sqrt(L)
    @test 47==sum(s.<.1*sqrt(L))
    @test 49==sum(s.>.9*sqrt(L))

    s = svdvals(AZ_A(Fourier(100),0.0..0.5;oversamplingfactor=2,normalizedsampling=true))
    @test 47==sum(s.<.1)
    @test 49==sum(1+1e-10 .> s .>.9)

    # right side should be appropriately normalized
    @test sample_data(exp, Fourier(100), 0.0..0.5; oversamplingfactor=2,normalizedsampling=true)≈
        1/sqrt(399)*GridSampling(subgrid(interpolation_grid(Fourier(399)),0.0..0.5))*exp

    # left side should be appropriately normalized
    @test AZ_A(Fourier(100),0.0..0.5;samplingstyle=OversamplingStyle(),oversamplingfactor=2,normalizedsampling=true)≈
        discretization(exp, Fourier(100), 0.0..0.5; oversamplingfactor=2,normalizedsampling=true)

    @test sample_data(exp, Fourier(100),0.0..0.5; oversamplingfactor=2,normalizedsampling=false)≈
        GridSampling(subgrid(interpolation_grid(Fourier(399)),0.0..0.5))*exp

    @test AZ_A(Fourier(100), 0.0..0.5; samplingstyle=OversamplingStyle(),oversamplingfactor=2,normalizedsampling=false)≈
        discretization(exp, Fourier(100), 0.0..0.5; oversamplingfactor=2, normalizedsampling=false)

    F = approximate(exp, Fourier(100), 0.0..0.5; samplingstyle=OversamplingStyle(),oversamplingfactor=2,normalizedsampling=true)
    @test abs(F[1](.2)-exp(.2) )< 1e-12

    F = approximate(exp, Fourier(100), 0.0..0.5; samplingstyle=OversamplingStyle(),oversamplingfactor=2,normalizedsampling=false)
    @test abs(F[1](.2)-exp(.2) )< 1e-12

    P = WeightedSumPlatform(FourierPlatform(),x->1,x->sqrt(x))
    ap = approximationproblem(P,(10,10))
    azdual(ap)
end


"""
    slippage!(slipx, slipy, height, velx, vely, δ, μ)

Fluid substrate interaction that effectively mimics a velocity boundary condition at ``h=0``.

# Arguments

- `slipx :: Array{<:Number,2}`: The x-component of the force due the velocity boundary condition
- `slipy :: Array{<:Number,2}`: The y-component of the force due the velocity boundary condition
- `height::Array{<:Number,2}`: Height field `` h(\\mathbf{x},t)``
- `velx::Array{<:Number,2}`: x-component of the macroscopic velocity vector
- `vely::Array{<:Number,2}`: y-component of the macroscopic velocity vector
- `δ <: Number`: Extrapolation length into the substrate where the **no-slip** is met
- `μ <: Number`: Kinematic viscosity of the simulation, dependent on the value of **τ**

# Mathematics

With the velocity boundary condition at the fluid substrate interface we build the second main descriptor between our model and the thin film equation.
One well studied assumption is that the fluid velocity vanishes at `` h(\\mathbf{x}) = 0 `` which is called **no slip** condition.

TBD

# Examples

TBD

# References

- [Zitz, Scagliarini and Harting](https://journals.aps.org/pre/abstract/10.1103/PhysRevE.100.033313)
- [Münch, Wagner und Witelski](https://link.springer.com/article/10.1007/s10665-005-9020-3)
- [Oron, Davis and Bankoff](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.69.931)
"""
function slippage!(slipx, slipy, height, velx, vely, δ, μ)
    slipx .= (6μ .* height .* velx) ./ (2 .* height.^2 .+ 6δ .* height .+ 3δ^2 )
    slipy .= (6μ .* height .* vely) ./ (2 .* height.^2 .+ 6δ .* height .+ 3δ^2 )
    return nothing
end

function slippage!(slip, height, vel, δ, μ)
    slip .= (6μ .* height .* vel) ./ (2 .* height.^2 .+ 6δ .* height .+ 3δ^2 )
    return nothing
end

"""
    thermal!(fx, fy, height, kᵦT, μ, δ)

Computations of force due to thermal fluctuations.

# Arguments

- `fx :: Array{<:Number,2}`: x-component of the force due to the fluctuations
- `fy :: Array{<:Number,2}`: y-component of the force due to the fluctuations
- `height :: Array{<:Number,2}`: Height field ``h(\\mathbf{x},t) ``
- `kᵦT <: Number`: Strenght of thermal fluctuations in lattice units
- `μ <: Number`: The kinetic viscosity
- `δ <: Number`: Slip length, needed to normalize

# Mathematics

TBD

# Examples
```jldoctest
julia> using Swalbe, Statistics, Test

julia> x = ones(50,50); y = ones(50,50); h = ones(50,50);

julia> Swalbe.thermal!(x, y, h, 0.1, 1/6, 1)

julia> @test mean(x) ≈ 0.0 atol=1e-2
Test Passed

julia> @test mean(y) ≈ 0.0 atol=1e-2
Test Passed

julia> @test var(x) ≈ 2*0.1/11 atol=(2*0.1/11)/10 # var = 2kbt*6*μ/slip
Test Passed

```

# References

- [Grün, Mecke and Rauscher](https://link.springer.com/article/10.1007/s10955-006-9028-8)
- [Mecke, Rauscher](https://iopscience.iop.org/article/10.1088/0953-8984/17/45/042/meta)
- [Davidovitch, Moro and Stone](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.95.244505)

"""
function thermal!(fluc_x, fluc_y, height, kᵦT, μ, δ)
    randn!(fluc_x)
    randn!(fluc_y)
    fluc_x .*= sqrt.(2 .* kᵦT .* μ .* 6 .* height ./
                    (2 .* height.^2 .+
                     6 .* height .* δ .+
                     3 .* δ^2))
    fluc_y .*= sqrt.(2 .* kᵦT .* μ .* 6 .* height ./
                    (2 .* height.^2 .+
                     6 .* height .* δ .+
                     3 .* δ^2))
    return nothing
end

function thermal!(fluc, height, kᵦT, μ, δ)
    randn!(fluc)
    fluc .*= sqrt.(2 .* kᵦT .* μ .* 6 .* height ./
                  (2 .* height.^2 .+
                   6 .* height .* δ .+
                   3 .* δ^2))

    return nothing
end

"""
    update_rho()

Time evolution of the `active` field rho.
"""
function update_rho!(rho, rho_int, height, dgrad, differentials; D=1.0, M=0.0)
    lap_rho, grad_rho, lap_h, grad_h = view_four(differentials)
    ∇²f!(lap_rho, rho, dgrad)
    ∇f!(grad_rho, rho, dgrad)
    ∇²f!(lap_h, height, dgrad)
    ∇f!(grad_h, height, dgrad)
    rho_int .= (D .* lap_rho .-
               M .* (grad_rho.^2 .+ rho .* lap_rho) .-
               D .* (grad_rho .* grad_h ./ height + rho .* (lap_h ./ height .- (grad_h ./ height).^2 )))

    rho .+= rho_int

    return nothing
end

"""
    view_four()

Splits a chuck of memory in four equivalent chucks
"""
function view_four(f)
    f1 = view(f, :, 1)
    f2 = view(f, :, 2)
    f3 = view(f, :, 3)
    f4 = view(f, :, 4)

    return f1, f2, f3, f4
end


using JuMP
using Cbc
using Dates

function readData()
    arr = Array{Int, 2}(undef, 0,0 )
    if isfile("instances/temp.txt")
        myFile = open("instances/temp.txt")
        data = readlines(myFile)
        n = length(data)
        arr = fill(0,(n - 3, n -10))

        i = 0
        for line in data
            if line == ""
                continue
            end
            i += 1
            var = split(line, " ")
            for j in 1:length(var)
                arr[i, j] = parse(Int, var[j])
            end
        end
    end
    return arr
end

function square(n::Int64)
    return n*n
end

function solve_TSP(d::Array{Int, 2})
    numOfAero = d[1,1]#number of aerodromes
    start = d[2,1]
    endP = d[3,1]
    numOfMust = d[4,1]#number of points must be visited
    numOfRegion = d[5,1]#number of regions
    maxDistance = d[7,1]#max distance travel without landing

    tempstart = start
    tempend = endP

    lines = size(d, 1) #return the number of lines of array d

    # let startPoint at the first, lastPoint at the last
    for j in 1:2
        temp = d[start+7,j]
        d[start+7,j] = d[8,j]
        d[8,j] = temp
        temp = d[endP+7,j]
        d[endP+7,j] = d[numOfAero+7,j]
        d[numOfAero+7,j] = temp
    end
    #n -> the size of the coordinate matrix
    n = lines - 7

    regOfEach = Array{Int}(zeros(n))#an array used to store the information of region for each aerodrome
    for j in 1:n
        regOfEach[j] = d[6,j]
    end

    #because of the swap between the first point and the end point, the position of the region array need to be changed correspondingly
    temp = regOfEach[1] #temp is used to store the value needed to be swapped
    regOfEach[1] = regOfEach[start]
    regOfEach[start] = temp

    temp = regOfEach[numOfAero]
    regOfEach[numOfAero] = regOfEach[endP]
    regOfEach[endP] = temp

    start = 1#reset the start and end point
    endP = numOfAero

    disArr = Array{Int, 2}(undef, n, n)#an array used to store the information of the distance between each point
    for i in 1:n
        for j in 1:n
            disArr[i, j] = round(sqrt(square(d[i+7,1]-d[j+7,1]) + square(d[i+7,2]-d[j+7,2])))
        end
    end

    Model
    m = Model(Cbc.Optimizer)
    set_optimizer_attribute(m, "threads", 16)

    #Variables
    @variable(m, x[i in 1:n, j in 1:n], Bin)
    @variable(m, visited[i in 1:n], Bin)
    @variable(m, u[j in 1:n] >= 0)

    #Objective function
    @objective(m, Min, sum(disArr[i,j]*x[i,j] for i in 1:n, j in 1:n))

    #Constraints for preventing self-loop
    @constraint(m, loopsForbidden[i in 1:n], x[i,i] == 0)

    #start from startPoint
    @constraint(m, fromStartOut, sum(x[start, j] for j in 1:n) == 1)
    @constraint(m, fromStartIn, sum(x[i, start] for i in 1:n) == 0)
    @constraint(m, visited[1] == 1)

    #end at endPoint
    @constraint(m, toEndOut, sum(x[endP, j] for j in 1:n) == 0)
    @constraint(m, toEndIn, sum(x[i, endP] for i in 1:n) == 1)
    @constraint(m, visited[numOfAero] == 1)

    #number of point must be visited
    @constraint(m, sum(x[i,j] for i in 1:n, j in 1:n) >= numOfMust-1)

    #the max distance travel without landing
    @constraint(m, maxDis[i in 1:n, j in 1:n], x[i,j]*disArr[i,j] <= maxDistance)

    #constraint for middle points
    #each middle point could only have one out-degree
    @constraint(m, noMoreThanOne1[i in 2:n-1], sum(x[i,j] for j in 2:n) <= 1)
    #each middle point could only have one in-degree
    @constraint(m, noMoreThanOne2[j in 2:n-1], sum(x[i,j] for i in 1:n-1) <= 1)
    #build the relation between variable x and visited, and ensure that in-degree == out-degree
    @constraint(m, middlePoint1[i in 2:n-1], sum(x[i,j] for j in 1:n) == visited[i])
    @constraint(m, middlePoint2[j in 2:n-1], sum(x[i,j] for i in 1:n) == visited[j])

    #region constraint that ensure that each region must be visited once
    @constraint(m, [i in 2:n-1], visited[i] == sum(x[i,j] for j in 1:n))
    @constraint(m, region1[i in 1:numOfRegion], sum(visited[j] for j in 1:n if regOfEach[j] == i) >= 1)

    #MTZ
    @constraint(m, mtz[i in 1:n, j in 2:n], u[j] >= u[i]+1-n+n*x[i,j]+(n-2)*x[j,i])
    @constraint(m, mtz2[j in 1:n], u[j] <= n)
    @constraint(m, mtz3[j in 1:n], u[j] >= 1)

    #Solve the problem
    set_silent(m)
    optimize!(m)

    # return to origin for the proposed outcome
    for i in 1:n
        temp = x[i,tempstart]
        x[i,tempstart] = x[i,start]
        x[i,start] = temp
        temp = x[tempstart,i]
        x[tempstart,i] = x[start,i]
        x[start,i] = temp
        temp = x[i,tempend]
        x[i,tempend] = x[i,endP]
        x[i,endP] = temp
        temp = x[tempend,i]
        x[tempend,i] = x[endP,i]
        x[endP,i] = temp
    end
    println("Result - Optimal solution found")
    println("")
    println(" Distance travelled = ", JuMP.objective_value(m))
    print("Edges used in the solutin :")
    for i in 1:n
        for j in 1:n
            valij =  JuMP.value(x[i,j])
            if valij <= 1.000001 && valij >= 0.999999
                print(i, "->", j, " ")
            end
        end
    end
    println()

end

d1=now(Dates.UTC)
D = readData()
solve_TSP(D)
d2=now(Dates.UTC)
println(d2-d1)


using Slomo: SersicModel, IsotropicModel, ConstantBetaModel, RSBetaModel
using Slomo.Models: density, density2d, mass, potential, beta, g_jeans, K_jeans
using Slomo.Integrate: integrate

using Test

rtol = 1e-5

# sersic model
for model in [SersicModel(), SersicModel(1.0, 2.0), SersicModel(1.0, 2.0, 3.0)]
    Mtot = model.Mtot
    Re = model.Re
    integrand3d(x) = 4π * x^2 * density(model, x)
    integrand2d(x) = 2π * x * density2d(model, x)
    @test isapprox(Mtot, 2.0 * integrate(integrand2d, Re * rtol, Re), rtol = rtol)
    @test isapprox(Mtot, integrate(integrand2d, Re * rtol, Re / rtol), rtol = rtol)
    @test isapprox(Mtot, integrate(integrand3d, Re * rtol, Re / rtol), rtol = rtol)
    for R in [1, 1.0, [1], [1.0], [1.0, 2.0, 3.0]]
        @test size(density(model, R)) == size(R)
        @test size(density2d(model, R)) == size(R)
        @test size(mass(model, R)) == size(R)
        @test size(potential(model, R)) == size(R)
    end
end

# isotropic model
model = IsotropicModel()
for r in [1, 1.0, [1], [1.0], [1.0, 2.0, 3.0]]
    R = minimum(r) / 2.0
    @test beta(model, r) == zeros(size(r))
    @test size(beta(model, r)) == size(r)
    @test size(g_jeans(model, r)) == size(r)
    @test size(K_jeans(model, r, R)) == size(r)
end

# constant beta model
for model in [ConstantBetaModel(-9.1), ConstantBetaModel(-0.5), ConstantBetaModel(),
              ConstantBetaModel(0), ConstantBetaModel(0.5), ConstantBetaModel(0.96)]
    for r in [1, 1.0, [1], [1.0], [1.0, 2.0, 3.0]]
        R = minimum(r) / 2.0
        if isa(r, AbstractArray)
            β = beta(model, r)
            @test all(β .== β[1])
        end
        @test size(beta(model, r)) == size(r)
        @test size(g_jeans(model, r)) == size(r)
        @test size(K_jeans(model, r, R)) == size(r)
    end
end

# RS beta model
model = RSBetaModel()
for r in [1, 1.0, [1], [1.0], [1.0, 2.0, 3.0]]
    @test size(beta(model, r)) == size(r)
    @test size(g_jeans(model, r)) == size(r)
end


using Yao
using Yao.Intrinsics
using LuxurySparse
using SparseArrays
using StaticArrays
using LinearAlgebra
using Yao.Blocks
using Test

using BenchmarkTools

println("##### Matrix #####")
nbit = 16
mmm = randn(ComplexF64, 2, 2)
println("u1mat, t ≈ 600us")
display(@benchmark u1mat(nbit, $mmm, 2))

println("\nunmat, t ≈ 1.1ms")
display(@benchmark unmat(nbit, $mmm, (2,)))
@test u1mat(nbit, mmm, 2) == unmat(nbit, mmm, (2,))

println("\n##### SparseMatrixCSC #####")
nbit = 16
mmm = mat(P1)
println("unmat, t ≈ 1.1ms")
display(@benchmark unmat(nbit, $mmm, (2,)))

println("\n######## PermMatrix #############")
nbit = 16
mmm = X |> mat
println("unmat, t ≈ 300us")
display(@benchmark unmat(nbit, $mmm, (2,)))

println("\n######## Diagonal #############")
nbit = 16
mmm = Z |> mat
println("unmat, t ≈ 400us")
display(@benchmark unmat(nbit, $mmm, (2,)))


### Somatório de idade
# Uma criança quer saber qual é a soma de todas as idades que ela já teve.
# Elaborar programa que lê  uma idade qualquer e
# responde rapidamente a essa pergunta
# ( fórmula para calcular a soma dos N primeiros números inteiros: N (N+1)/2 ).

print("inform a age ")
age = parse(UInt8, readline())
age = age * ((age+1)/2)

print(UInt8(age),"\n")


using LinearAlgebra
using StaticArrays
using OffsetArrays
using Plots
using StiffKinetic
using Printf

Nx = 2^8 + 1
x  = range(0.0, 4.0, length=Nx)
dx = step(x)

uL, uR = 4.0, 2.0
U0 = uL*Float64.(0.0 .<= x .<= 2.0) + uR*Float64.(2.0 .< x .<= 4.0)
V0 = zero(U0)

p = u -> u
f = u -> u # /!\ we may use |f'| = 1 in the schemes

pb = RelaxPb(U0,V0,f,p)
pbs = [pb]

ε = 2^-1

T = 0.25
dt = (2^-4) * dx^2

dt_anim = 2^-6 * T
nt_anim = Int(T/dt_anim)

nt_pf = Int(dt_anim/dt)

anim = @animate for t in 0 : dt_anim : T
    str_t = @sprintf "t = %1.3f" t
    plot(x, pbs[1].U.data, title = str_t, ylims=(1.8, 4.2))
    # plot!(x, pbs[1].V.data, title = "t = $t", ylims=(-5, 4.2))
    for _ in 1 : nt_pf
        imex_bdf4!(pbs, dt, dx, ε)
    end
end

gif(anim, "riemann.gif", fps=24)


# Semantics for abstract values #

"""
    lub(a, b)

Compute the least upper bound (i.e. join) of abstract values `a` and `b`.
"""
function lub end

"""
    glb(a, b)

Compute the greatest lower bound (i.e. meet) of abstract values `a` and `b`.
"""
function glb end

"""
    widen(a, b)

Compute the widening of abstract values `a` and `b`.
"""
function widen end

include("boolean.jl")
include("interval.jl")

const DEFAULT_ABSTRACTIONS = Dict(
    :boolean => BooleanAbs,
    :integer => IntervalAbs{Int},
    :numeric => IntervalAbs{Float64}
)


using LsqFit

@testset "qpdf for GaussianStateBHD" begin
    r, θ, dim = 0.8, 2π, 100
    ρ = SqueezedState(r, θ, Matrix, dim=dim)
    d = GaussianStateBHD(ρ)

    m = 10
    n = 1000
    θs = LinRange(0, 2π, m)
    xs = LinRange(-3, 3, n)

    g_pdf = Vector{Float64}(undef, n)

    for i in 1:n
        g_pdf[i] = qpdf(d, π/3, xs[i])
    end

    g_pdfs = qpdf(d, θs, xs)

    moments = [QuantumStateDistributions.mean(d, π/3), QuantumStateDistributions.std(d, π/3)]
    @. model(x, p) = 1 / (p[2] * √(2π)) * exp(-(x - p[1])^2 / (2 * p[2]^2))
    p0 = [0.5, 0.5]

    g_fit = curve_fit(model, xs, g_pdf, p0)
    @test coef(g_fit) ≈ moments

    for i in 1:m
        g_fits = curve_fit(model, xs, g_pdfs[i, :], p0)
        moments = [QuantumStateDistributions.mean(d, θs[i]), QuantumStateDistributions.std(d, θs[i])]

        @test coef(g_fits) ≈ moments
    end
end

@testset "qpdf for QuantumStateBHD" begin
    r, θ, dim = 0.8, 2π, 100
    ρ = SqueezedState(r, θ, Matrix, dim=dim)
    q = QuantumStateBHD(ρ)
    d = GaussianStateBHD(ρ)

    m = 10
    n = 1000
    θs = LinRange(0, 2π, m)
    xs = LinRange(-3, 3, n)

    q_pdf = Vector{Float64}(undef, n)

    for i in 1:n
        q_pdf[i] = qpdf(q, π/3, xs[i])
    end

    q_pdfs = qpdf(q, θs, xs)

    moments = [QuantumStateDistributions.mean(d, π/3), QuantumStateDistributions.std(d, π/3)]
    @. model(x, p) = 1 / (p[2] * √(2π)) * exp(-(x - p[1])^2 / (2 * p[2]^2))
    p0 = [0.5, 0.5]

    q_fit = curve_fit(model, xs, q_pdf, p0)
    @test coef(q_fit) ≈ moments

    for i in 1:m
        q_fits = curve_fit(model, xs, q_pdfs[i, :], p0)
        moments = [QuantumStateDistributions.mean(d, θs[i]), QuantumStateDistributions.std(d, θs[i])]

        @test coef(q_fits) ≈ moments
    end
end

@testset "pdf for density matrix" begin
    r, θ, dim = 0.8, 2π, 100
    ρ = SqueezedState(r, θ, Matrix, dim=dim)
    d = GaussianStateBHD(ρ)

    m = 10
    n = 1000
    θs = LinRange(0, 2π, m)
    xs = LinRange(-3, 3, n)

    ρ_pdf = Vector{Float64}(undef, n)

    for i in 1:n
        ρ_pdf[i] = qpdf(ρ, π/3, xs[i])
    end

    ρ_pdfs = qpdf(ρ, θs, xs)

    moments = [QuantumStateDistributions.mean(d, π/3), QuantumStateDistributions.std(d, π/3)]
    @. model(x, p) = 1 / (p[2] * √(2π)) * exp(-(x - p[1])^2 / (2 * p[2]^2))
    p0 = [0.5, 0.5]

    ρ_fit = curve_fit(model, xs, ρ_pdf, p0)
    @test coef(ρ_fit) ≈ moments

    for i in 1:m
        ρ_fits = curve_fit(model, xs, ρ_pdfs[i, :], p0)
        moments = [QuantumStateDistributions.mean(d, θs[i]), QuantumStateDistributions.std(d, θs[i])]

        @test coef(ρ_fits) ≈ moments
    end
end


function DAMP(ALPHA)
      A=[-0.2670    0.8820   -0.1080   -8.8000   -0.1260   -0.3600   -7.2100   -0.3800    0.0610
         -0.1100    0.8520   -0.1080  -25.8000   -0.0260   -0.3590   -0.5400   -0.3630    0.0520
          0.3080    0.8760   -0.1880  -28.9000    0.0630   -0.4430   -5.2300   -0.3780    0.0520
          1.3400    0.9580    0.1100  -31.4000    0.1130   -0.4200   -5.2600   -0.3860   -0.0120
          2.0800    0.9620    0.2580  -31.2000    0.2080   -0.3830   -6.1100   -0.3700   -0.0130
          2.9100    0.9740    0.2260  -30.7000    0.2300   -0.3750   -6.6400   -0.4530   -0.0240
          2.7600    0.8190    0.3440  -27.7000    0.3190   -0.3290   -5.6900   -0.5500    0.0500
          2.0500    0.4830    0.3620  -28.2000    0.4370   -0.2940   -6.0000   -0.5820    0.1500
          1.5000    0.5900    0.6110  -29.0000    0.6800   -0.2300   -6.2000   -0.5950    0.1300
          1.4900    1.2100    0.5290  -29.8000    0.1000   -0.2100   -6.4000   -0.6370    0.1580
          1.8300   -0.4930    0.2980  -38.3000    0.4470   -0.1200   -6.6000   -1.0200    0.2400
          1.2100   -1.0400   -2.2700  -35.3000   -0.3300   -0.1000   -6.0000   -0.8400    0.1500]
      #
      S= 0.2 * ALPHA
      K= floor(S)+3
      if(K <= 1)
            K= 2
      end
      if(K >=  12)
            K=  11
      end
      DA= S - K +3
      L = K + fix( 1.1*sign(DA) )
      #
      D=zeros(9,1)
      K=convert(Int64, K)
      L=convert(Int64, L)
      for I= 1:9
            D[I]= A[K,I] + abs(DA) * (A[L,I] - A[K,I])
      end
      return D
end


include("attributes.jl")

function PMM1_leader(x, y)
    k = min(length(x), length(y)) ÷ 2

    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]

    q1 = sum( (y1 - x1.^3/100).^2 )
    q2 = 10 + y[k+1] + 1e6sum( y[k+2:end].^2 )

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)

    p1 = q1 + r1
    p2 = q2 + r2
    F = abs(p1) + abs(p2)

    F, zeros(0), zeros(0)
end

function PMM1_follower(x, y)
    k = min(length(x), length(y)) ÷ 2

    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]

    q1 = sum( (y1 - x1.^3/100).^2 )
    q2 = 10 + y[k+1] + 1e6sum( y[k+2:end].^2 )

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)

    p1 = q1 + r1
    p2 = q2 + r2

    f = p1 + p2

    f, zeros(0), zeros(0)
end

function PMM2_leader(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    q1 = (y[1]- x[1] *sin(x[1]) ).^2 + 1e6sum((y[2:k]- x[2:k] .* sin.(x[2:k]) ).^2)

    m = k+1:length(y)
    v = (y[m] - sqrt.(m))
    q2 = sum(v.^2) + sum(0.5v)^2 + sum(0.5v)^4

    x1 = x[1:k]; x2 = x[k+1:end];
    r1 = -sum(x1.^2)
    r2 = -x2[1]^2 - 1e6sum(x2[2:end].^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    F = abs(p1) + abs(p2)

    F, zeros(0), zeros(0)
end

function PMM2_follower(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    q1 = (y[1]- x[1] *sin(x[1]) ).^2 + 1e6sum((y[2:k]- x[2:k] .* sin.(x[2:k]) ).^2)

    m = k+1:length(y)
    v = (y[m] - sqrt.(m))
    q2 = sum(v.^2) + sum(0.5v)^2 + sum(0.5v)^4

    x1 = x[1:k]; x2 = x[k+1:end];
    r1 = -sum(x1.^2)
    r2 = -x2[1]^2 - 1e6sum(x2[2:end].^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    f = p1 + p2

    f, zeros(0), zeros(0)
end

function PMM3_leader(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = sum( (y1 - x1.^3/100).^2 )
    q2 = sum( 100(y2[1:end-1].^2 - y2[2:end]).^2 + (y2[1:end-1] .- 1).^2 )

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    F = abs(p1) + abs(p2)

    F, zeros(0), zeros(0)
end

function PMM3_follower(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = sum( (y1 - x1.^3/100).^2 )
    q2 = sum( 100(y2[1:end-1].^2 - y2[2:end]).^2 + (y2[1:end-1] .- 1).^2 )

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    f = p1 + p2

    f, zeros(0), zeros(0)
end

function PMM4_leader(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = sum( (y1 - 10 ./ (1 .+ 2.5*x1.^2)).^2 )
    q2 = 10length(y2) + sum(y2.^2 - 10cos.(2π*y2))

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    F = abs(p1) + abs(p2)

    F, zeros(0), zeros(0)
end

function PMM4_follower(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = sum( (y1 - 10 ./ (1 .+ 2.5*x1.^2)).^2 )
    q2 = 10length(y2) + sum(y2.^2 - 10cos.(2π*y2))

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    f = p1 + p2

    f, zeros(0), zeros(0)
end

function PMM5_leader(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = 10k + sum( (y1-x1).^2 - 10cos.(2π*abs.(y1-x1)))
    q2 = sum( abs.(y2).^(2:length(y2)+1) )

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    F = abs(p1) + abs(p2)

    F, zeros(0), zeros(0)
end

function PMM5_follower(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = 10k + sum( (y1-x1).^2 - 10cos.(2π*abs.(y1-x1)))
    q2 = sum( abs.(y2).^(2:length(y2)+1) )

    r1 = -sum(x1.^2)
    r2 = -sum(x2.^2)
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    f = p1 + p2

    f, zeros(0), zeros(0)
end

function PMM6_leader(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = 1 + (1/4)*sum((y1 - x1).^2) - prod(cos.(10(y1 - x1) / sqrt.(1:k)))
    q2 = 10length(y2) + sum(y2.^2 - 10cos.(2π*y2))

    r1 = -sum(x1.^2)
    r2 = -10length(x2) - sum(x2.^2 - 10cos.(2π*x2))
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    F = abs(p1) + abs(p2)

    F, zeros(0), zeros(0)
end

function PMM6_follower(x, y)
    k = min(length(x), length(y)) ÷ 2

    ###########################
    x1 = x[1:k]; x2 = x[k+1:end];
    y1 = y[1:k]; y2 = y[k+1:end];

    q1 = 1 + (1/4)*sum((y1 - x1).^2) - prod(cos.(10(y1 - x1) / sqrt.(1:k)))
    q2 = 10length(y2) + sum(y2.^2 - 10cos.(2π*y2))

    r1 = -sum(x1.^2)
    r2 = -10length(x2) - sum(x2.^2 - 10cos.(2π*x2))
    ###########################

    p1 = q1 + r1
    p2 = q2 + r2

    f = p1 + p2

    f, zeros(0), zeros(0)
end

function PMM1_optimum(uldim, lldim)
    k = min(uldim, lldim) ÷ 2
    x = zeros(uldim)
    y = zeros(lldim)
    y[k+1] = -10
    return x, y
end

function PMM2_optimum(uldim, lldim)
    k = min(uldim, lldim) ÷ 2
    x = zeros(uldim)
    y = zeros(lldim)
    y[k+1:length(y)] = sqrt.(k+1:length(y))
    return x, y
end

function PMM3_optimum(uldim, lldim)
    k = min(uldim, lldim) ÷ 2
    x = zeros(uldim)
    y = zeros(lldim)
    y[k+1:length(y)] .= 1
    return x, y
end

function PMM4_optimum(uldim, lldim)
    k = min(uldim, lldim) ÷ 2
    x = zeros(uldim)
    y = zeros(lldim)
    y[1:k] .= 10
    return x, y
end

function PMM5_optimum(uldim, lldim)
    k = min(uldim, lldim) ÷ 2
    x = zeros(uldim)
    y = zeros(lldim)
    return x, y
end

function PMM6_optimum(uldim, lldim)
    k = min(uldim, lldim) ÷ 2
    x = zeros(uldim)
    y = zeros(lldim)
    return x, y
end

#=
const PMM6_optimum = PMM_optimum
PMM1_optimum = PMM2_optimum = PMM3_optimum = PMM4_optimum = PMM5_optimum = PMM6_optimum
=#


# NOTE: Bolt's background functions are in terms of x ≡ ln(a), the log scale factor

# derived quantities (I've chosen natural units, possibly the wrong choice)
const km_s_Mpc_100 = ustrip(natural(100.0u"km/s/Mpc"))  # [eV]
const G_natural = ustrip(natural(float(NewtonianConstantOfGravitation))) #[eV^-2]
const ζ = 1.2020569 #Riemann ζ(3) for phase space integrals
#In the natural units used here, ħ=kb=c=1, G as above

H₀(par::AbstractCosmoParams) = par.h * km_s_Mpc_100
ρ_crit(par::AbstractCosmoParams) = (3 / 8π) * H₀(par)^2 / G_natural  # [eV⁴]
function Ω_Λ(par::AbstractCosmoParams)
    #Below can definitely be more streamlined, I am just making it work for now
    Tγ = (15/ π^2 *ρ_crit(par) *par.Ω_r)^(1/4)
    νfac = (90 * ζ /(11 * π^4)) * (par.Ω_r * par.h^2 / Tγ) *((par.N_ν/3)^(3/4))
    #^the factor that goes into nr approx to neutrino energy density, plus equal sharing ΔN_eff factor for single massive neutrino
    Ω_ν = par.Σm_ν*νfac/par.h^2
    return 1 - (par.Ω_r*(1+(2/3)*(7par.N_ν/8)*(4/11)^(4/3))  # dark energy density
                                         + par.Ω_b + par.Ω_m
                                         + Ω_ν) #assume massive nus are non-rel today
end

# Hubble parameter ȧ/a in Friedmann background
function H_a(a, par::AbstractCosmoParams)
    ρ_ν,_ = ρP_0(a,par) # we don't atually need pressure?
    return H₀(par) * √((par.Ω_m + par.Ω_b ) * a^(-3)
                        + ρ_ν/ρ_crit(par)
                        + par.Ω_r*(1+(2/3)*(7par.N_ν/8)*(4/11)^(4/3)) * a^(-4)
                        + Ω_Λ(par))
end
# conformal time Hubble parameter, aH
ℋ_a(a, par::AbstractCosmoParams) = a * H_a(a, par)

# functions in terms of x
H(x, par::AbstractCosmoParams) = H_a(x2a(x),par)
ℋ(x, par::AbstractCosmoParams) = ℋ_a(x2a(x), par)

# conformal time
function η(x, par::AbstractCosmoParams)
    return quadgk(a -> 1.0 / (a * ℋ_a(a, par)), 0.0, x2a(x),rtol=1e-6)[1]
end

#background FD phase space
function f0(q,par::AbstractCosmoParams)
    Tν =  (par.N_ν/3)^(1/4) *(4/11)^(1/3) * (15/ π^2 *ρ_crit(par) *par.Ω_r)^(1/4)
    gs =  2 #should be 2 for EACH neutrino family (mass eigenstate)
    return gs / (2π)^3 / ( exp(q/Tν) +1)
end

function dlnf0dlnq(q,par::AbstractCosmoParams) #this is actually only used in perts
    Tν =  (par.N_ν/3)^(1/4) * (4/11)^(1/3) * (15/ π^2 *ρ_crit(par) *par.Ω_r)^(1/4)
    return -q / Tν /(1 + exp(-q/Tν))
end

#FIXME use FastGaussQuadrature instead of slower adaptive 9as in perts)
function ρP_0(a,par::AbstractCosmoParams)
    #Background phase space energy density and pressure
    Tν =  (par.N_ν/3)^(1/4) * (4/11)^(1/3) * (15/ π^2 *ρ_crit(par) *par.Ω_r)^(1/4)
    m = par.Σm_ν
    qmin=1e-18
    qmax=1e1
    ρ = 4π * a^(-4) * quadgk(q ->  q^2 * √( q^2 + (a*m)^2 ) * f0(q,par) ,qmin, qmax,rtol=1e-6)[1]
    P = 4π/3 * a^(-4) * quadgk(q -> q^2 * q^2 /√( q^2 + (a*m)^2) * f0(q,par), qmin, qmax,rtol=1e-6)[1]
    return ρ,P#,norm
end
# now build a Background with these functions

# a background is parametrized on the scalar type T, the interpolator type IT,
# and a type for the grid GT
abstract type AbstractBackground{T, IT<:AbstractInterpolation{T,1}, GT} end

struct Background{T, IT, GT} <: AbstractBackground{T, IT, GT}
    H₀::T
    η₀::T
    ρ_crit::T
    Ω_Λ::T

    x_grid::GT
    quad_pts::Array{T,1}
    quad_wts::Array{T,1}

    ℋ::IT
    ℋ′::IT
    ℋ′′::IT
    η::IT
    η′::IT
    η′′::IT
end

function Background(par::AbstractCosmoParams{T}; x_grid=-20.0:0.01:0.0, nq=15) where T
    ℋ_  = spline([ℋ(x, par) for x in x_grid], x_grid)
    η_   = spline([η(x, par) for x in x_grid], x_grid)
    quad_pts, quad_wts =  gausslegendre( nq ) #12 should get 1e-3, 15 conservative
    return Background(
        T(H₀(par)),
        T(η(0.0, par)),
        T(ρ_crit(par)),
        T(Ω_Λ(par)),

        x_grid,
        convert(Array{T,1},quad_pts), #explicit call to convert instead of constructor for arrays
        convert(Array{T,1},quad_wts),

        ℋ_,
        spline_∂ₓ(ℋ_, x_grid),
        spline_∂ₓ²(ℋ_, x_grid),

        η_,
        spline_∂ₓ(η_, x_grid),
        spline_∂ₓ²(η_, x_grid),

    )
end


using DirectDetections, Distributions, Plots, MCMCChains

@named B = DirectDetections.Planet(
    Deterministic(
        e = (sys, pl) -> 10^pl.loge,
        a = (sys, pl) -> 10^pl.loga,
        mass = (sys, pl) -> 10^pl.logm,
    ),
    Priors(
        # a = Uniform(0, 25),
        # e = TruncatedNormal(0.0, 0.2, 0, 1.0),
        # Note: priors with sharp edges (e.g. Uniform priors) are challenging for HMC samplers.
        # An alternative could be wide Gaussians, for example.
        τ = Normal(0.5, 1),
        ω = Normal(pi, 2pi),
        i = Normal(pi, 2pi),
        Ω = Normal(pi, 2pi),
        # mass = Uniform(0, 1),

        # Reparameterize a few properties for better sampling
        loge = TruncatedNormal(-2, 1.5, -Inf, 0),
        loga = Uniform(-1, 1.5),
        logm = Uniform(-2, 0),

    ),
    Astrometry(
        (epoch=mjd("2016-12-15"), ra=0.133*1e3, dec=-0.174*1e3, σ_ra=0.007*1e3, σ_dec=0.007*1e3),
        (epoch=mjd("2017-03-12"), ra=0.126*1e3, dec=-0.176*1e3, σ_ra=0.004*1e3, σ_dec=0.004*1e3),
        (epoch=mjd("2017-03-13"), ra=0.127*1e3, dec=-0.172*1e3, σ_ra=0.004*1e3, σ_dec=0.004*1e3),
        (epoch=mjd("2018-02-08"), ra=0.083*1e3, dec=-0.133*1e3, σ_ra=0.010*1e3, σ_dec=0.010*1e3),
        (epoch=mjd("2018-11-28"), ra=0.058*1e3, dec=-0.122*1e3, σ_ra=0.010*1e3, σ_dec=0.020*1e3),
        (epoch=mjd("2018-12-15"), ra=0.056*1e3, dec=-0.104*1e3, σ_ra=0.008*1e3, σ_dec=0.008*1e3),
    )
)

@named HD82134 = System(
    # Deterministic(
    #     j = Returns(1),
    #     k = Returns(1)
    # ),
    Priors(
        μ = Normal(1.61, 0.05),
        plx = gaia_plx(gaia_id=756291174721509376),
    ),
    ProperMotionAnomHGCA(gaia_id=756291174721509376),
    B,
)

##
plot(B.astrometry)

## Sampling from the posterior
chain = DirectDetections.hmc(
    HD82134,  0.90,
    # MCMCThreads(),
    # num_chains=4,
    adaptation =    500,
    iterations =  2_000,
    tree_depth =     10,
)

##
gelmandiag(chain)

## Plot the input data and samples from the posterior
plotmodel(chain, clims=(5, 20))

## Create a corner plot / pair plot.
# We can access any property from the chain specified in Priors or in Deterministic.
using PairPlots
table = (;
    a=         chain["B[a]"],
    μ=         chain["μ"],
    m=         chain["B[mass]"],
    e=         chain["B[e]"],
    i=rad2deg.(rem2pi.(chain["B[i]"],RoundDown)),
    Ω=rad2deg.(rem2pi.(chain["B[Ω]"],RoundDown)),
    ω=rad2deg.(rem2pi.(chain["B[ω]"],RoundDown)),
    τ=     rem.(chain["B[τ]"],1,RoundDown),
)
labels=[
    "a",
    "\\mu",
    "m",
    "e",
    "i",
    "\\Omega",
    "\\omega",
    "\\tau",
]
units = [
    "(au)",
    "(_\\odot)",
    "(_\\odot)",
    "",
    "(\\degree)",
    "(\\degree)",
    "(\\degree)",
    "",
]
corner(table, labels, units);
nothing


using Plots
plotlyjs();

include("../julia/intensity_inhomopoisson.jl");
include("../julia/utils.jl");

x_true = readcsv("../data/spikes.csv")[:];
rs_true = readcsv("../data/firing_rate_true.csv")[:];

rs_kernel = intensity_inhomopoisson(x_true, w=0.01);

plot(ts, rs_true, label="True")
plot!(ts, rs_kernel, label="Estimated")

xlabel!("Time (s)")
ylabel!("Firing rate (Hz)")


using Soss
using SampleChains
using SampleChainsDynamicHMC
using Statistics
using LinearAlgebra
using Random
using Tullio

function loglik(X,y)
    (N,k) = size(X)
    yᵗy = y' * y
    yᵗX = vec(y' * X)
    XᵗX = X' * X
    ∑X = vec(sum(X; dims=1))
    ∑y = sum(y)

    function f(α, β, σ)
        # If α==0 this would be
        # -N * log(σ) - 0.5/σ^2 * (yᵗy - 2 * yᵗX * β + β' * XᵗX * β)
        t0 = yᵗy - α * (2∑y - N * α)
        @tullio t1 = 2β[i] * (α * ∑X[i] - yᵗX[i])
        @tullio t2 = β[i] * XᵗX[i,j] * β[j]
        -N * log(σ) - 0.5/σ^2 * (t0 + t1 + t2)
    end
    return f
end

function makeℓ(X, y, pr)
    k = size(X,2)
    ll = loglik(X,y)
    ℓ(pars) = ll(pars.α, pars.β, pars.σ) + logdensity_def(pr(k=k), pars)
end

function bayeslm(
    rng::AbstractRNG,
    X,
    y,
    pr;
    N::Int = 1000,
    ad_backend = Val(:ForwardDiff),
    kwargs...,
)
    ℓ = makeℓ(X, y, pr)
    t = as(pr(k=size(X,2)))

    chain = initialize!(DynamicHMCChain, ℓ, t)

    drawsamples!(chain,N-1)
end

rng = Random.GLOBAL_RNG

# One million data points, 4 parameters + intercept
N = 1000000;
k = 4;
X = randn(N,k);
α = 10.0;
β = randn(k);
σ = 0.2;
y = α .+ X * β + σ * randn(N);

using Soss

# Whatever prior you want
pr = @model k begin
    α ~ Cauchy()
    β ~ Normal() |> iid(k)
    σ ~ Exponential()
end;

# Sample from the posterior
@time chain = bayeslm(rng, X, y, pr; N=1000)


"""
    Module Basis2DTri

Includes DG core functions.
"""

module Basis2DTri

using Basis1D
using LinearAlgebra
using DelimitedFiles

export vandermonde_2D, grad_vandermonde_2D
export nodes_2D, equi_nodes_2D, quad_nodes_2D

"""
    simplex_2D(a, b, i, j)

Evaluate 2D "Legendre" basis phi_ij at (a,b) returned by rstoab

# Examples
```jldoctest
"""
function simplex_2D(a, b, i, j)
    h₁ = jacobiP(a, 0, 0, i)
    h₂ = jacobiP(b, 2i+1, 0, j)
    return @. sqrt(2.0)*h₁*h₂*(1-b)^i
end

"""
    grad_simplex_2D(a, b, id, jd)

Evalute the partial derivative w.r.t. (r,s) of 2D "Legendre" polynomial with
index, or order, (id,jd) at (a,b)

# Examples
```jldoctest
"""

function grad_simplex_2D(a, b, id, jd)
    fa = jacobiP(a, 0, 0, id)
    gb = jacobiP(b, 2*id+1, 0, jd)
    dfa = grad_jacobiP(a, 0, 0, id)
    dgb = grad_jacobiP(b, 2*id+1, 0, jd)

    dmodedr = @. dfa*gb
    if id > 0
        dmodedr = dmodedr.*((0.5*(1 .- b)).^(id-1))
    end

    dmodeds = @. dfa*(gb*(0.5*(1+a)))
    if id > 0
        dmodeds = dmodeds.*((0.5*(1 .- b)).^(id-1))
    end

    tmp = @. dgb*((0.5*(1-b))^id)
    if id > 0
        tmp = tmp-0.5*id*gb.*((0.5*(1 .- b)).^(id-1))
    end
    dmodeds = dmodeds+fa.*tmp

    dmodedr = 2^(id+0.5)*dmodedr
    dmodeds = 2^(id+0.5)*dmodeds
    return dmodedr, dmodeds
end

"""
    rstoab(r, s)

Transfer from reference element coordinate (r,s) to polynomial basis evaluation
coordinate (a,b)

# Examples
```jldoctest
"""
function rstoab(r, s)
    a = zeros(length(r), 1)
    for n = 1:length(r)
        if s[n] != 1
            a[n] = 2*(1+r[n])/(1-s[n])-1
        else
            a[n] = -1
        end
    end
    return a, s
end

"""
    vandermonde_2D(N, r)

Initialize the 2D Vandermonde matrix of order N "Legendre" polynomials at
nodes (r,s)

# Examples
```jldoctest
"""
function vandermonde_2D(N, r, s)
    Np = convert(Int,(N+1)*(N+2)/2)
    V2D = zeros(length(r), Np)
    a, b = rstoab(r, s)
    sk = 1
    for i = 0:N
        for j = 0:N-i
            V2D[:,sk] = simplex_2D(a,b,i,j)
            sk = sk+1
        end
    end
    return V2D
end

"""
    gradV_2D(N, Np, r, s)

Initilize 2D gradient Vandermonde matrix of derivatives 2D "Legendre" polynomials
of order N at (r,s)

# Examples
```jldoctest
"""
function grad_vandermonde_2D(N, r, s)
    Np = convert(Int,(N+1)*(N+2)/2)
    V2Dr = zeros(length(r), Np)
    V2Ds = zeros(length(r), Np)
    a, b = rstoab(r, s)

    sk = 1
    for i = 0:N
        for j = 0:N-i
            V2Dr[:,sk], V2Ds[:,sk] = grad_simplex_2D(a, b, i, j)
            sk = sk+1
        end
    end

    return V2Dr, V2Ds
end

# ===================================================

"""
    xytors(x, y)

Transfer from equilateral triangle coordinate (a,b) to reference element
coordinate (r,s)

# Examples
```jldoctest
"""
function xytors(x, y)
    L₁ = @. (sqrt(3.0)y+1.0)/3.0
    L₂ = @. (-3.0x-sqrt(3.0)y+2.0)/6.0
    L₃ = @. (3.0x-sqrt(3.0)y+2.0)/6.0
    r = -L₂+L₃-L₁
    s = -L₂-L₃+L₁
    return r, s
end

"""
    function warp_factor(N, rout)

Compute warp factor for triangular interp nodes.

# Examples
```jldoctest
"""

function warp_factor(N, rout)
    LGLr,w1D = gauss_lobatto_quad(0, 0, N)
    # LGLr = transpose(LGLr[:])
    req = LinRange(-1,1,N+1)
    Veq = vandermonde_1D(N, req)
    Nr = length(rout)
    Pmat = zeros(N+1, Nr)
    for i = 1:N+1
        Pmat[i,:] = transpose(jacobiP(rout, 0, 0, i-1))
    end
    Lmat = transpose(Veq)\Pmat
    # warp = transpose(Lmat)*transpose(LGLr.-req)
    warp = transpose(Lmat)*(LGLr.-req[:])
    zerof = @. (abs(rout) < 1.0 - 1.0e-10)
    sf = @. 1.0 - (zerof*rout)^2
    warp = @. warp/sf + warp*(zerof-1)
    return warp
end

"""
    nodes_2D(N)

Compute optimized interpolation nodes using blend & warp method on equilateral
triangles for polynomial of order N, with Np points

# Examples
```jldoctest
"""

function nodes_2D(N)

    Np = convert(Int,(N+1)*(N+2)/2)

    alpopt = [0.0 0.0 1.4152 0.1001 0.2751 0.98 1.0999 1.2832 1.3648 1.4773 1.4959 1.5743 1.577 1.6223 1.6258]
    α = N < 16 ? alpopt[N] : 5/3

    L1 = zeros(Np, 1)
    L2 = zeros(Np, 1)
    L3 = zeros(Np, 1)
    sk = 1
    for n = 1:N+1
        for m = 1:N+2-n
            L1[sk] = (n-1)/N
            L3[sk] = (m-1)/N
            sk += 1;
        end
    end
    L2 = @. 1.0-L1-L3
    x = -L2+L3
    y = (-L2-L3+2*L1)/sqrt(3.0)
    blend1 = 4.0*L2.*L3
    blend2 = 4.0*L1.*L3
    blend3 = 4.0*L1.*L2
    warpf1 = warp_factor(N, L3-L2)
    warpf2 = warp_factor(N, L1-L3)
    warpf3 = warp_factor(N, L2-L1)
    warp1 = @. blend1*warpf1*(1+(α*L1).^2)
    warp2 = @. blend2*warpf2*(1+(α*L2).^2)
    warp3 = @. blend3*warpf3*(1+(α*L3).^2)

    x = x + 1*warp1 + cos(2*pi/3)*warp2 + cos(4*pi/3)*warp3
    y = y + 0*warp1 + sin(2*pi/3)*warp2 + sin(4*pi/3)*warp3

    r, s = xytors(x, y)
    return r, s
end

"""
    equi_nodes_2D(N)

Compute optimized interpolation nodes using blend & warp method on equilateral
triangles for polynomial of order N, with Np points

# Examples
```jldoctest
"""

function equi_nodes_2D(N)
    Np = convert(Int,(N+1)*(N+2)/2)

    r = zeros(Np,1)
    s = zeros(Np,1)

    r1D = LinRange(-1,1,N+1)
    sk = 1
    for i = 0:N
        for j = 0:N-i
            r[sk] = r1D[i+1]
            s[sk] = r1D[j+1]
            sk += 1
        end
    end

    return r, s
end

"""
    quad_nodes_tri(N)

Returns quadrature nodeswhich exactly integrate degree N polynomials

# Examples
```jldoctest
"""

function quad_nodes_tri(N)

    if N<28
        rsw = readdlm(string("./src/QuadratureData/quad_nodes_tri_N", N, ".txt"),' ', Float64, '\n')
        r = rsw[:,1]
        s = rsw[:,2]
        w = rsw[:,3]
    else
        cubNA = convert(Int,ceil( (N+1)/2))
        cubNB = convert(Int,ceil( (N+1)/2))
        cubA,cubWA = gauss_quad(0,0, cubNA-1)
        cubB,cubWB = gauss_quad(1,0, cubNB-1)

        cubA = ones(cubNB,1)*cubA'
        cubB = cubB*ones(1,cubNA)

        r = @. 0.5*(1+cubA)*(1-cubB)-1
        s = cubB
        w = 0.5*cubWB*(cubWA')
    end

    return r[:], s[:], w[:]
end

"""
    quad_nodes_2D(N)

# Examples
```jldoctest
"""

function quad_nodes_2D(N)
    r,s,w = quad_nodes_tri(N)
    return r,s,w
end

end


for i in 1:length(sol.t)
    ca1 = Tnt * sol.u[i]
    cb1 = chderive(Float64(0.0), ρmax, ca1, gridnum)
    cc1 = chderive(Float64(0.0), ρmax, cb1, gridnum)
    m1 = Tn * cb1
    m2 = Tn * cc1
    m1fun=Fun(S, ApproxFun.transform(S, m1))
    m2fun=Fun(S, ApproxFun.transform(S, m2))
    minpos=optimize(Vout, 0.0, ρmax).minimizer
    mass_σ[i]=sqrt(2*minpos*m2fun(minpos)*Λ^2)
end

mass_σ=similar(sol.t)

mass_σ

89*Λ^-2

plot([sol.u[2]])
plot(ρ,sol.u[1])

myf=Fun(S, ApproxFun.transform(S, sol.u[end-10]))

plot(myf'',0.0001,0.015)

plot(m1fun,0,0.03)

plot(Λ*exp.(sol.t), mass_σ)


using SIAN

println("Setting up the problem")

ode = @ODEmodel(
  x4'(t) = - k5 * x4(t) // (k6 + x4(t)),
  x5'(t) = k5 * x4(t) // (k6 + x4(t)) - k7 * x5(t) / (k8 + x5(t) + x6(t)),
  x6'(t) = k7 * x5(t) // (k8 + x5(t) + x6(t)) - k9 * x6(t) * (k10 - x6(t)) // k10,
  x7'(t) = k9 * x6(t) * (k10 - x6(t)) // k10,
  y1(t) = x4(t),
  y2(t) = x5(t)
)

res = identifiability_ode(ode, get_parameters(ode); p=0.999, p_mod=0, nthrds=1)

println(res)


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:4] <= 1)

                     @variable(m, -23.072345451695163 <= q <= 18.38163265505858)

                     add_NL_constraint(m, :(softplus(-0.6895881478464707 + 0.42824131807966515*softplus(-0.9657925733535908 + -0.20993433529810623*$(x[1]) + -0.9858887282433288*$(x[2]) + -0.7926811137744871*$(x[3]) + -0.8802726339564089*$(x[4])) + -0.8758217593243995*softplus(-0.29556778373435044 + 0.6695148008497993*$(x[1]) + 0.7620585252555783*$(x[2]) + 0.8362720647957991*$(x[3]) + 0.8751574333999192*$(x[4])) + -0.3819126296826152*softplus(-0.8627823044260601 + -0.3188988197195952*$(x[1]) + -0.18058802482760727*$(x[2]) + -0.7255181409249616*$(x[3]) + -0.8658172698743405*$(x[4])) + -0.7523924735752416*softplus(0.5506348394687617 + 0.9730675890304568*$(x[1]) + 0.0064031244486910666*$(x[2]) + -0.590531971380631*$(x[3]) + -0.964793293764588*$(x[4]))) + softplus(0.5903696565764314 + 0.6045010784464115*softplus(-0.9657925733535908 + -0.20993433529810623*$(x[1]) + -0.9858887282433288*$(x[2]) + -0.7926811137744871*$(x[3]) + -0.8802726339564089*$(x[4])) + 0.19992590084675577*softplus(-0.29556778373435044 + 0.6695148008497993*$(x[1]) + 0.7620585252555783*$(x[2]) + 0.8362720647957991*$(x[3]) + 0.8751574333999192*$(x[4])) + -0.13493555263327828*softplus(-0.8627823044260601 + -0.3188988197195952*$(x[1]) + -0.18058802482760727*$(x[2]) + -0.7255181409249616*$(x[3]) + -0.8658172698743405*$(x[4])) + 0.0007730145672497635*softplus(0.5506348394687617 + 0.9730675890304568*$(x[1]) + 0.0064031244486910666*$(x[2]) + -0.590531971380631*$(x[3]) + -0.964793293764588*$(x[4]))) + softplus(-0.987583954230804 + 0.6297203029880079*softplus(-0.9657925733535908 + -0.20993433529810623*$(x[1]) + -0.9858887282433288*$(x[2]) + -0.7926811137744871*$(x[3]) + -0.8802726339564089*$(x[4])) + -0.939181196020844*softplus(-0.29556778373435044 + 0.6695148008497993*$(x[1]) + 0.7620585252555783*$(x[2]) + 0.8362720647957991*$(x[3]) + 0.8751574333999192*$(x[4])) + 0.23747324715066886*softplus(-0.8627823044260601 + -0.3188988197195952*$(x[1]) + -0.18058802482760727*$(x[2]) + -0.7255181409249616*$(x[3]) + -0.8658172698743405*$(x[4])) + 0.2556344288953589*softplus(0.5506348394687617 + 0.9730675890304568*$(x[1]) + 0.0064031244486910666*$(x[2]) + -0.590531971380631*$(x[3]) + -0.964793293764588*$(x[4]))) + softplus(-0.44105426128556546 + -0.39452594471110514*softplus(-0.9657925733535908 + -0.20993433529810623*$(x[1]) + -0.9858887282433288*$(x[2]) + -0.7926811137744871*$(x[3]) + -0.8802726339564089*$(x[4])) + 0.16620534874676274*softplus(-0.29556778373435044 + 0.6695148008497993*$(x[1]) + 0.7620585252555783*$(x[2]) + 0.8362720647957991*$(x[3]) + 0.8751574333999192*$(x[4])) + 0.6245292013722641*softplus(-0.8627823044260601 + -0.3188988197195952*$(x[1]) + -0.18058802482760727*$(x[2]) + -0.7255181409249616*$(x[3]) + -0.8658172698743405*$(x[4])) + 0.9983459126930239*softplus(0.5506348394687617 + 0.9730675890304568*$(x[1]) + 0.0064031244486910666*$(x[2]) + -0.590531971380631*$(x[3]) + -0.964793293764588*$(x[4]))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


using FastTransforms, Compat
using Compat.Test, Compat.LinearAlgebra, Compat.Random
if VERSION < v"0.7"
    srand(0)
else
    using Compat.Statistics
    Random.seed!(0)
end

include("basictests.jl")

include("chebyshevtests.jl")

include("nuffttests.jl")

include("chebyshevjacobitests.jl")

include("chebyshevlegendretests.jl")

include("gaunttests.jl")

include("fftBigFloattests.jl")

include("paduatests.jl")

include("butterflytests.jl")

include("sphericalharmonics/sphericalharmonictests.jl")

include("toeplitztests.jl")


"""
$(TYPEDEF)

A system of stochastic differential equations.

# Fields
$(FIELDS)

# Example

```
using ModelingToolkit

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

eqs = [D(x) ~ σ*(y-x),
       D(y) ~ x*(ρ-z)-y,
       D(z) ~ x*y - β*z]

noiseeqs = [0.1*x,
            0.1*y,
            0.1*z]

de = SDESystem(eqs,noiseeqs,t,[x,y,z],[σ,ρ,β])
```
"""
struct SDESystem <: AbstractODESystem
    """The expressions defining the drift term."""
    eqs::Vector{Equation}
    """The expressions defining the diffusion term."""
    noiseeqs::AbstractArray{Operation}
    """Independent variable."""
    iv::Variable
    """Dependent (state) variables."""
    states::Vector{Variable}
    """Parameter variables."""
    ps::Vector{Variable}
    """
    Time-derivative matrix. Note: this field will not be defined until
    [`calculate_tgrad`](@ref) is called on the system.
    """
    tgrad::RefValue{Vector{Expression}}
    """
    Jacobian matrix. Note: this field will not be defined until
    [`calculate_jacobian`](@ref) is called on the system.
    """
    jac::RefValue{Matrix{Expression}}
    """
    `Wfact` matrix. Note: this field will not be defined until
    [`generate_factorized_W`](@ref) is called on the system.
    """
    Wfact::RefValue{Matrix{Expression}}
    """
    `Wfact_t` matrix. Note: this field will not be defined until
    [`generate_factorized_W`](@ref) is called on the system.
    """
    Wfact_t::RefValue{Matrix{Expression}}
    """
    Name: the name of the system
    """
    name::Symbol
    """
    Systems: the internal systems
    """
    systems::Vector{SDESystem}
end

function SDESystem(deqs::AbstractVector{<:Equation}, neqs, iv, dvs, ps;
                   systems = SDESystem[],
                   name = gensym(:SDESystem))
    iv′ = convert(Variable,iv)
    dvs′ = convert.(Variable,dvs)
    ps′ = convert.(Variable,ps)
    tgrad = RefValue(Vector{Expression}(undef, 0))
    jac = RefValue(Matrix{Expression}(undef, 0, 0))
    Wfact   = RefValue(Matrix{Expression}(undef, 0, 0))
    Wfact_t = RefValue(Matrix{Expression}(undef, 0, 0))
    SDESystem(deqs, neqs, iv′, dvs′, ps′, tgrad, jac, Wfact, Wfact_t, name, systems)
end

function generate_diffusion_function(sys::SDESystem, dvs = sys.states, ps = sys.ps; kwargs...)
    dvs′ = convert.(Variable,dvs)
    ps′ = convert.(Variable,ps)
    return build_function(sys.noiseeqs, dvs′, ps′, sys.iv;
                          conv = ODEToExpr(sys),kwargs...)
end

"""
```julia
function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = sys.states, ps = sys.ps;
                                     version = nothing, tgrad=false, sparse = false,
                                     jac = false, Wfact = false, kwargs...) where {iip}
```

Create an `SDEFunction` from the [`SDESystem`](@ref). The arguments `dvs` and `ps`
are used to set the order of the dependent variable and parameter vectors,
respectively.
"""
function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = sys.states, ps = sys.ps,
                                     u0 = nothing;
                                     version = nothing, tgrad=false, sparse = false,
                                     jac = false, Wfact = false, kwargs...) where {iip}
    f_oop,f_iip = generate_function(sys, dvs, ps; expression=Val{false}, kwargs...)
    g_oop,g_iip = generate_diffusion_function(sys, dvs, ps; expression=Val{false}, kwargs...)

    f(u,p,t) = f_oop(u,p,t)
    f(du,u,p,t) = f_iip(du,u,p,t)
    g(u,p,t) = g_oop(u,p,t)
    g(du,u,p,t) = g_iip(du,u,p,t)

    if tgrad
        tgrad_oop,tgrad_iip = generate_tgrad(sys, dvs, ps; expression=Val{false}, kwargs...)
        _tgrad(u,p,t) = tgrad_oop(u,p,t)
        _tgrad(J,u,p,t) = tgrad_iip(J,u,p,t)
    else
        _tgrad = nothing
    end

    if jac
        jac_oop,jac_iip = generate_jacobian(sys, dvs, ps; expression=Val{false}, sparse=sparse, kwargs...)
        _jac(u,p,t) = jac_oop(u,p,t)
        _jac(J,u,p,t) = jac_iip(J,u,p,t)
    else
        _jac = nothing
    end

    if Wfact
        tmp_Wfact,tmp_Wfact_t = generate_factorized_W(sys, dvs, ps, true; expression=Val{false}, kwargs...)
        Wfact_oop, Wfact_iip = tmp_Wfact
        Wfact_oop_t, Wfact_iip_t = tmp_Wfact_t
        _Wfact(u,p,dtgamma,t) = Wfact_oop(u,p,dtgamma,t)
        _Wfact(W,u,p,dtgamma,t) = Wfact_iip(W,u,p,dtgamma,t)
        _Wfact_t(u,p,dtgamma,t) = Wfact_oop_t(u,p,dtgamma,t)
        _Wfact_t(W,u,p,dtgamma,t) = Wfact_iip_t(W,u,p,dtgamma,t)
    else
        _Wfact,_Wfact_t = nothing,nothing
    end

    M = calculate_massmatrix(sys)
    _M = (u0 === nothing || M == I) ? M : ArrayInterface.restructure(u0 .* u0',M)

    SDEFunction{iip}(f,g,jac=_jac,
                      tgrad = _tgrad,
                      Wfact = _Wfact,
                      Wfact_t = _Wfact_t,
                      mass_matrix = _M,
                      syms = Symbol.(sys.states))
end

function DiffEqBase.SDEFunction(sys::SDESystem, args...; kwargs...)
    SDEFunction{true}(sys, args...; kwargs...)
end

function rename(sys::SDESystem,name)
    ODESystem(sys.eqs, sys.noiseeqs, sys.iv, sys.states, sys.ps, sys.tgrad, sys.jac, sys.Wfact, sys.Wfact_t, name, sys.systems)
end

"""
```julia
function DiffEqBase.SDEProblem{iip}(sys::SDESystem,u0map,tspan,p=parammap;
                                    version = nothing, tgrad=false,
                                    jac = false, Wfact = false,
                                    checkbounds = false, sparse = false,
                                    sparsenoise = sparse,
                                    linenumbers = true, parallel=SerialForm(),
                                    kwargs...)
```

Generates an SDEProblem from an SDESystem and allows for automatically
symbolically calculating numerical enhancements.
"""
function DiffEqBase.SDEProblem{iip}(sys::SDESystem,u0map,tspan,parammap=DiffEqBase.NullParameters();
                                    version = nothing, tgrad=false,
                                    jac = false, Wfact = false,
                                    checkbounds = false, sparse = false,
                                    sparsenoise = sparse,
                                    linenumbers = true, parallel=SerialForm(),
                                    kwargs...) where iip

    dvs = states(sys)
    ps = parameters(sys)
    u0 = varmap_to_vars(u0map,dvs)
    p = varmap_to_vars(parammap,ps)
    f = SDEFunction{iip}(sys,dvs,ps,u0;tgrad=tgrad,jac=jac,Wfact=Wfact,
                         checkbounds=checkbounds,
                         linenumbers=linenumbers,parallel=parallel,
                         sparse=sparse)
    if typeof(sys.noiseeqs) <: AbstractVector
        noise_rate_prototype = nothing
    elseif sparsenoise
        I,J,V = findnz(SparseArrays.sparse(sys.noiseeqs))
        noise_rate_prototype = SparseArrays.sparse(I,J,zero(eltype(u0)))
    else
        noise_rate_prototype = zeros(eltype(u0),size(sys.noiseeqs))
    end

    SDEProblem{iip}(f,f.g,u0,tspan,p;noise_rate_prototype=noise_rate_prototype,kwargs...)
end

function DiffEqBase.SDEProblem(sys::SDESystem, args...; kwargs...)
    SDEProblem{true}(sys, args...; kwargs...)
end


function heatmap(mat::Array{Float64,2})
  Nrow, Ncol = size(mat)
  xvals = Array(Float64,length(mat))
  yvals = Array(Float64,length(mat))
  colorvals = Array(Float64,length(mat))
  index = 1
  for x in 1:Ncol
    for y in 1:Nrow
      xvals[index] = x
      yvals[index] = y
      colorvals[index] = mat[y,x]
      index+=1
    end
  end
  plot(x=xvals,y=yvals,color=colorvals, Geom.rectbin,
  	Scale.x_discrete, Scale.y_discrete,
	Coord.cartesian(yflip=true,fixed=true),
	Guide.xlabel(""),Guide.ylabel(""),
	Scale.ContinuousColorScale(Scale.lab_gradient(colorant"white",colorant"red"),minvalue=0.))
end

function writeMatrix(filename::ASCIIString,matrix::Matrix)
  stream = open(filename,"w+")
  write(stream,mat)
  close(stream)
end

function readMatrix{T<:Real}(filename::ASCIIString,format::Type{T},dims::Tuple{Vararg{Int64}})
  stream = open(filename,"r")
  matrix = read(stream,format,dims)
  close(stream)
  return matrix
end


clearconsole()
# using Base # not required
using FileIO
using MatlabCompat
using MatlabCompat.Io
using MatlabCompat.ImageTools
import MatlabCompat.MathTools
import MatlabCompat.MathTools: max
using Test
using Images
using FixedPointNumbers
# using Tk

const TEST_FOLDER = dirname(@__FILE__)
const JANUS_PATH = joinpath(TEST_FOLDER, "janus.m");
const TESTMAT_PATH = joinpath(TEST_FOLDER, "test.mat");

# basic test
println("foo test")
x = function ()
    return 1 == 1
end
@test x()

################################
#     ImageTools Module Tests
################################

# make a test image
array = [0.1  0.001  0  0;
0.1  0.401  0  0;
0.1  0.01   0  0;
0.1  0.901  0  0.6]; # should be Float : Float32, Float64, etc

# Any array can be treated as an Image. However, in graphical environments, only arrays with Colorant element types are automatically displayed as images.
#  Coloran typest=(Gray, RGB, ARGB, etc.)

# Syntax 1 - Array{Gray}
img1 =Gray.(array);
#  # calculates a grayscale representation of a color image using the Rec 601 luma. (https://juliaimages.org/latest/function_reference/#Color-conversion-1)
# imgMet=ImageMeta(img, time="after noon");
println(typeof(img1)) # gives Array{Gray{Float64},2}

# Syntax 2 - ReinterpretArray
img2 = colorview(Gray, array);
# returns a view of the numeric array A, interpreting successive elements of A as if they were channels of Colorant C. (https://juliaimages.org/latest/function_reference/#ImageCore.colorview)
imgMet=ImageMeta(img2, time="after noon"); # to add ImageMeta
println(typeof(img2)) # gives Base.ReinterpretArray{Gray{Float64},2,Float64,Array{Float64,2}}

# Syntax 3 - ReinterpretArray + ImageMeta
imgMet = ImageMeta(colorview(Gray, array), time="after noon")
 # returns an ImageMeta type, which holds both data() with Colorant element type, and properties() (Dict type)
properties(imgMet)["location"] = "university"; # to access and extract properties
img3=data(imgMet); # to access and extract data
println(typeof(img3)) # gives Base.ReinterpretArray{Gray{Float64},2,Float64,Array{Float64,2}}

# Syntax 4 - Array + ImageMeta
# this does not work with this module
imgMet = ImageMeta(array, time="after noon")
 # returns an ImageMeta type, which holds both data() but array has Float type instead of Colorant, and properties() (Dict type)
properties(imgMet)["location"] = "university"; # to access and extract properties
img4=data(imgMet); # to access and extract data
println(typeof(img4)) # gives Array{Float64,2}
# this synatx does

img=img1; # choose img for test

# test the fuctions
println("setting true tests results");
trueThreshold = 0.11100752480522075;
trueBWSum= 3;

println("testing graythresh()");
# @test graythresh(img) == trueThreshold;
graythresh(img) == trueThreshold;

println("testing im2bw()");
# @test sum( reinterpret( Float32, float32( im2bw(img, trueThreshold) ) )[:] ) == trueBWSum;
sum( reinterpret( Float32, float32( im2bw(img, trueThreshold) ) )[:] ) == trueBWSum;

println("testing imread()");
img5 = MatlabCompat.imread("http://matlabcompat.github.io/img/example.tif");
expected = nothing
expected = "Images.Image{ColorTypes.Gray{FixedPointNumbers.UFixed{UInt8,8}},2,Array{ColorTypes.Gray{FixedPointNumbers.UFixed{UInt8,8}},2}}"

@test "$(typeof(img5))" == expected;

################################
#     Support Module Tests
################################

println("testing im2mat()");
mat = im2mat(img)
@test "$(typeof(mat))" == "Array{Float32,2}";

println("testing mat2im()");
expected = nothing
if (false)
    expected = "Image{Float64,2,Array{Float64,2}}"
else
    expected = "Images.Image{Float64,2,Array{Float64,2}}"
end
@test "$(typeof(mat2im(img)))" == expected;

println("testing rosetta()");
@test "$(typeof(rosetta(JANUS_PATH)))" == "Array{UTF8String,1}";

################################
#     Io Module Tests
################################

println("testing load()");
println("$(typeof(MatlabCompat.load(TESTMAT_PATH)))")
@test "$(typeof(MatlabCompat.load(TESTMAT_PATH)))" == "Dict{ASCIIString,Any}";
# "$(typeof(A))" == "Dict{Any,Any}"

################################
#     StringTools Module Tests
################################
println("testing disp()");
@test disp("test") == true;

println("testing num2str()");
@test num2str(3) == "3";

println("testing strcat()");
@test strcat("1", "2", "3") == "123";

################################
#     MathTools Module Tests
################################
matrix = [1 2 3];

println("testing numel()");
@test numel(matrix[:]) == 3;

println("testing max()");

@test max(matrix) == maximum(matrix);


"""
    mapmany(f, a...)

Like `map`, but `f(x)` for each `x ∈ a` may return an arbitrary number of values to insert
into the output.

# Example

```jldoctest
julia> mapmany(x -> 1:x, [1,2,3])
6-element Array{Int64,1}:
 1
 1
 2
 1
 2
 3
```
"""
function mapmany(f, a)
    T = eltype(promote_op(f, eltype(a)))
    out = T[]
    for x ∈ a
        append!(out, f(x))
    end
    return out
end

mapmany(f, a, b, c...) = mapmany(x->f(x...), zip(a, b, c...))

"""
    flatten(a)

Takes a collection of collections `a` and returns a collection containing all the elements
of the subcollecitons of `a`. Equivalent to `mapmany(idenity, a)`.

# Example

```jldoctest
julia> flatten([1:1, 1:2, 1:3])
6-element Array{Int64,1}:
 1
 1
 2
 1
 2
 3
```
"""
flatten(x) = mapmany(identity, x)

struct MappedIterator{F, T}
	f::F
	parent::T
end
@inline function iterate(it::MappedIterator)
	x = iterate(it.parent)
	return x === nothing ? nothing : (it.f(x[1]), x[2])
end
@inline function iterate(it::MappedIterator, i)
	x = iterate(it.parent, i)
	return x === nothing ? nothing : (it.f(x[1]), x[2])
end

# Inherit any iteration and indexing properties from parent
Base.IteratorSize(it::MappedIterator) = Base.IteratorSize(it.parent)
Base.length(it::MappedIterator) = length(it.parent)
Base.size(it::MappedIterator) = size(it.parent)
Base.axes(it::MappedIterator) = axes(it.parent)
Base.keys(it::MappedIterator) = keys(it.parent)
@propagate_inbounds Base.getindex(it::MappedIterator, i) = it.parent[i]

struct MappedArray{T, N, F, A <: AbstractArray{<:Any, N}} <: AbstractArray{T, N}
	f::F
	parent::A
end
parent(a::MappedArray) = a.parent
size(a::MappedArray) = size(a.parent)
axes(a::MappedArray) = axes(a.parent)
IndexStyle(a::MappedArray) = IndexStyle(a.parent)
@propagate_inbounds getindex(a::MappedArray{T}, i::Int) where {T} = a.f(a.parent[i])::T
@propagate_inbounds getindex(a::MappedArray{T}, i::Int...) where {T} = a.f(a.parent[i...])::T

"""
    mapview(f, a)

Return a view of `a` where each element is mapped through function `f`. The iteration and
indexing properties of `a` are preserved. Similar to `map(f, a)`, except evaluated lazily.

# Example

```julia
julia> a = [1,2,3];

julia> b = mapview(-, a)
3-element MappedArray{Int64,1,typeof(-),Array{Int64,1}}:
 -1
 -2
 -3

julia> a[1] = 10;

julia> b
3-element MappedArray{Int64,1,typeof(-),Array{Int64,1}}:
 -10
  -2
  -3
```
"""
mapview(f, a) = MappedIterator(f, a)
mapview(f, a::AbstractArray{T, N}) where {T, N} = MappedArray{promote_op(f, T), N, typeof(f), typeof(a)}(f, a)

mapview(::typeof(identity), a) = a
mapview(::typeof(identity), a::AbstractArray) = a


ENV["PYTHON_JL_RUNTIME_PYTHON"] = Sys.which("python")
using Pkg
using PyCall
using OrderedCollections
using LinearAlgebra
using SpikeSynchrony
using SpikingNeuralNetworks
SNN = SpikingNeuralNetworks
SNN.@load_units

#include("../opt_single_cell_utils.jl")

import DataStructures
using JLD

global ngt_spikes
global opt_vec
global extremum
global extremum_param
global ngt_spikes
global fitness

using Plots
unicodeplots()

###
global cell_type="ADEXP"
global vecp=false
###
using SpikeNetOpt
SNO = SpikeNetOpt

(vmgtv,vmgtt,ngt_spikes,ground_spikes) = SNO.get_data()
println("Ground Truth")
plot(vmgtt[:],vmgtv[:]) |> display

#lower,upper=get_izhi_ranges()

lower,upper=get_adexp_ranges()
ALLEN_DURATION = 2000 * ms
ALLEN_DELAY = 1000 * ms

function loss(E,ngt_spikes,ground_spikes)
    spikes = raster_synchp(E)
    spikes = [s*ms for s in spikes]
    maxt = findmax(sort!(unique(vcat(spikes,ground_spikes))))[1]
    if size(spikes)[1]>1
        t, S = SPIKE_distance_profile(spikes, ground_spikes;t0=0,tf = maxt)
        spkd = SpikeSynchrony.trapezoid_integral(t, S)/(t[end]-t[1]) # == SPIKE_distance(y1, y2)
    else
        spkd = 1.0
    end
    delta = abs(size(spikes)[1] - ngt_spikes)
    return spkd+delta

end

function zz_(param)
    if cell_type=="IZHI"
        pp = SNN.IZParameter(;a = param[1], b = param[2], c = param[3], d = param[4])
        E = SNN.IZ(;N = 1, param = pp)
    end
    if cell_type=="ADEXP"
        adparam = SNN.ADEXParameter(;a = param[1],
            b = param[2],
            cm = param[3],
            v0 = param[4],
            τ_m = param[5],
            τ_w = param[6],
            θ = param[7],
            delta_T = param[8],
            v_reset = param[9],
            spike_delta = param[10])

        E = SNN.AD(;N = 1, param=adparam)
    end

    SNN.monitor(E, [:v])
    SNN.monitor(E, [:fire])
    ALLEN_DURATION = 2000 * ms
    ALLEN_DELAY = 1000 * ms
    current = current_search(cell_type,param,ngt_spikes)
    E.I = [current*nA]#[param[5]*nA]

    SNN.sim!([E]; dt =1*ms, delay=ALLEN_DELAY,stimulus_duration=ALLEN_DURATION,simulation_duration = ALLEN_DURATION+ALLEN_DELAY+443ms)
    vecplot(E, :v) |> display
    error = loss(E,ngt_spikes,ground_spikes)
    error
end

function z(x::AbstractVector)
    param = x
    params = SNN.IZParameter(;a = param[1], b = param[2], c = param[3], d = param[4])
    E = SNN.IZ(;N = 1, param = params)
    SNN.monitor(E, [:v])
    SNN.monitor(E, [:fire])
    ALLEN_DURATION = 2000 * ms
    ALLEN_DELAY = 1000 * ms
    E.I = [param[5]*nA]
    SNN.sim!([E]; dt =1*ms, delay=ALLEN_DELAY,stimulus_duration=ALLEN_DURATION,simulation_duration = ALLEN_DURATION+ALLEN_DELAY+500ms)
    error = loss(E,ngt_spikes)
    @show(error)
    error

end

function checkmodel(param)
    if cell_type=="IZHI"
        pp = SNN.IZParameter(;a = param[1], b = param[2], c = param[3], d = param[4])
        E = SNN.IZ(;N = 1, param = pp)
    end
    if cell_type=="ADEXP"

        adparam = SNN.ADEXParameter(;a = param[1],
            b = param[2],
            cm = param[3],
            v_rest = param[4],
            tau_m = param[5],
            tau_w = param[6],
            v_thresh = param[7],
            delta_T = param[8],
            v_reset = param[9],
            spike_height = param[10])
    end
    E = SNN.AD(;N = 1, param=adparam)

    ALLEN_DURATION = 2000 * ms
    ALLEN_DELAY = 1000 * ms
    E.I = [current_search(param,ngt_spikes)*nA]

    SNN.monitor(E, [:v])
    SNN.sim!([E]; dt =1*ms, delay=ALLEN_DELAY,stimulus_duration=ALLEN_DURATION,simulation_duration = ALLEN_DURATION+ALLEN_DELAY+443ms)
    if vecp
        vec = SNN.vecplot(E, :v)
        vec |> display
        vec
    end

end

ɛ = 0.125
options = GA(
    populationSize = 20,
    ɛ = ɛ,
    selection = ranklinear(1.5),#ss,
    crossover = intermediate(1.0),#line(1.0),#xovr,
    mutation = uniform(1.0),#domainrange(fill(1.0,ts)),#ms
)
@time result = Evolutionary.optimize(zz_,lower,upper, initd, options,
    Evolutionary.Options(iterations=20, successive_f_tol=25, show_trace=true, store_trace=true)
)
fitness = minimum(result)
println("GA: ɛ=$ɛ) => F: $(minimum(result))")# C: $(Evolutionary.iterations(result))")
extremum_param = Evolutionary.minimizer(result)
opt_vec = checkmodel(extremum_param)
println("probably jumbled extremum param")
println(extremum_param)
plot(opt_vec)|> display
plot(vmgtt[:],vmgtv[:])|> display
#, label = "randData", ylabel = "Y axis",color = :red, legend = :topleft, grid = :off, xlabel = "Numbers Rand")
#p = twiny()
#plot!(p,vec, label = "log(x)", legend = :topright, box = :on, grid = :off, xlabel = "Log values") |> display
#return fitness
#end
#end
#break
#end


# Imports Sundials and DifferentialEquations to access the CVODE solvers
# Imports DynamicBoundspODEsIneq to use DifferentialInequality integrator
# Imports DynamicBoundsBase for API and problem description functions.
using DynamicBoundspODEsIneq, DynamicBoundsBase, Sundials, DifferentialEquations

# Create problem
y0(u) = [1.2; 1.1]
function f!(dy, y, u, t)
    dy[1] = u[2]*y[1]*(one(typeof(u[1])) - y[2])*u[1]
    dy[2] = u[2]*y[2]*(y[1] - one(typeof(u[1])))
    nothing
end
tspan = (0.0, 1.0)

u_l = Float64[1.1; 2.95]
u_u = Float64[2.2; 3.05]

prob = DynamicBoundsBase.ODERelaxProb(f!, tspan, y0, u_l, u_u)
set!(prob, SupportSet([i for i in 0.0:0.01:1.0]))

# Creates the integrator specifying that relaxations and subgradients
# thereof should be computed.
integrator = DifferentialInequality(prob,
                                    calculate_relax = false,
                                    calculate_subgradient = false)

# defines a point in p at which to compute relaxations
ratio = rand(2)
pstar = u_l.*ratio .+ u_u.*(1.0 .- ratio)
setall!(integrator, ParameterValue(), pstar)

# Computes the relaxation and subgradients
relax!(integrator)

# Computes a trajectory of the pODEs at pstar
integrate!(integrator)