function write_ine(str::String, A::Matrix{Int}, b::Vector{Int})
    fp = open(str, "w")
    println(fp, "H-representation")
    println(fp, "begin")
    m, n = size(A)
    @assert m == length(b)
    println(fp, "$m $(n+1) integer")
    for i in 1:m
        println(fp, join([b[i] A[i,:]], " "))
    end
    println(fp, "end")
    close(fp)
    nothing
end

function write_ine(str::String, A::Matrix{Float64}, b::Vector{Float64})
    fp = open(str, "w")
    println(fp, "H-representation")
    println(fp, "begin")
    m, n = size(A)
    @assert m == length(b)
    println(fp, "$m $(n+1) real")
    for i in 1:m
        @printf(fp, "%E ", b[i])
        for j in 1:n
            @printf(fp, "%E ", A[i,j])
        end
        println(fp)
    end
    println(fp, "end")
    close(fp)
    nothing
end

function write_ext(str::String, ext::Vector)
    fp = open(str, "w")
    println(fp, "V-representation")
    println(fp, "begin")
    m = length(ext)
    n = length(ext[1])
    println(fp, "$m $n real")
    for i in 1:m
        vv = ext[i]
        @assert length(vv) == n
        println(fp, join(vv, " "))
    end
    println(fp, "end")
    println(fp, "hull")
    close(fp)
    nothing
end

function read_ext(fname::String)
    fp = open(fname, "r")
    vertices = Vector[]
    rays = Vector[]

    while true
        line = strip(readline(fp), '\n')
        line == "begin" && break
    end
    line = strip(readline(fp), '\n')
    (m,n,typ) = filter(x->!isempty(x), split(line, " "))
    line = strip(readline(fp), '\n')
    elem = []
    while line != "end"
        res  = filter(x->!isempty(x), split(line, " "))
        nums = map(float, res)
        try
            elem = convert(Vector{Int}, nums)
        catch
            elem = nums
        end
        if elem[1] == 1 # vertex
            push!(vertices, elem[2:end])
        else
            push!(rays, elem[2:end])
        end
        line = strip(readline(fp), '\n')
    end
    close(fp)
    return vertices, rays
end

function read_ine(fname::String)
    fp = open(fname, "r")

    while true
        line = strip(readline(fp), '\n')
        line == "begin" && break
    end
    line = strip(readline(fp), '\n')
    (mm,nn,typ) = filter(x->!isempty(x), split(line, " "))
    m,n = int(mm), int(nn)
    line = strip(readline(fp), '\n')
    A = Array(Float64,0,n-1)
    b = Float64[]
    while line != "end"
        res  = filter(x->!isempty(x), split(line, " "))
        elem = map(float, res)
        push!(b, elem[1])
        A = [A; -elem[2:end]']
        line = strip(readline(fp), '\n')
    end
    @assert size(A) == (m,n-1)
    close(fp)
    return b, A
end


filename() = "day15.txt"

"""
Read data and return a pair of data structures:
1. A dictionary of tuples where K=spoken number, V=(lastpos,recentpos)
2. An array of spoken numbers
"""
function read_data()
    seq = parse.(Int, split(readline(filename()), ","))
    return Dict(v => (0, k) for (k,v) in enumerate(seq)), seq
end

"""
Solve the puzzle with a simple loop. Since we always spoken the last
spoken number, the Dict is guaranteed to have a value.

If the first value of the tuple is 0, then it is a starting value and say 0;
otherwise, say the difference of values in that tuple. Then, update the
Dict for the spoken number with a new tuple.
"""
function solve(year)
    dct, seq = read_data()
    start = length(seq) + 1
    spoken = seq[end]
    for i in start:year
        tup = dct[spoken]
        val = tup[1] == 0 ? 0 : tup[2] - tup[1]
        if haskey(dct, val)
            dct[val] = (dct[val][2], i)
        else
            dct[val] = (0, i)
        end
        spoken = val
    end
    return spoken
end

part1() = solve(2020)
part2() = solve(30000000)

#=
julia> @benchmark part2()
BenchmarkTools.Trial:
  memory estimate:  395.84 MiB
  allocs estimate:  84
  --------------
  minimum time:     2.576 s (0.32% GC)
  median time:      2.583 s (1.69% GC)
  mean time:        2.583 s (1.69% GC)
  maximum time:     2.591 s (3.05% GC)
  --------------
  samples:          2
  evals/sample:     1
=#

using Plots

function animation(year)
    anim = Animation()
    dct, seq = read_data()
    start = length(seq) + 1
    spoken = seq[end]
    plot(seq, background_color = :black, legend = :none,
        xlabel = "Year", ylabel = "Number Spoken",
        title = "Advent of Code (Day 15)",
        size = (500,300),
        xlims = (1, 2100),
        ylims = (1, 1600))
    for i in start:year
        tup = dct[spoken]
        val = tup[1] == 0 ? 0 : tup[2] - tup[1]
        if haskey(dct, val)
            dct[val] = (dct[val][2], i)
        else
            dct[val] = (0, i)
        end
        spoken = val
        push!(seq, spoken)
        if i % 20 == 0
            plot!(seq[1:i])
            frame(anim)
        end
    end
    return gif(anim, "day15_anim.gif"; fps = 25)
end


using Distributions:Random
using AdaptiveABC
using SequentialLOB
using Random
using Distributions
using Optim
using NLopt
using JLD2
using Plots

# Set Simulator Parameters and Seed
D = 1.0
σ = 1.5
nu = 0.5
μ = 1.0
parameter_true = [D, σ, nu, μ]
parameter_names = ["D", "σ", "nu", "μ"]
plotting_parameter_names = ["D", "σ", "ν", "μ"]
n_replications = 10
Random.seed!(8929200)
# Constants
num_paths = 1; M = 400 ; T = 2299 ; p₀ = 238.75 ; L = 200
Δx = L / M ; λ = 1.0 ; Δt = (Δx^2) / (2.0 * D) ; α_lob = 0.0
println("Δx = $Δx and Δt = $Δt")

# Create model, simulate to obtain observation vector and then calculate summary statistics
true_lob_model = SLOB(num_paths,
    T, p₀, M, L, D, σ, nu, α_lob, SourceTerm(λ, μ))
true_lob_price_path = true_lob_model(7136)
true_lob_log_returns = diff(log.(true_lob_price_path[:,1]))
true_lob_summary_stats = get_summary_stats(true_lob_log_returns)

# plt = plot(
#     1:T + 1, true_lob_price_path[:,1], legend=false,
#     xlab="Time", ylab="Mid-Price",
# )

# Define Summary function used in AdaptiveABC. Takes in a vector of parameters and outputs a success boolean and the
# simulated summart statistics
function summary_fn(parameters, n_summary_stats, n_replications)
    try
        D, σ, nu, μ = parameters
        model = SLOB(num_paths, T, p₀, M, L, D, σ, nu, α_lob, SourceTerm(λ, μ))
        summary_stats = Array{Float64,2}(undef, n_summary_stats, n_replications)
        for i in 1:n_replications
            sim_price_path = model()
            sim_obs = diff(log.(sim_price_path[:, 1]))
            summary_stats[:, i] = get_summary_stats(true_lob_log_returns, sim_obs)
        end
        return true, summary_stats
    catch e
        return false, zeros(n_summary_stats, n_replications)
    end
end

# Setup for Calibration of Parameters through AdaptiveABC
# Define Prior distribution using Prior struct
prior = Prior([
    Uniform(0.5, 3.0), # D
    Uniform(0.1, 3.0), # σ
    Uniform(0.05, 0.95), # nu
    Uniform(0.1, 3.0) # μ
])

#####
# Calibration Technique: ABC Rejection BBWM
#####

# Distance Function to measure distance between observed summary stats and simulated summary stats
boot_weight_matrix = BlockBootstrapWeightMatrix(122477, vcat([0], true_lob_log_returns), get_summary_stats, 100, 10_000)
weighted_bootstrap = WeightedBootstrap(vcat([0], true_lob_log_returns), get_summary_stats, boot_weight_matrix)

abc_input_wb = ABCInput(
    prior,
    parameter_names,
    true_lob_summary_stats,
    summary_fn,
    17,
    weighted_bootstrap
)

abc_reject_wb = JLD2.load_object("$(dirname(@__FILE__))/abc_reject_wb.jld2")

# abc_reject_wb = ABCRejection(
#     abc_input_wb,
#     3_000,
#     n_replications,
#     100.0,
#     parallel=true,
#     seed=2517413766247174462
# )

posterior_means = AdaptiveABC.parameter_means(abc_reject_wb)
println()
println("ABC rejection BBWM: D, σ, nu, μ")
print(round.(posterior_means[:,end], digits=3))
println()

# JLD2.save_object("$(dirname(@__FILE__))/abc_reject_wb.jld2", abc_reject_wb)

# ABC rejection BBWM: D, σ, nu, μ
# [1.551, 1.821, 0.503, 1.496]

param_inds = [1,2,3,4]
plt = plot(abc_reject_wb, iteration_colours=cgrad(:blues, 5, categorical=true),
    iterations=[1], params_true=parameter_true[param_inds],
    prior_dists=abc_input_wb.prior.distribution[param_inds],
    param_inds=param_inds, param_names=plotting_parameter_names[param_inds])

savefig(plt, "$(dirname(@__FILE__))/abc_reject_wb.pdf")
plt

#####
# Calibration Technique: ABC Rejection MADWE
#####
abc_input_we = ABCInput(
    prior,
    parameter_names,
    true_lob_summary_stats,
    summary_fn,
    17,
    WeightedEuclidean(true_lob_log_returns, get_summary_stats, ones(17), "MAD")
)

abc_reject_we = JLD2.load_object("$(dirname(@__FILE__))/abc_reject_we.jld2")
new_cutoff = 25.0

abc_reject_we.parameters = abc_reject_we.parameters[:, abc_reject_we.distances .<= new_cutoff]
abc_reject_we.n_successes = sum(abc_reject_we.distances .<= new_cutoff)
abc_reject_we.summary_stats = abc_reject_we.summary_stats[:, abc_reject_we.distances .<= new_cutoff, :]
abc_reject_we.weights = abc_reject_we.weights[abc_reject_we.distances .<= new_cutoff]

abc_reject_we.distances = abc_reject_we.distances[abc_reject_we.distances .<= new_cutoff]
# abc_reject_we = ABCRejection(
#     abc_input_we,
#     3_000,
#     n_replications,
#     23.0,
#     parallel=true,
#     seed=738173189
# )
posterior_means = AdaptiveABC.parameter_means(abc_reject_we)
println()
println("ABC rejection MADWE: D, σ, nu, μ")
print(round.(posterior_means[:,end], digits=3))
println()

# JLD2.save_object("$(dirname(@__FILE__))/abc_reject_we.jld2", abc_reject_we)

# ABC rejection MADWE: D, σ, nu, μ
# [1.747, 1.536, 0.51, 1.54]

param_inds = [1,2,3,4]
plt = plot(abc_reject_we, iteration_colours=cgrad(:blues, 5, categorical=true),
    iterations=[1], params_true=parameter_true[param_inds],
    prior_dists=abc_input_we.prior.distribution[param_inds],
    param_inds=param_inds, param_names=plotting_parameter_names[param_inds])

savefig(plt, "$(dirname(@__FILE__))/abc_reject_we.pdf")
plt

#####
# Calibration Technique: ABC-PMC BBWM
#####

# Distance Function to measure distance between observed summary stats and simulated summary stats
boot_weight_matrix = BlockBootstrapWeightMatrix(122477, vcat([0], true_lob_log_returns), get_summary_stats, 100, 10_000)
weighted_bootstrap = WeightedBootstrap(vcat([0], true_lob_log_returns), get_summary_stats, boot_weight_matrix)

abc_input_wb = ABCInput(
    prior,
    parameter_names,
    true_lob_summary_stats,
    summary_fn,
    17,
    weighted_bootstrap
)

abc_pmc_out_wb = JLD2.load_object("$(dirname(@__FILE__))/abc_pmc_out_wb.jld2")
# abc_pmc_out_wb = ABC_PMC(
#     abc_input_wb,
#     100,
#     n_replications,
#     0.25,
#     10_000,
#     10;
#     parallel=true,
#     batch_size=250,
#     seed=918731553
# )
posterior_means = AdaptiveABC.parameter_means(abc_pmc_out_wb)

println()
println("ABC-PMC BBWM: D, σ, nu, μ")
print(round.(posterior_means[:,end], digits=3))
println()

# JLD2.save_object("$(dirname(@__FILE__))/abc_pmc_out_wb.jld2", abc_pmc_out_wb)

# ABC-PMC BBWM: D, σ, nu, μ
# [1.086, 1.596, 0.457, 1.279]

param_inds = [1,2,3,4]
plt = plot(abc_pmc_out_wb, iteration_colours=cgrad(:blues, 5, categorical=true),
    iterations=[1, abc_pmc_out_wb.n_iterations ÷ 2, abc_pmc_out_wb.n_iterations], params_true=parameter_true[param_inds],
    prior_dists=abc_input_wb.prior.distribution[param_inds],
    param_inds=param_inds, param_names=plotting_parameter_names[param_inds])
plt
savefig(plt, "$(dirname(@__FILE__))/abc_pmc_wb.pdf")

#####
# Calibration Technique: ABC-PMC MADWE
#####
abc_input_we = ABCInput(
    prior,
    parameter_names,
    true_lob_summary_stats,
    summary_fn,
    17,
    WeightedEuclidean(true_lob_log_returns, get_summary_stats, ones(17), "MAD")
)

abc_pmc_out_we = JLD2.load_object("$(dirname(@__FILE__))/abc_pmc_out_we.jld2")
# abc_pmc_out_we = ABC_PMC(
#     abc_input_we,
#     100,
#     n_replications,
#     0.15,
#     10_000,
#     10;
#     parallel=true,
#     batch_size=250,
#     seed=83912738712
# )
posterior_means = AdaptiveABC.parameter_means(abc_pmc_out_we)
println("")
println("ABC-PMC MADWE: D, σ, nu, μ")
print(round.(posterior_means[:,end], digits=3))
println("")
# ABC-PMC MADWE: D, σ, nu, μ
# [1.182, 1.767, 0.566, 1.75]

# JLD2.save_object("$(dirname(@__FILE__))/abc_pmc_out_we.jld2", abc_pmc_out_we)

param_inds = [1,2,3,4]
plt = plot(abc_pmc_out_we, iteration_colours=cgrad(:blues, 5, categorical=true),
    iterations=[1, abc_pmc_out_we.n_iterations ÷ 2, abc_pmc_out_we.n_iterations], params_true=parameter_true[param_inds],
    prior_dists=abc_input_we.prior.distribution[param_inds],
    param_inds=param_inds, param_names=plotting_parameter_names[param_inds])
savefig(plt, "$(dirname(@__FILE__))/abc_pmc_we.pdf")
plt

#####
# Calibration Technique: Nelder-Mead BBWM
#####

# Distance Function to measure distance between observed summary stats and simulated summary stats
boot_weight_matrix = BlockBootstrapWeightMatrix(122477, vcat([0], true_lob_log_returns), get_summary_stats, 100, 10_000)
weighted_bootstrap = WeightedBootstrap(vcat([0], true_lob_log_returns), get_summary_stats, boot_weight_matrix)

function f(x, weight, reps)
    success, sum_stats = summary_fn(x, 17, reps)
    return weight(sum_stats)
end

f_wb(x, grad) = f(x, weighted_bootstrap, n_replications)
# D, σ, nu, μ
lower = [0.5, 0.1, 0.05, 0.1]
upper = [3.0, 3.0, 0.95, 3.0]

nm_reps = 10
results = zeros(4, nm_reps)
Random.seed!(736161311)
for i in 1:nm_reps
    println("Iteration $i")
    opt = Opt(:LN_NELDERMEAD, 4)
    opt.lower_bounds = lower
    opt.upper_bounds = upper
    opt.xtol_rel = 1e-6
    opt.min_objective = f_wb
    init_params = rand(Int.(rand(MersenneTwister(), UInt32)), prior)

    (minf, minx, ret) = NLopt.optimize(opt, init_params)

    results[:,i] = minx
end
println()
println("Nelder-Mead BBWM: ")
print(round.(mean(results, dims=2), digits=3))
print()
results
println()
# Nelder-Mead BBWM:
# [1.778; 1.944; 0.719; 1.756]

#####
# Calibration Technique: Nelder-Mead MADWE
#####

weighted_mad = WeightedEuclidean(true_lob_log_returns, get_summary_stats, ones(17), "MAD")
sim_sum_stats = zeros(17, 100, n_replications)
Random.seed!(1773616333)
for i in 1:size(sim_sum_stats, 2)
    params = rand(Int.(rand(MersenneTwister(), UInt32)), prior)
    success, sum_stats = summary_fn(params, 17, n_replications)
    sim_sum_stats[:,i,:] = sum_stats
end

inv_weights = [AdaptiveABC.MAD(sim_sum_stats[i, :, :]) for i in 1:17]  # For each statistic, calculate the MAD across all n_sim, n_rep values
weights = 1.0 ./ inv_weights

weighted_mad.weights = weights
f_mad(x, grad) = f(x, weighted_mad, n_replications)
# D, σ, nu, μ
lower = [0.5, 0.1, 0.05, 0.1]
upper = [3.0, 3.0, 0.95, 3.0]

nm_reps = 10
results = zeros(4, nm_reps)
Random.seed!(9918133091)
for i in 1:nm_reps
    println("Iteration $i")
    opt = Opt(:LN_NELDERMEAD, 4)
    opt.lower_bounds = lower
    opt.upper_bounds = upper
    opt.xtol_rel = 1e-6
    opt.min_objective = f_mad
    init_params = rand(Int.(rand(MersenneTwister(), UInt32)), prior)

    (minf, minx, ret) = NLopt.optimize(opt, init_params)

    results[:,i] = minx
end
println()
println("Nelder-Mead MADWE: ")
print(round.(mean(results, dims=2), digits=3))
results
println()
# Nelder-Mead MADWE:
# [2.373; 1.924; 0.52; 1.295]


# This file is part of IntegerSequences.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module Kolakoski

using Nemo

export ModuleKolakoski
export KolakoskiList, C000002, I000002, L000002

"""

* KolakoskiList, C000002, I000002, L000002
"""
const ModuleKolakoski = ""

"""

Generate the Kolakoski sequence which is the unique sequence over the alphabet ``{1, 2}`` starting with ``1`` and having the sequence of run lengths identical with itself.
"""
C000002() =
    Channel(csize = 10) do c
        x = y = Int(-1)

        while true
            put!(c, [2, 1][(x&1)+1])
            f = y & ~(y + 1)
            x = xor(x, f)
            y = (y + 1) | (f & (x >> 1))
        end
    end

struct KolakoskiSeq
    count::Int
    ch::Channel
    KolakoskiSeq(count) = new(count, C000002())
end

function Base.iterate(I::KolakoskiSeq)
    if I.count == 0
        close(I.ch)
        return nothing
    end
    (take!(I.ch), (0))
end

function Base.iterate(I::KolakoskiSeq, S)
    j = S[1] + 1
    if I.count == j
        close(I.ch)
        return nothing
    end
    (take!(I.ch), (j))
end

Base.length(I::KolakoskiSeq) = I.count
Base.eltype(I::KolakoskiSeq) = Int

"""

Iterate over the first ``n`` Kolakoski numbers.
"""
I000002(n::Int) = KolakoskiSeq(n)

"""

Return the list of the first ``n`` terms of the Kolakoski sequence.
"""
function KolakoskiList(len::Int)
    len ≤ 0 && return []
    generator = C000002()
    L = [take!(generator) for _ ∈ 1:len]
    close(generator)
    return L
end

"""

Return the list of the first ``n`` terms of the Kolakoski sequence.
"""
L000002(n::Int) = KolakoskiList(n)

#START-TEST-########################################################

using Test

function test()

    @testset "Kolakoski" begin
        K = KolakoskiList(100)
        @test K[1] == 1
        @test K[33] == 2
        @test K[72] == 2

        generator = C000002()
        for n ∈ [1, 33, 72]
            k = take!(generator)
            @test K[n] == k
        end
        close(generator)
    end
end

function demo()
    println(KolakoskiList(20))

    generator = C000002()
    o = e = 0
    for n ∈ 1:80
        take!(generator) == 1 ? o += 1 : e += 1
        print(o - e, " ")
    end
    println()
    close(generator)

    for f ∈ I000002(20)
        print(f, ", ")
    end
    println()
    print(L000002(20))
    println()
end

"""

I000002(100000) ::
    0.000035 seconds (31 allocations: 2.969 KiB)
KolakoskiList(10000) ::
    0.086202 seconds (120.03 k allocations: 10.226 MiB, 17.61% gc time)
"""
function perf()
    @time I000002(100000)
    @time KolakoskiList(100000)
end

function main()
    test()
    demo()
    perf()
end

main()

end # module

#=

[1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2, 1]

1 0 -1 0 1 0 1 0 -1 0 -1 -2 -1 0 -1 0 1 0 -1 0 -1 0 1 0 1 0 -1 0
1 0 1 2 1 2 1 0 1 0-1 0 1 0 1 0 -1 0 -1 0 1 0 1 2 1 0 1 0 -1 0 1
0 1 0 -1 0 -1 -2 -1 0 -1 0 1 0 1 0 -1 0 -1 0 1 0

1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2, 1,

[1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2, 1]

  0.000035 seconds (31 allocations: 2.969 KiB)
  0.086202 seconds (120.03 k allocations: 10.226 MiB, 17.61% gc time)

=#


function test_discrete_sets(T)
    n = 10
    s1 = DiscreteVectorDictionary{T}(n)
    @test domaintype(s1) == Int
    @test codomaintype(s1) == T
    @test length(s1) == n
    @test isdiscrete(s1)

    s2 = resize(s1, 2*n)
    @test domaintype(s2) == Int
    @test codomaintype(s2) == T
    @test length(s2) == 2*n

    # Support
    @test in_support(s1, 1, 4)
    @test !in_support(s1, 1, 0)
    @test in_support(s1, 1, length(s1))
    @test !in_support(s1, 1, length(s1)+1)
    @test !in_support(s1, 1, 2.5)

    @test support(s1) == ClosedInterval{Int}(1, length(s1))

    # Evaluation
    @test eval_element(s1, 1, 1) == one(T)
    @test eval_element(s1, 1, 2) == zero(T)
    @test eval_element(s1, n, n) == one(T)

    t1 = DiscreteArrayDictionary{T}((10,10))
    @test codomaintype(t1) == T
    @test domaintype(t1) == ProductIndex{2}
    @test size(t1) == (10,10)

    @test in_support(t1, (1,1), 5)
    @test !in_support(t1, (1,1), 101)
    @test in_support(t1, (1,1), (3,3))
    @test !in_support(t1, (1,1), (11,3))
    @test in_support(t1, (1,1), CartesianIndex(5,3))
    @test !in_support(t1, (1,1), CartesianIndex(5,11))

    t2 = resize(t1, (20,20))
    @test codomaintype(t2) == T
    @test domaintype(t2) == ProductIndex{2}
    @test size(t2) == (20,20)

    # Evaluation
    @test eval_element(t1, 1, 1) == one(T)
    @test eval_element(t1, 1, (1,1)) == one(T)
    @test eval_element(t1, 1, CartesianIndex(1,1)) == one(T)
    @test eval_element(t1, (1,1), 1) == one(T)
    @test eval_element(t1, CartesianIndex(1,1), 1) == one(T)
    @test eval_element(t1, CartesianIndex(1,1), (1,1)) == one(T)

    @test eval_element(t1, 2, 5) == zero(T)
    @test eval_element(t1, (2,1), 5) == zero(T)
    @test eval_element(t1, (2,1), CartesianIndex(3,3)) == zero(T)

end


using Random, LinearAlgebra

import DifferentialEquations: ODEProblem, solve
import Distributions: MvNormal, rand

export generate_data

"""
    generate_data( ssm, u0; seed=42)

from StateSpace generate:
 - true state (xt)
 - partial/noisy observations (yo)
 - catalog
"""
function generate_data(ssm::StateSpaceModel, u0::Vector{Float64}, seed = 42)

    rng = MersenneTwister(seed)

    try
        @assert ssm.dt_states < ssm.dt_obs
    catch
        @error " ssm.dt_obs must be bigger than ssm.dt_states"
    end

    try
        @assert mod(ssm.dt_obs, ssm.dt_states) == 0.0
    catch
        @error " ssm.dt_obs must be a multiple of ssm.dt_states "
    end

    tspan = (0.0, ssm.nb_loop_test)

    prob = ODEProblem(ssm.model, u0, tspan, ssm.params)

    # generSate true state (xt)
    sol = solve(prob, saveat = ssm.dt_states * ssm.dt_integration)
    xt = TimeSeries(sol.t, sol.u)

    # generate  partial/noisy observations (yo)
    nt = xt.nt
    nv = xt.nv

    yo = TimeSeries(xt.t, xt.u .* NaN)
    step = ssm.dt_obs ÷ ssm.dt_states
    nt = length(xt.t)

    d = MvNormal(ssm.sigma2_obs .* Matrix(I, nv, nv))
    ε = rand(rng, d, nt)
    for j = 1:step:nt
        for i in ssm.var_obs
            yo.u[j][i] = xt.u[j][i] + ε[i, j]
        end
    end

    # generate catalog
    u0 = last(sol)
    tspan = (0.0, ssm.nb_loop_train)
    prob = ODEProblem(ssm.model, u0, tspan, ssm.params)
    sol = solve(prob, saveat = ssm.dt_integration)
    n = length(sol.t)

    catalog_tmp = sol.u

    xt, yo, Catalog(hcat(catalog_tmp...), ssm)

end


# Functions to estimate the derivative(s) of the mean spectrum (probably could do better w/ spline or GP)
function calc_deriv_simple{T}(x::AbstractArray{T,1})
  @assert length(x)>=3
  dx = similar(x)
  dx[1] = x[2]-x[1]
  dx[end] = x[end]-x[end-1]
  for i in 2:(length(x)-1)
     dx[i] = (x[i+1]-x[i-1])/2
  end
  return dx
end

function calc_deriv2_simple{T}(x::AbstractArray{T,1})
  @assert length(x)>=3
  dx2 = similar(x)
  dx2[1] = x[3]-2*x[2]+x[1]
  dx2[end] = x[end]-2*x[end-1]+x[end-2]
  for i in 2:(length(x)-1)
     dx2[i] = (x[i+1]+x[i-1]-2*x[i])
  end
  return dx2
end

function calc_doppler_component_simple{T}(lambda::AbstractArray{T,1}, flux::AbstractArray{T,1})
  @assert length(lambda) == length(flux)
  dlambdadpix = calc_deriv_simple(lambda);
  dfluxdpix = calc_deriv_simple(flux);
  doppler_basis = dfluxdpix.*(lambda./dlambdadpix)
end

function calc_doppler_component_simple{T}(lambda::AbstractArray{T,1}, flux::AbstractArray{T,2})
  doppler_basis = calc_doppler_component_simple(lambda,vec(mean(flux,2)))
end

function calc_doppler_quadratic_term_simple{T}(lambda::AbstractArray{T,1}, flux::AbstractArray{T,1})
  @assert length(lambda) == length(flux)
  dlambdadpix = calc_deriv_simple(lambda);
  d2fluxdpix2 = calc_deriv2_simple(flux);
  doppler_quad_term = 0.5*d2fluxdpix2.*(lambda.^2./dlambdadpix.^2)
end

function calc_doppler_quadratic_term_simple{T}(lambda::AbstractArray{T,1}, flux::AbstractArray{T,2})
  doppler_quad_term = calc_doppler_quadratic_term_simple(lambda,vec(mean(flux,2)))
end


"Best Effort Ranking"
module BestEffortRanking

using ..AExpressions

export get_fitness

"""p is the synthesized program
e is the list of examples to test the model on(could be a starting frame and
then a number of iterations/sequence of events and then ending frame and would
say is correct if the ending frames match)
a is the aexpr representation of the program
the closer to 0 the value the better the program"""
function get_fitness(p, e, a, distance_coefficient=3.0, size_coefficient=1.0)
  return (distance_coefficient * get_distance(p, e) +
  size_coefficient * sizeA(a))
end

"""for examples that the model gets wrong find the objects in the expected result
and take the euclidean distance of each object.
return the average of (sum of euclidean distances)/(max possible sum of euclidean distances)"""
function get_distance(p, e)
  max_distance = sqrt(15*15 + 15*15)
  overall_distance = 0
  max_possible_distance = 0

  for index = 1:length(e)
    expected, steps = e[index]
    state = p.init(steps[1]...)
    for i = 2:length(steps)
      state = p.next(state, steps[i]...)
    end
    actual_cells = p.render(state.scene)
    expected_cells = expected(p)

    overall_distance += compare_states(expected_cells, actual_cells)
    if length(actual_cells) > length(expected_cells)
      longer = length(actual_cells)
    else
      longer = length(expected_cells)
    end
    max_possible_distance += longer * max_distance
  end
  overall_distance/max_possible_distance
end

function compare_states(expected_cells, actual_cells)::Float64
  max_distance = sqrt(15*15 + 15*15)
  for cells in [expected_cells, actual_cells]
    sort(cells, by = x -> x.position.y)
    sort!(cells, by=x -> x.position.x)
    sort!(cells, by=x -> x.color)
  end
  index_expected = 1
  index_actual = 1
  distance = 0
  while index_expected <= length(expected_cells) && index_actual <= length(actual_cells)
    ex_cell = expected_cells[index_expected]
    ac_cell = actual_cells[index_actual]

    if ex_cell.color == ac_cell.color
      distance += euclidean_distance(ex_cell.position.x, ex_cell.position.y, ac_cell.position.x, ac_cell.position.y)
      index_expected += 1
      index_actual += 1
    elseif ex_cell.color > ac_cell.color
      distance += max_distance
      index_actual += 1
    else
      distance += max_distance
      index_expected += 1
    end
  end
  extra_cells = length(actual_cells) - index_actual + length(expected_cells) - index_expected + 2

  distance += abs(extra_cells) * max_distance
  distance
end

function euclidean_distance(x1, y1, x2, y2)
  sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2))
end
end


module TestWiringDiagramAlgorithms

using Test
using Catlab.Doctrines
using Catlab.WiringDiagrams

A, B, C, D = Ob(FreeBiproductCategory, :A, :B, :C, :D)
I = munit(FreeBiproductCategory.Ob)
f = Hom(:f, A, B)
g = Hom(:g, B, C)
h = Hom(:h, C, D)

# Diagonals and codiagonals
###########################

junction_diagram(args...) = singleton_diagram(Junction(args...))

# Add junctions for copies.
d = to_wiring_diagram(compose(f, mcopy(B)))
original = copy(d)
junctioned = compose(to_wiring_diagram(f), junction_diagram(:B,1,2))
@test add_junctions!(d) == junctioned
@test rem_junctions!(d) == original

d = to_wiring_diagram(compose(mcopy(A), otimes(f,f)))
original = copy(d)
junctioned = compose(junction_diagram(:A,1,2), to_wiring_diagram(otimes(f,f)))
@test add_junctions!(d) == junctioned
@test rem_junctions!(d) == original

# Add junctions for merges.
d = to_wiring_diagram(compose(mmerge(A), f))
original = copy(d)
junctioned = compose(junction_diagram(:A,2,1), to_wiring_diagram(f))
@test is_permuted_equal(add_junctions!(d), junctioned, [2,1])
@test rem_junctions!(d) == original

d = to_wiring_diagram(compose(otimes(f,f), mmerge(B)))
original = copy(d)
junctioned = compose(to_wiring_diagram(otimes(f,f)), junction_diagram(:B,2,1))
@test is_permuted_equal(add_junctions!(d), junctioned, [2,3,1])
@test rem_junctions!(d) == original

# Add junctions for deletions.
d = to_wiring_diagram(compose(f, delete(B)))
original = copy(d)
junctioned = compose(to_wiring_diagram(f), junction_diagram(:B,1,0))
@test add_junctions!(d) == junctioned
@test rem_junctions!(d) == original

# Add junctions for creations.
d = to_wiring_diagram(compose(create(A), f))
original = copy(d)
junctioned = compose(junction_diagram(:A,0,1), to_wiring_diagram(f))
@test is_permuted_equal(add_junctions!(d), junctioned, [2,1])
@test rem_junctions!(d) == original

# Add junctions for copies, merges, deletions, and creations, all at once.
d = to_wiring_diagram(compose(create(A),f,mcopy(B),mmerge(B),g,delete(C)))
original = copy(d)
junctioned = compose(
  junction_diagram(:A,0,1),
  to_wiring_diagram(f),
  junction_diagram(:B,1,2),
  junction_diagram(:B,2,1),
  to_wiring_diagram(g),
  junction_diagram(:C,1,0)
)
d = add_junctions!(d)
# XXX: An isomorphism test would be more convenient.
perm = [ findfirst([b] .== boxes(d)) for b in boxes(junctioned) ]
@test is_permuted_equal(d, junctioned, perm)
@test rem_junctions!(d) == original

# Normalize copies.
d = to_wiring_diagram(compose(mcopy(A), otimes(f,f)))
normalized = to_wiring_diagram(compose(f, mcopy(B)))
@test normalize_copy!(d) == normalized

d = to_wiring_diagram(compose(f, mcopy(B), otimes(g,g)))
normalize_copy!(d)
normalized = to_wiring_diagram(compose(f, g, mcopy(C)))
perm = sortperm(boxes(d); by=box->box.value)
@test is_permuted_equal(d, normalized, perm)

d = to_wiring_diagram(compose(mcopy(A), otimes(f,f), otimes(g,g)))
normalize_copy!(d)
perm = sortperm(boxes(d); by=box->box.value)
@test is_permuted_equal(d, normalized, perm)

# Normalize deletions.
d = to_wiring_diagram(f)
@test normalize_delete!(d) == to_wiring_diagram(f)

d = WiringDiagram(I,I)
add_box!(d, f)
@test normalize_delete!(d) == WiringDiagram(I,I)

d = WiringDiagram(A, B)
fv = add_box!(d, f)
gv = add_box!(d, g)
hv = add_box!(d, h)
add_wires!(d, [
  (input_id(d),1) => (fv,1),
  (fv,1) => (gv,1),
  (gv,1) => (hv,1),
  (fv,1) => (output_id(d),1),
])
@test normalize_delete!(d) == to_wiring_diagram(f)

# Normalize wiring diagrams representing morphisms in a cartesian category.
d = to_wiring_diagram(compose(
  mcopy(A),
  otimes(id(A),mcopy(A)),
  otimes(f,f,f),
  otimes(id(B), id(B), compose(g, delete(C)))
))
normalized = to_wiring_diagram(compose(f, mcopy(B)))
@test normalize_cartesian!(d) == normalized

# Layout
########

d = to_wiring_diagram(f)
@test crossing_minimization_by_sort(d, box_ids(d), sources=[input_id(d)]) == [1]

d = to_wiring_diagram(otimes(f,g))
@test crossing_minimization_by_sort(d, box_ids(d), sources=[input_id(d)]) == [1,2]

d = WiringDiagram(I,I)
fv1, fv2 = add_box!(d, f), add_box!(d, f)
gv1, gv2 = add_box!(d, g), add_box!(d, g)
add_wires!(d, [ Wire((fv1,1) => (gv1,1)), Wire((fv2,1),(gv2,1)) ])
@test crossing_minimization_by_sort(d, [gv1,gv2], sources=[fv1,fv2]) == [1,2]
@test crossing_minimization_by_sort(d, [fv1,fv2], targets=[gv1,gv2]) == [1,2]

end


using BenchmarkTools
using Random
using Distances, LinearAlgebra

const SUITE = BenchmarkGroup()

Random.seed!(1234)

dim = 50
N1 = 1000;
N2 = 500;
alpha = 2.0

X = rand(Float64, N1, dim)
Y = rand(Float64, N2, dim)

KXY = rand(Float64, N1, N2)
KX = rand(Float64, N1, N1)
sKX = Symmetric(rand(Float64, N1, N1))
kX = rand(Float64, N1)

include("kernelmatrix.jl")
include("MLKernels.jl")


using DiffEqFlux, GalacticOptim, OrdinaryDiffEq

#A desired MWE for now, not a test yet.

function rober(du,u,p,t)
  y₁,y₂,y₃ = u
  k₁,k₂,k₃ = p
  du[1] = -k₁*y₁ + k₃*y₂*y₃
  du[2] =  k₁*y₁ - k₃*y₂*y₃ - k₂*y₂^2
  du[3] =  y₁ + y₂ + y₃ - 1
  nothing
end
M = [1. 0  0
     0  1. 0
     0  0  0]
prob_mm = ODEProblem(ODEFunction(rober,mass_matrix=M),[1.0,0.0,0.0],(0.0,10.0),(0.04,3e7,1e4))
sol = solve(prob_mm,Rodas5(),reltol=1e-8,abstol=1e-8)

dudt2 = Chain(x -> x.^3,Dense(6,50,tanh),Dense(50,2))

ndae = NeuralDAE(dudt2, (u,p,t) -> [u[1] + u[2] + u[3] - 1], tspan, M, DImplicitEuler(),
                        differential_vars = [true,true,false])
truedu0 = similar(u₀)
f(truedu0,u₀,p,0.0)

ndae(u₀,truedu0,Float64.(ndae.p))

function predict_n_dae(p)
    ndae(u₀,p)
end

function loss(p)
    pred = predict_n_dae(p)
    loss = sum(abs2,sol .- pred)
    loss,pred
end

p = p .+ rand(3) .* p

optfunc = GalacticOptim.OptimizationFunction((x, p) -> loss(x), GalacticOptim.AutoZygote())
optprob = GalacticOptim.OptimizationProblem(optfunc, p)
res = GalacticOptim.solve(optprob, BFGS(initial_stepnorm = 0.0001))


@plottest begin
    # SRM0 params
    η₀ = 5.0
    τᵣ = 1.0

    # Input spike train params
    rate = 0.01
    T = 20
    ∂t = 0.01
    n = convert(Int, ceil(T / ∂t))

    srm = Neuron(QueuedSynapse(Synapse.Delta()), SRM0(η₀, τᵣ), Threshold.Poisson(5.0, 0.5, 0.1))
    input = ConstantRate(rate)
    spikes = excite!(srm, input, n)

    # callback to record voltages
    voltages = Float64[]
    record = function ()
        push!(voltages, getvoltage(srm))
    end

    # simulate
    output = simulate!(srm, n; dt = ∂t, cb = record, dense = true)

    # plot raster plot
    raster_plot = rasterplot(∂t .* spikes, ∂t .* output, label = ["Input", "Output"], xlabel = "Time (sec)",
                    title = "Raster Plot (δ response)")
    xlims!(0, T)

    # plot dense voltage recording
    plot(∂t .* collect(1:n), voltages,
        title = "SRM Membrane Potential with Varying Presynaptic Responses", xlabel = "Time (sec)", ylabel = "Potential (V)", label = "δ response")

    # resimulate using presynaptic response
    srm = Neuron(QueuedSynapse(Synapse.Alpha()), SRM0(η₀, τᵣ), Threshold.Poisson(5.0, 0.5, 0.1))
    voltages = Float64[]
    excite!(srm, spikes)
    simulate!(srm, n; dt = ∂t, cb = record, dense = true)

    # plot voltages with response function
    voltage_plot = plot!(∂t .* collect(1:n), voltages, label = "α response")
    xlims!(0, T)

    plot(raster_plot, voltage_plot, layout = grid(2, 1), xticks = 0:T)
end joinpath(datadir, "Srm0-Threshold-Test.png") !isci


abstract type GeometricStatement end

struct Hypothesis <: GeometricStatement
    points::OrderedDict{Symbol, PointStatus}
    constraints::Vector{GeometricConstraint}
end

function show(io::IO, hp::Hypothesis)
    println(io, "POINTS:")
    println(io, "------------")
    for (p, s) in hp.points
        println(io, p, " : ", s)
    end
    println(io, "")
    println(io, "HYPOTHESIS:")
    println(io, "------------")
    for (i, c) in enumerate(hp.constraints)
        println(io, "(", i, ") ", c)
    end
end

struct Thesis <: GeometricStatement
    constraints::Vector{GeometricConstraint}
end

function show(io::IO, th::Thesis)
    println(io, "THESIS:")
    println(io, "------------")
    for c in th.constraints
        println(io, c)
    end
end

# macros
"""
    @hp(block)

macro to construct the hypothesis of the theorem.

### Input

block -- An expression containing the hypothesis of the theorem, can be a single statement
         or a sequence of statements between `begin...end`.

### Output

An object of type `Hypothesis`.

### Examples

```jldoctest
julia> @hp O := Circle(A, B, C)
POINTS:
------------
A : free
B : free
C : free
O : dependent by (1)

HYPOTHESIS:
------------
(1) OA ≅ OB ≅ OC
```
"""
macro hp(block)
    _parse_hp(block)
end

"""
    @th(block)

macro to construct the thesis of the theorem.

### Input

block -- An expression containing the thesis of the theorem, can be a single statement
         or a sequence of statements between `begin...end`.

### Output

An object of type `Thesis`.

### Examples

```jldoctest
julia> @th Segment(A, O) ≅ Segment(O, C)
THESIS:
------------
AO ≅ OC
```
"""
macro th(block)
    _parse_th(block)
end

#####################
## Prover interface #
#####################

abstract type AbstractGeometricProver end
abstract type AbstractGeometricProof end

struct NoProof <: AbstractGeometricProof end
show(io::IO, ::NoProof) = println(io, "PROOF:\n------\nProof missing")
isproved(::NoProof) = false

"""
    prove(hp::Hypothesis, th::Thesis[, method=RittWuMethod])
    prove(theorem::Theorem[, method=RittWuMethod])

Proves a thereom with given hypothesis `hp` and thesis `th`.

### Input

- `hp::Hypothesis`                  -- hypothesis of the theorem
- `th::Thesis`                      -- thesis of the theorem
- `method<:AbstractGeometricProver` -- method to prove the theorem, default `RittWuMethod`.

Alternatively, instead of passing hypothesis and thesis as two distinct parameters, it is
possible to pass them together as a Theorem object.

### Output

A proof of the the theorem. The type of the output depends on the chosen method

### Algorithms

Currently the following provers are supported

- RittWuMethod

### Examples

```jldoctest
julia> hp = @hp D := Parallelogram(A, B, C, D)
POINTS:
------------
A : free
B : free
C : free
D : dependent by (1)

HYPOTHESIS:
------------
(1) ABCD parallelogram

julia> th = @th Segment(A, B) ≅ Segment(C, D)
THESIS:
------------
AB ≅ CD

julia> prove(hp, th)
Assigned coordinates:
---------------------
A = (0, 0)
B = (u₁, 0)
C = (u₂, u₃)
D = (x₁, x₂)

Goal 1: success

Nondegeneracy conditions:
-------------------------
```
"""
prove(hp::Hypothesis, th::Thesis) = prove(hp, th, RittWuMethod)

#####################
# Theorem interface #
#####################
struct Theorem{T<:AbstractGeometricProof}
    hp::Hypothesis
    th::Thesis
    p::T
end

function show(io::IO, thm::Theorem)
    show(io, thm.hp)
    println(io)
    show(io, thm.th)
    println(io)
    show(io, thm.p)
end

Theorem(hp::Hypothesis, th::Thesis) = Theorem(hp, th, NoProof())

prove(t::Theorem, prover=RittWuMethod) = Theorem(t.hp, t.th, prove(t.hp, t.th, prover))

isproved(t::Theorem) = isproved(t.p)


@inline function U!(k, U)
  @inbounds for r = 1:k
    U[1,r] = -r
    for j = 2:k
      U[j,r] = U[j-1,r] * ((j-1) - r)/j
    end
  end
  nothing
end

function R!(k, ρ, cache)
  @unpack R = cache
  @inbounds for r = 1:k
    R[1,r] = -r * ρ
    for j = 2:k
      R[j,r] = R[j-1,r] * ((j-1) - r * ρ)/j
    end
  end
  nothing
end

# This functions takes help of D2 array to create backward differences array D
# Ith row of D2 keeps Ith order backward differences (∇ⁱyₙ)

function backward_diff!(cache::OrdinaryDiffEqMutableCache, D, D2, k, flag=true)
  flag && copyto!(D[1], D2[1,1])
  for i = 2:k
    for j = 1:(k-i+1)
      @.. D2[i,j] = D2[i-1,j] - D2[i-1,j+1]
    end
    flag && copyto!(D[i], D2[i,1])
  end
end

function backward_diff!(cache::OrdinaryDiffEqConstantCache, D, D2, k, flag=true)
  flag && (D[1] = D2[1,1])
  for i = 2:k
    for j = 1:(k-i+1)
      D2[i,j] = D2[i-1,j] - D2[i-1,j+1]
    end
    flag && (D[i] = D2[i,1])
  end
end

# this function updates backward difference array D when stepsize gets change
# Formula -> D = D * (R * U)
# and it is taken from the paper -
# Implementation of an Adaptive BDF2 Formula and Comparison with the MATLAB Ode15s paper
# E. Alberdi Celaya, J. J. Anza Aguirrezabala, and P. Chatzipantelidis
function reinterpolate_history!(cache::OrdinaryDiffEqMutableCache, D, R, k)
  @unpack tmp = cache.nlsolver
  fill!(tmp,zero(eltype(D[1])))
  for j = 1:k
    for k = 1:k
      @. tmp += D[k] * R[k,j]
    end
    D[j] .= tmp
    fill!(tmp, zero(eltype(tmp)))
  end
end

function reinterpolate_history!(cache::OrdinaryDiffEqConstantCache, D, R, k)
  tmp = zero(D[1])
  for j = 1:k
    for k = 1:k
      tmp += D[k] * R[k,j]
    end
    D[j] = tmp
  end
end

function calc_R(ρ, k, ::Val{N}) where {N}
  R = zero(MMatrix{N,N,typeof(ρ)})
  @inbounds for r = 1:k
    R[1,r] = -r * ρ
    for j = 2:k
      R[j,r] = R[j-1,r] * ((j-1) - r * ρ)/j
    end
  end
  SArray(R)
end

function update_D!(D, dd, k)
  dd = _vec(dd)
  @views @.. D[:,k+2] = dd - D[:,k+1]
  @views @.. D[:,k+1] = dd
  for i in k:-1:1
    @views @.. D[:,i] = D[:,i] + D[:,i+1]
  end
  return nothing
end

const γₖ = @SVector[sum(1//j for j in 1:k) for k in 1:6]

error_constant(integrator, order) = error_constant(integrator, integrator.alg, order)
function error_constant(integrator, alg::QNDF, k)
  @unpack γₖ = integrator.cache
  κ = alg.kappa[k]
  κ*γₖ[k] + inv(k+1)
end

function compute_weights!(ts,k,weights)
  for i = 1:k+1
      weights[i] = one(eltype(weights))
      for j = 1:i-1
          weights[i] *= ts[i] - ts[j]
      end
      for j = i+1:k+1
          weights[i] *= ts[i] - ts[j]
      end
      weights[i] = 1/weights[i]
  end
end

function calc_Lagrange_interp(k,weights,t,ts,u_history,u::Number)
  if t in ts
    i = searchsortedfirst(ts,t,rev=true)
    return u_history[i]
  else
    for i in 1:k+1
      u += weights[i]/(t-ts[i])*u_history[i]
    end
    for i in 1:k+1
      u *= t-ts[i]
    end
  end
  u
end

function calc_Lagrange_interp(k,weights,t,ts,u_history,u)
  vc = _vec(u)
  if t in ts
    i = searchsortedfirst(ts,t,rev=true)
    @.. @views vc = u_history[:,i]
    return u
  else
    for i in 1:k+1
      @.. @views vc += weights[i]/(t-ts[i])*u_history[:,i]
    end
    for i in 1:k+1
      @.. u *= t-ts[i]
    end
  end
  u
end

function calc_Lagrange_interp!(k,weights,t,ts,u_history,u)
  vc = _vec(u)
  if t in ts
    i = searchsortedfirst(ts,t,rev=true)
    @.. @views vc = u_history[:,i]
  else
    for i in 1:k+1
      @.. @views vc += weights[i]/(t-ts[i])*u_history[:,i]
    end
    for i in 1:k+1
      @.. u *= t-ts[i]
    end
  end
end

#This code refers to https://epubs.siam.org/doi/abs/10.1137/S0036144596322507

function calc_finite_difference_weights(ts,t,order,::Val{N}) where {N}
  max_order = N
  c = zero(MMatrix{max_order+1,max_order+1,eltype(ts)})
  c1 = one(t)
  c4 = ts[1] - t
  c[1,1] = one(t)
  for i in 2:order+1
    c2 = one(t)
    c5 = c4
    c4 = ts[i] - t
    @inbounds for j = 1:i-1
      c3 = ts[i] - ts[j]
      c2 *= c3
      if j == i-1
        for k in i:-1:2
          c[i,k] = c1*((k-1)*c[i-1,k-1]-c5*c[i-1,k])/c2
        end
        c[i,1] = zero(t)
      end
      for k in i:-1:2
        c[j,k] = (c4*c[j,k] - (k-1)*c[j,k-1])/c3
      end
      c[j,1] = zero(t)
    end
    c1 = c2
  end
  return SArray(c)
end


left_associative_contraction_sequence(N::Integer) = reduce((x, y) -> Any[y, x], 1:N)
left_associative_contraction_sequence(A) = left_associative_contraction_sequence(length(A))

"""
    contraction_cost(A; sequence)

Return the cost of contracting the collection of ITensors according to the specified sequence,
where the cost is measured in the number of floating point operations that would need to
be performed to contract dense tensors of the dimensions specified by the indices of the tensors
(so for now, sparsity is ignored in computing the costs).
Pairwise costs are returned in a vector (contracting `N` tensors requires `N-1` pairwise
contractions). You can use `sum(contraction_cost(A; sequence))` to get the total cost of the
contraction.

If no sequence is specified, left associative contraction is used, in other words the sequence
is equivalent to `[[[[1, 2], 3], 4], …]`.
"""
function contraction_cost(A; sequence = left_associative_contraction_sequence(A))
  pairwise_costs = Number[]
  _contraction_cost!(pairwise_costs, A, sequence)
  return pairwise_costs
end

function _contraction_cost!(pairwise_costs, A, sequence)
  inds1 = _contraction_cost!(pairwise_costs, A, sequence[1])
  inds2 = _contraction_cost!(pairwise_costs, A, sequence[2])
  return _pairwise_contraction_cost!(pairwise_costs, inds1, inds2)
end

_contraction_cost!(pairwise_costs, As, sequence::Integer) = As[sequence]

function _pairwise_contraction_cost!(pairwise_costs, A1, A2)
  cost = dim(union(A1, A2))
  push!(pairwise_costs, cost)
  return symdiff(A1, A2)
end


function predict_new_rbf(a_all,a_layer1,x_new,obs,neurons,n_per_part,inds_all,x_old,vars,cc)
    println("hello")
    layer1=zeros(obs,neurons)
    for i=1:neurons
        if i in 1:1:neurons println(Dates.format(now(), "HH:MM:SS")," Predicting responce of ",i," neuron, of ",neurons," Total.") end
        x1=copy(x_old[inds_all[n_per_part[i]+1:n_per_part[i+1]],:])
        obs1=size(x1,1)
        phi1 = calc_phi(x1,x_new,obs1,obs,cc,vars)
        # layer1[:,i].=phi1*a_all[i,1:obs1]
        layer1[:,i].=phi1*a_all[i]

    end

    predl1=[layer1 ones(obs)]*a_layer1

    return predl1,layer1

end


# Generate sequences of the Poisson events and plot histogram to check predictive distribution

include("./metadata.jl")
using Metadata

function run_sim()
    n_sim = 500
    n = 100
    w = 30
    seqs = cumsum(rand(1 : w, n))
    for i in 1 : n_sim
        new_dat = cumsum(rand(1 : w, n))
        seqs = vcat(seqs, new_dat)
    end
    println("sim_poisson_hist.jl > Save generated data ino ", outdir*"sim_poisson_hist.txt")
    writedlm(outdir*"sim_poisson_hist.txt", seqs)
end
run_sim()


using ParametricFunctions
using Base.Test
using ContinuousTransformations
import ForwardDiff: derivative

include("utilities.jl")

include("test-accuracy.jl")
include("test-families.jl")
include("test-collocation.jl")
include("test-plots.jl")


# using MKL
using Test
using DataFlowTasks
using DataFlowTasks.TiledFactorization
using LinearAlgebra
using BenchmarkTools
using VectorizationBase

nc = min(Int(VectorizationBase.num_cores()), Threads.nthreads())

capacity = 50
sch = DataFlowTasks.JuliaScheduler(capacity)
DataFlowTasks.setscheduler!(sch)

tilesize = 256
TiledFactorization.TILESIZE[] = tilesize

PLOT    = true
SAVEFIG = true

nn    = 1000:100:5000 |> collect
t_openblas = Float64[]
t_forkjoin = Float64[]
t_dataflow = Float64[]

for m in nn
    # create an SPD matrix
    A = rand(m,m)
    A = (A + adjoint(A))/2
    A = A + m*I
    nt = Threads.nthreads()

    # test open blas
    @info "OpenBLAS cholesky"
    @info BLAS.get_config()
    BLAS.set_num_threads(nc)
    @info "NBLAS = $(BLAS.get_num_threads())"
    b = @benchmark cholesky!(B) setup=(B=copy($A)) evals=1
    push!(t_openblas,median(b).time)

    # test DataFlowTasks parallelism
    @info "pseudotiled cholesky"
    @info "NBLAS = $(BLAS.get_num_threads())"
    b = @benchmark TiledFactorization.cholesky!(B) setup=(B=copy($A)) evals=1
    push!(t_dataflow,median(b).time)

    # test fork-join parallelism
    b = @benchmark TiledFactorization._cholesky_forkjoin!(B) setup=(B=PseudoTiledMatrix(copy($A),TiledFactorization.TILESIZE[]))
    push!(t_forkjoin,median(b).time)

    # compute the error
    DataFlowTasks.TASKCOUNTER[] = 1 # reset task counter to display how many tasks were created for
    F = TiledFactorization.cholesky(A)

    BLAS.set_num_threads(8)
    er = norm(F.L*F.U-A,Inf)/max(norm(A),norm(F.L*F.U))

    F = cholesky!(copy(A))
    er_blas = norm(F.L*F.U-A,Inf)/max(norm(A),norm(F.L*F.U))

    # for m×m tiled matrix, there should be 1/6*(m^3-m) + 1/2*(m^2-m) + m tasks
    # created
    println("="^80)
    @info "m                 = $(m)"
    @info "Number of tasks   = $(DataFlowTasks.TASKCOUNTER[])"
    @info "Number of threads = $(nt)"
    @info "Number of cores   = $(nc)"
    @info "er                = $er"
    @info "er_blas           = $er_blas"
    @info "t_blas            = $(t_openblas[end])"
    @info "t_dataflow        = $(t_dataflow[end])"
    @info "t_forkjoin        = $(t_forkjoin[end])"
    println("="^80)
end

if PLOT
    using Plots
    flops = @. 1/3*nn^3 + 1/2*nn^2 # I think this is correct (up to O(n)), but double check
    plot(nn,flops./(t_openblas),label="OpenBLAS",xlabel="n",ylabel="GFlops/second",m=:x,title="Cholesky factorization",legend=:bottomright)
    plot!(nn,flops./(t_forkjoin),label="TiledFactorization (forkjoin)",m=:x)
    plot!(nn,flops./(t_dataflow),label="TiledFactorization (dataflow)",m=:x)
    SAVEFIG && savefig(joinpath(DataFlowTasks.PROJECT_ROOT,"benchmarks/choleskyperf_capacity_$(capacity)_tilesize_$(tilesize).png"))
    # peakflops vary, not sure how to measure it. Maybe use cpuinfo?
    # peak = LinearAlgebra.peakflops()
    # plot!(nn,peak/1e9*ones(length(nn)))
    nothing
end


using RayTracingWeekend
using BenchmarkTools
#using StaticArrays
#using Images

using Images, LinearAlgebra, Random, RandomNumbers.Xorshifts, StaticArrays

# const Vec3{T<:AbstractFloat} = SVector{3, T}

# # Adapted from @Christ_Foster's: https://discourse.julialang.org/t/ray-tracing-in-a-week-end-julia-vs-simd-optimized-c/72958/40
# @inline @inbounds function Base.getproperty(v::SVector{3}, name::Symbol)
#     name === :x || name === :r ? getfield(v, :data)[1] :
#     name === :y || name === :g ? getfield(v, :data)[2] :
#     name === :z || name === :b ? getfield(v, :data)[3] : getfield(v, name)
# end

# @inline @inbounds function Base.getproperty(v::SVector{2}, name::Symbol)
#     name === :x || name === :r ? getfield(v, :data)[1] :
#     name === :y || name === :g ? getfield(v, :data)[2] : getfield(v, name)
# end

# @inline squared_length(v::Vec3) = v ⋅ v
# @inline near_zero(v::Vec3) = squared_length(v) < 1e-5
# @inline rgb(v::Vec3) = RGB(v...)
# @inline rgb_gamma2(v::Vec3) = RGB(sqrt.(v)...)

# struct Ray{T}
# 	origin::Vec3{T} # Point
# 	dir::Vec3{T} # Vec3 # direction (unit vector)
# end

# @inline point(r::Ray{T}, t::T) where T <: AbstractFloat = r.origin .+ t .* r.dir # equivalent to C++'s ray.at()

# @inline function skycolor(ray::Ray{T}) where T
# 	white = SA[1.0, 1.0, 1.0]
# 	skyblue = SA[0.5, 0.7, 1.0]
# 	t = T(0.5)*(ray.dir.y + one(T))
#     (one(T)-t)*white + t*skyblue
# end

# claforte: if I comment out this TRNG re-definition, the multithreading crash happens!

# Instantiate 1 RNG (Random Number Generator) per thread, for performance
# Fix the random seeds, to make it easier to benchmark changes.
#const TRNG = [Xoroshiro128Plus(i) for i = 1:128]    # claforte CRASH: When I commented-out this line, the crash occured.

# reseed!() = for (i,rng) in enumerate(TRNG) Random.seed!(rng, i) end # reset the seed
# reseed!()

# @inline function trand() # thread-local rand()
#     @inbounds rng = TRNG[Threads.threadid()]
#     rand(rng)
# end

# @inline function trand(::Type{T}) where T # thread-local rand()
#     @inbounds rng = TRNG[Threads.threadid()]
#     rand(rng, T)
# end

# @inline function random_vec3_in_sphere(::Type{T}) where T # equiv to random_in_unit_sphere()
# 	while true
# 		p = random_vec3(T(-1), T(1))
# 		if p⋅p <= 1
# 			return p
# 		end
# 	end
# end

# @inline random_between(min::T=0, max::T=1) where T = trand(T)*(max-min) + min # equiv to random_double()
# @inline random_vec3(min::T, max::T) where T = @inbounds @SVector[random_between(min, max) for i ∈ 1:3]
# @inline random_vec2(min::T, max::T) where T = @inbounds @SVector[random_between(min, max) for i ∈ 1:2]

# "Random unit vector. Equivalent to C++'s `unit_vector(random_in_unit_sphere())`"
# @inline random_vec3_on_sphere(::Type{T}) where T = normalize(random_vec3_in_sphere(T))

# @inline function random_vec2_in_disk(::Type{T}) where T # equiv to random_in_unit_disk()
# 	while true
# 		p = random_vec2(T(-1), T(1))
# 		if p⋅p <= 1
# 			return p
# 		end
# 	end
# end

# "An object that can be hit by Ray"
# abstract type Hittable end

# """Materials tell us how rays interact with a surface"""
# abstract type Material{T <: AbstractFloat} end

# "Record a hit between a ray and an object's surface"
# struct HitRecord{T <: AbstractFloat}
# 	t::T # distance from the ray's origin to the intersection with a surface.

# 	p::Vec3{T} # point of the intersection between an object's surface and a ray
# 	n⃗::Vec3{T} # surface's outward normal vector, points towards outside of object?

# 	# If true, our ray hit from outside to the front of the surface.
# 	# If false, the ray hit from within.
# 	front_face::Bool

# 	mat::Material{T}

# 	@inline HitRecord(t::T,p,n⃗,front_face,mat) where T = new{T}(t,p,n⃗,front_face,mat)
# end

# struct Sphere{T <: AbstractFloat} <: Hittable
# 	center::Vec3{T}
# 	radius::T
# 	mat::Material{T}
# end

# """Equivalent to `hit_record.set_face_normal()`"""
# @inline @fastmath function ray_to_HitRecord(t::T, p, outward_n⃗, r_dir::Vec3{T}, mat::Material{T})::Union{HitRecord,Nothing} where T
# 	front_face = r_dir ⋅ outward_n⃗ < 0
# 	n⃗ = front_face ? outward_n⃗ : -outward_n⃗
# 	HitRecord(t,p,n⃗,front_face,mat)
# end

# struct Scatter{T<: AbstractFloat}
# 	r::Ray{T}
# 	attenuation::Vec3{T}

# 	# claforte: TODO: rename to "absorbed?", i.e. not reflected/refracted?
# 	reflected::Bool # whether the scattered ray was reflected, or fully absorbed
# 	@inline Scatter(r::Ray{T},a::Vec3{T},reflected=true) where T = new{T}(r,a,reflected)
# end

# struct Lambertian{T} <: Material{T}
# 	albedo::Vec3{T}
# end

# """Compute reflection vector for v (pointing to surface) and normal n⃗.

# 	See [diagram](https://raytracing.github.io/books/RayTracingInOneWeekend.html#metal/mirroredlightreflection)"""
# @inline @fastmath reflect(v::Vec3{T}, n⃗::Vec3{T}) where T = v - (2v⋅n⃗)*n⃗

# """Create a scattered ray emitted by `mat` from incident Ray `r`.

# 	Args:
# 		rec: the HitRecord of the surface from which to scatter the ray.

# 	Return `nothing`` if it's fully absorbed. """
# @inline @fastmath function scatter(mat::Lambertian{T}, r::Ray{T}, rec::HitRecord{T})::Scatter{T} where T
# 	scatter_dir = rec.n⃗ + random_vec3_on_sphere(T)
# 	if near_zero(scatter_dir) # Catch degenerate scatter direction
# 		scatter_dir = rec.n⃗
# 	else
# 		scatter_dir = normalize(scatter_dir)
# 	end
# 	scattered_r = Ray{T}(rec.p, scatter_dir)
# 	attenuation = mat.albedo
# 	return Scatter(scattered_r, attenuation)
# end

# @inline @fastmath function hit(s::Sphere{T}, r::Ray{T}, tmin::T, tmax::T)::Union{HitRecord,Nothing} where T
#     oc = r.origin - s.center
#     #a = r.dir ⋅ r.dir # unnecessary since `r.dir` is normalized
# 	a = 1
# 	half_b = oc ⋅ r.dir
#     c = oc⋅oc - s.radius^2
#     discriminant = half_b^2 - a*c
# 	if discriminant < 0 return nothing end # no hit!
# 	sqrtd = √discriminant

# 	# Find the nearest root that lies in the acceptable range
# 	root = (-half_b - sqrtd) / a
# 	if root < tmin || tmax < root
# 		root = (-half_b + sqrtd) / a
# 		if root < tmin || tmax < root
# 			return nothing # no hit!
# 		end
# 	end

# 	t = root
# 	p = point(r, t)
# 	n⃗ = (p - s.center) / s.radius
# 	return ray_to_HitRecord(t, p, n⃗, r.dir, s.mat)
# end

# const HittableList = Vector{Hittable}

# """Find closest hit between `Ray r` and a list of Hittable objects `h`, within distance `tmin` < `tmax`"""
# @inline function hit(hittables::HittableList, r::Ray{T}, tmin::T, tmax::T)::Union{HitRecord,Nothing} where T
#     closest = tmax # closest t so far
#     best_rec::Union{HitRecord,Nothing} = nothing # by default, no hit
#     @inbounds for i in eachindex(hittables) # @paulmelis reported gave him a 4X speedup?!
# 		h = hittables[i]
#         rec = hit(h, r, tmin, closest)
#         if rec !== nothing
#             best_rec = rec
#             closest = best_rec.t # i.e. ignore any further hit > this one's.
#         end
#     end
#     best_rec
# end

#@inline color_vec3_in_rgb(v::Vec3{T}) where T = 0.5normalize(v) + SA{T}[0.5,0.5,0.5]

# struct Metal{T} <: Material{T}
# 	albedo::Vec3{T}
# 	fuzz::T # how big the sphere used to generate fuzzy reflection rays. 0=none
# 	@inline Metal(a::Vec3{T}, f::T=0.0) where T = new{T}(a,f)
# end
# @inline @fastmath function scatter(mat::Metal{T}, r_in::Ray{T}, rec::HitRecord)::Scatter{T} where T
# 	reflected = normalize(RaytracingWeekend.reflect(r_in.dir, rec.n⃗) + mat.fuzz*random_vec3_on_sphere(T)) # claforte: fully-qualified, to reuse that module's definition
# 	Scatter(Ray(rec.p, reflected), mat.albedo)
# end

# "Scene with 2 Lambertian spheres"
# function scene_2_spheres(; elem_type::Type{T}) where T
# 	spheres = Sphere[]

# 	# small center sphere
# 	push!(spheres, Sphere((SA{T}[0,0,-1]), T(0.5), Lambertian(SA{T}[0.7,0.3,0.3])))

# 	# ground sphere
# 	push!(spheres, Sphere((SA{T}[0,-100.5,-1]), T(100), Lambertian(SA{T}[0.8,0.8,0.0])))
# 	HittableList(spheres)
# end

# """Scene with 2 Lambertian, 2 Metal spheres.

# 	See https://raytracing.github.io/images/img-1.11-metal-shiny.png"""
# function scene_4_spheres(; elem_type::Type{T}) where T
# 	scene = scene_2_spheres(; elem_type=elem_type)

# 	# left and right Metal spheres
# 	push!(scene, Sphere((SA{T}[-1,0,-1]), T(0.5), Metal((SA{T}[0.8,0.8,0.8]), T(0.3))))
# 	push!(scene, Sphere((SA{T}[ 1,0,-1]), T(0.5), Metal((SA{T}[0.8,0.6,0.2]), T(0.8))))
# 	return scene
# end

# struct Camera{T <: AbstractFloat}
# 	origin::Vec3{T}
# 	lower_left_corner::Vec3{T}
# 	horizontal::Vec3{T}
# 	vertical::Vec3{T}
# 	u::Vec3{T}
# 	v::Vec3{T}
# 	w::Vec3{T}
# 	lens_radius::T
# end

# """
# 	Args:
# 		vfov: vertical field-of-view in degrees
# 		aspect_ratio: horizontal/vertical ratio of pixels
#       aperture: if 0 - no depth-of-field
# """
# function default_camera(lookfrom::Vec3{T}=(SA{T}[0,0,0]),
# 						lookat::Vec3{T}=(SA{T}[0,0,-1]),
# 						vup::Vec3{T}=(SA{T}[0,1,0]),
# 						vfov::T=T(90), aspect_ratio::T=T(16/9),
# 						aperture::T=T(0), focus_dist::T=T(1)) where T
# 	viewport_height = T(2) * tand(vfov/T(2))
# 	viewport_width = aspect_ratio * viewport_height

# 	w = normalize(lookfrom - lookat)
# 	u = normalize(vup × w)
# 	v = w × u

# 	origin = lookfrom
# 	horizontal = focus_dist * viewport_width * u
# 	vertical = focus_dist * viewport_height * v
# 	lower_left_corner = origin - horizontal/T(2) - vertical/T(2) - focus_dist*w
# 	lens_radius = aperture/T(2)
# 	Camera{T}(origin, lower_left_corner, horizontal, vertical, u, v, w, lens_radius)
# end

# claforte CRASH: if I comment-out the following line, and run with
# `julia --project=. --threads=8 --inline=no repro_crash.jl`, I get a crash with an error message:
# `nested task error: BoundsError: attempt to access 1-element Vector{Xoroshiro128Plus} at index [2]`
# ... but if I run instead with:
# `julia --project=. --threads=8 repro_crash.jl`...
# ... I get the usual segmentation fault.
# My guess: anything that uses TRNG, directly or indirectly, needs to be redefined in this file instead of used from the module.
#

# @inline @fastmath function get_ray(c::Camera{T}, s::T, t::T) where T
# 	rd = SVector{2,T}(c.lens_radius * random_vec2_in_disk(T))
# 	offset = c.u * rd.x + c.v * rd.y #offset = c.u * rd.x + c.v * rd.y
#     Ray(c.origin + offset, normalize(c.lower_left_corner + s*c.horizontal +
# 									 t*c.vertical - c.origin - offset))
# end

# """Compute color for a ray, recursively

# 	Args:
# 		depth: how many more levels of recursive ray bounces can we still compute?"""
# @inline @fastmath function ray_color(r::Ray{T}, world::HittableList, depth=16) where T
#     if depth <= 0
# 		return SA{T}[0,0,0]
# 	end

# 	rec::Union{HitRecord,Nothing} = hit(world, r, T(1e-4), typemax(T))
#     if rec !== nothing
# 		# For debugging, represent vectors as RGB:
# 		# claforte TODO: adapt to latest code!
# 		# return color_vec3_in_rgb(rec.p) # show the normalized hit point
# 		# return color_vec3_in_rgb(rec.n⃗) # show the normal in RGB
# 		# return color_vec3_in_rgb(rec.p + rec.n⃗)
# 		# return color_vec3_in_rgb(random_vec3_in_sphere())
# 		#return color_vec3_in_rgb(rec.n⃗ + random_vec3_in_sphere())

#         s = scatter(rec.mat, r, rec)
# 		if s.reflected
# 			return s.attenuation .* ray_color(s.r, world, depth-1)
# 		else
# 			return SA{T}[0,0,0]
# 		end
#     else
#         skycolor(r)
#     end
# end

# """Render an image of `scene` using the specified camera, number of samples.

# 	Args:
# 		scene: a HittableList, e.g. a list of spheres
# 		n_samples: number of samples per pixel, eq. to C++ samples_per_pixel

# 	Equivalent to C++'s `main` function."""
# function render(scene::HittableList, cam::Camera{T}, image_width=400,
# 				n_samples=1) where T
# 	# Image
# 	aspect_ratio = T(16.0/9.0) # TODO: use cam.aspect_ratio for consistency
# 	image_height = convert(Int64, floor(image_width / aspect_ratio))

# 	# Render
# 	img = zeros(RGB{T}, image_height, image_width)
# 	f32_image_width = convert(Float32, image_width)
# 	f32_image_height = convert(Float32, image_height)

# 	# Reset the random seeds, so we always get the same images...
# 	# Makes comparing performance more accurate.
# 	reseed!()

# 	Threads.@threads for i in 1:image_height
# 		@inbounds for j in 1:image_width # iterate over each row (FASTER?!)
# 			accum_color = SA{T}[0,0,0]
# 			u = convert(T, j/image_width)
# 			v = convert(T, (image_height-i)/image_height) # i is Y-down, v is Y-up!

# 			for s in 1:n_samples
# 				if s == 1 # 1st sample is always centered
# 					δu = δv = T(0)
# 				else
# 					# Supersampling antialiasing.
# 					δu = trand(T) / f32_image_width
# 					δv = trand(T) / f32_image_height
# 				end
# 				ray = get_ray(cam, u+δu, v+δv)
# 				accum_color += ray_color(ray, scene)
# 			end
# 			img[i,j] = rgb_gamma2(accum_color / n_samples)
# 		end
# 	end
# 	img
# end

# """
# 	Args:
# 		refraction_ratio: incident refraction index divided by refraction index of
# 			hit surface. i.e. η/η′ in the figure above"""
# @inline @fastmath function refract(dir::Vec3{T}, n⃗::Vec3{T}, refraction_ratio::T) where T
# 	cosθ = min(-dir ⋅ n⃗, one(T))
# 	r_out_perp = refraction_ratio * (dir + cosθ*n⃗)
# 	r_out_parallel = -√(abs(one(T)-squared_length(r_out_perp))) * n⃗
# 	normalize(r_out_perp + r_out_parallel)
# end

# struct Dielectric{T} <: Material{T}
# 	ir::T # index of refraction, i.e. η.
# end

# @inline @fastmath function reflectance(cosθ, refraction_ratio)
# 	# Use Schlick's approximation for reflectance.
# 	# claforte: may be buggy? I'm getting black pixels in the Hollow Glass Sphere...
# 	r0 = (1-refraction_ratio) / (1+refraction_ratio)
# 	r0 = r0^2
# 	r0 + (1-r0)*((1-cosθ)^5)
# end

# @inline @fastmath function scatter(mat::Dielectric{T}, r_in::Ray{T}, rec::HitRecord{T}) where T
# 	attenuation = SA{T}[1,1,1]
# 	refraction_ratio = rec.front_face ? (one(T)/mat.ir) : mat.ir # i.e. ηᵢ/ηₜ
# 	cosθ = min(-r_in.dir⋅rec.n⃗, one(T))
# 	sinθ = √(one(T) - cosθ^2)
# 	cannot_refract = refraction_ratio * sinθ > one(T)
# 	if cannot_refract || reflectance(cosθ, refraction_ratio) > trand(T)
# 		dir = reflect(r_in.dir, rec.n⃗)
# 	else
# 		dir = refract(r_in.dir, rec.n⃗, refraction_ratio)
# 	end
# 	Scatter(Ray{T}(rec.p, dir), attenuation) # TODO: rename reflected -> !absorbed?
# end

# @inline function scene_diel_spheres(left_radius=0.5; elem_type::Type{T}) where T # dielectric spheres
# 	spheres = Sphere[]

# 	# small center sphere
# 	push!(spheres, Sphere((SA{T}[0,0,-1]), T(0.5), Lambertian(SA{T}[0.1,0.2,0.5])))

# 	# ground sphere (planet?)
# 	push!(spheres, Sphere((SA{T}[0,-100.5,-1]), T(100), Lambertian(SA{T}[0.8,0.8,0.0])))

# 	# # left and right spheres.
# 	# # Use a negative radius on the left sphere to create a "thin bubble"
# 	push!(spheres, Sphere((SA{T}[-1,0,-1]), T(left_radius), Dielectric(T(1.5))))
# 	push!(spheres, Sphere((SA{T}[1,0,-1]), T(0.5), Metal((SA{T}[0.8,0.6,0.2]), T(0))))
# 	HittableList(spheres)
# end

# function scene_blue_red_spheres(; elem_type::Type{T}) where T # dielectric spheres
# 	spheres = Sphere[]
# 	R = cos(pi/4)
# 	push!(spheres, Sphere((SA{T}[-R,0,-1]), R, Lambertian(SA{T}[0,0,1])))
# 	push!(spheres, Sphere((SA{T}[ R,0,-1]), R, Lambertian(SA{T}[1,0,0])))
# 	HittableList(spheres)
# end

# function scene_random_spheres(; elem_type::Type{T}) where T
# 	spheres = Sphere[]

# 	# ground
# 	push!(spheres, Sphere((SA{T}[0,-1000,-1]), T(1000),
# 						  Lambertian(SA{T}[0.5,0.5,0.5])))

# 	for a in -11:10, b in -11:10
# 		choose_mat = trand(T)
# 		center = SA[a + T(0.9)*trand(T), T(0.2), b + T(0.9)*trand(T)]

# 		# skip spheres too close?
# 		if norm(center - SA{T}[4,0.2,0]) < T(0.9) continue end

# 		if choose_mat < T(0.8)
# 			# diffuse
# 			albedo = @SVector[trand(T) for i ∈ 1:3] .* @SVector[trand(T) for i ∈ 1:3]
# 			push!(spheres, Sphere(center, T(0.2), Lambertian(albedo)))
# 		elseif choose_mat < T(0.95)
# 			# metal
# 			albedo = @SVector[random_between(T(0.5),T(1.0)) for i ∈ 1:3]
# 			fuzz = random_between(T(0.0), T(5.0))
# 			push!(spheres, Sphere(center, T(0.2), Metal(albedo, fuzz)))
# 		else
# 			# glass
# 			push!(spheres, Sphere(center, T(0.2), Dielectric(T(1.5))))
# 		end
# 	end

# 	push!(spheres, Sphere((SA{T}[0,1,0]), T(1), Dielectric(T(1.5))))
# 	push!(spheres, Sphere((SA{T}[-4,1,0]), T(1),
# 						  Lambertian(SA{T}[0.4,0.2,0.1])))
# 	push!(spheres, Sphere((SA{T}[4,1,0]), T(1),
# 						  Metal((SA{T}[0.7,0.6,0.5]), T(0))))
# 	HittableList(spheres)
# end

ELEM_TYPE = Float64
t_default_cam = default_camera(SA{ELEM_TYPE}[0,0,0])
render(scene_2_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16)

t_cam1 = default_camera([13,2,3], [0,0,0], [0,1,0], 20, 16/9, 0.1, 10.0; elem_type=ELEM_TYPE)

print("render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 200, 32):")
_scene_random_spheres = scene_random_spheres(; elem_type=ELEM_TYPE)
render(_scene_random_spheres, t_cam1, 200, 32)
@benchmark render($_scene_random_spheres, $t_cam1, 200, 32)


"""
Authors : Adrien BLASSIAU Corentin JUVIGNY
TP RODD - 2019-2020
"""

# Ne pas enlever
include("./main.jl")

println("Chargement de JuMP")
using JuMP
println("Chargement de CPLEX")
using CPLEX
println("Cplex chargé")

@doc """
Modifie la solution sol de l'instance inst en appliquant CPLEX sur un
programme linéaire modélisant le problème (G_ULS).
""" ->
function run(inst, sol)
    T = inst.T
    M = inst.M
    R = inst.R
    Emax = inst.Emax
    f = inst.f
    e = inst.e
    h = inst.h
    p = inst.p
    d = inst.d

	m = Model(with_optimizer(CPLEX.Optimizer))
	MOI.set(m, MOI.RawParameter("CPXPARAM_MIP_Display"),0)

	@variable(m, 0 <= x[1:T,1:M])
	@variable(m, 0 <= s[1:T])
	@variable(m, y[1:T,1:M], Bin)

	## OBJECTIF
	##
	@objective(m, Min, sum(sum(p[t][j]*x[t,j]+f[t][j]*y[t,j] for j in 1:M)+h[t]*s[t] for t in 1:T))

	## CONTRA1NTE 1
	##
	## Contrainte de conservation
	@constraint(m, constraint1[t in 2:T], sum( x[t,j] for j in 1:M)-s[t]+s[t-1] == d[t])

	## CONTRA1NTE 2
	##
	## Contrainte de conservation
	@constraint(m, constraint2, sum(x[1,j]-s[1] for j in 1:M) == d[1])

	## CONTRA1NTE 3
	##
	## Contrainte de demande
	@constraint(m, constraint3[t in 1:T, j in 1:M], sum(d[t1] for t1 in t:T)*y[t,j]>=x[t,j])

	## CONTRA1NTE 4
	##
	## Contrainte écologique
	@constraint(m, constraint4[t in R:T], sum(sum( (e[t1][j]-Emax[t1])*x[t1,j] for j in 1:M) for t1 in t-R+1:t) <=0)

	optimize!(m)

	return (m,x,s,y)
end

@doc """
Cette fonction est appelée après la fonction `run` et permet de faire de l'affichage et des traitement sur la sortie de la fonction `run` ; sans pour autant affecter son temps de calcul.

Le paramètre cpu time est le temps de calcul de `run`. Les valeurs de `inst` et `sol` sont les mêmes qu’à la sortie de la fonction run. Enfin, `others` est ce qui est renvoyé par la fonction `run`. Vous pouvez ainsi effectuer des tests et afficher des résultats sans affecter le temps de calcul.
""" ->
function post_process(cpu_time::Float64, inst, sol, others)

	# Run a renvoyé le modèle et ses variables, qui ont été mis dans others.
	m,x,s,y = others

	solx = value.(x)
	emission_moyenne = 0
	prod_moyenne = 0
	for i in 1:inst.T
	    for j in 1:inst.M
	    	emission_moyenne+=solx[i,j]*inst.e[i][j]
	    	prod_moyenne+=solx[i,j]
	    end
	end
	emission_moyenne = emission_moyenne/prod_moyenne

	println("OBJECTIF : $(objective_value(m))")
	println("emission moyenne : $(emission_moyenne)")
	# println("VALEURS de x : $(value.(x))")
	# println("VALEURS de s : $(value.(s))")
	# println("VALEURS de y : $(value.(y))")

	# println("Temps de calcul : $cpu_time.")
end

# Ne pas enlever
if length(ARGS) > 0
	input_file = ARGS[1]

	main(input_file)
end


using Test, EllZeroTrendFiltering
using Interpolations

Random.seed!(31415)

# Auxilliary function for evaluating the approximation error for a specific
# piecewise linear approximation (I, Y), given the transition costs ℓ
function compute_cost(ℓ, I, Y)
    cost = 0.0;
    for k=1:length(I)-1
     cost += ℓ[I[k], I[k+1]](Y[k], Y[k+1])
    end
    return cost
end

## Test 1
# Compare the approximation error for specific piecewise linear approximations
# (I, Y) when the costs are computed  by
# (1) using ℓ computed by compute_discrete_transition_costs and
#     then evaluated using the above auxilliary function
# (2) simple linear interpolation

g1 = sin.(range(0, stop=π, length=50))
I1 = [1,10,30,40,50]
Y1 = g1[I1]
t_ss1 = [1, 10, 21, 33, 50]

g2 = g1
I2 = [1,15,30,36,50]
Y2 = randn(5)
t_ss2 = t_ss1

g3 = randn(20)
I3 = [1; 3:2:19; 20]
Y3 = randn(length(I3))
t_ss3 = [1,5,10,15,20]

g4 = g3
I4 = 1:length(g3)
Y4 = g4
t_ss4 = t_ss3

for (g, I, Y, t_subsampled) in ((g1, I1, Y1, t_ss1), (g2, I2, Y2, t_ss2), (g3, I3, Y3, t_ss3))#, (g4, I4, Y4))
    ℓ = compute_discrete_transition_costs(g)
    cost1 = compute_cost(ℓ,I,Y)

    y = interpolate((I,), Y, Gridded(Linear()))(1:length(g))
    cost2 = sum((y[1:end-1]-g[1:end-1]).^2) # Note: cost at i=N should not be included

    @test cost1 ≈ cost2

    # Test transition cost with t grid
    ℓ_subsampled = compute_discrete_transition_costs(g, t_subsampled)

    for i in 1:length(t_subsampled)-1
        for ip in (i+1):length(t_subsampled)
            t_i = t_subsampled[i]
            t_ip = t_subsampled[ip]
            @test ℓ_subsampled[i, ip] == ℓ[t_i, t_ip]
        end
    end
end

## Test 2
# Check the transition costs for a very simple example
ℓ = compute_discrete_transition_costs([1.0, 2.0, 3.0])

# (y - 1)^2
@test ℓ[1,2].P == [1.0 0.0; 0.0 0.0]
@test ℓ[1,2].q == [-2.0, 0]
@test ℓ[1,2].r == 1

# (y - 1)^2 + (y/2 + y'/2 - 2)^2
@test ℓ[1,3].P == [1.25 0.25; 0.25 0.25]
@test ℓ[1,3].q == [-4.0, -2.0]
@test ℓ[1,3].r == 5.0


using Convex, ECOS

"""
	contact constraints
		-differentiate through second-order cone program to get friction
"""
function friction_socp(model, x⁺, u, h) # version for particle
	q3 = view(x⁺, model.nq .+ (1:model.nq))
	q2 = view(x⁺, 1:model.nq)
	λ = view(u, model.idx_λ)

	v = (q3 - q2)[1:2] ./ h #
	y = model.μ * λ

	"Convex.jl"
	b = Variable(2)
	prob = minimize(v' * b)
	prob.constraints += norm(b) <= y
	solve!(prob, ECOS.Optimizer(verbose = false));

	# @show prob.status
	# @show b.value
	# @show prob.constraints[1].dual
	# prob.optval

	# # norms
	# _norm(x) = sqrt(x' * x)
	function vec_norm(x)
		if norm(x) == 0.0
			return ones(length(x)) ./ norm(ones(length(x)))
		else
			x ./ norm(x)
		end
	end
	# function d_vec_norm(x)
	# 	if norm(x) == 0.0
	# 		y = 1.0 * ones(length(x))#./norm(ones(length(x)))
	# 		return (I - y * y' / (y' * y)) /_norm(y)
	# 	else
	# 		(I - x * x' / (x' * x)) /_norm(x)
	# 	end
	# end
	function r(z,θ)
		b = z[1:2]
		ψ = z[3]
		y = θ[1]
		v = θ[2:3]

		return [norm(b) * v + ψ * b;
			    ψ * (y - norm(b))]
	end

	function drz(z,θ)
		b = z[1:2]
		ψ = z[3]
		y = θ[1]
		v = θ[2:3]

		return [v * vec_norm(b)' + ψ * I b;
			    -ψ * vec_norm(b)' (y - norm(b))]
	end

	function drθ(z,θ)
		b = z[1:2]
		ψ = z[3]
		y = θ[1]
		v = θ[2:3]
		return Array([zeros(2) Diagonal(norm(b) * ones(2));
					  ψ zeros(1, 2)])
	end

	# if y == 0.0
	# 	z = [zeros(2); ψ]
	# else
	# 	z = [b.value; ψ]
	# end
	z = [b.value; prob.constraints[1].dual]
	θ = [y; v]
	rz(x) = r(x, θ)
	rθ(x) = r(z, x)

	drdz = ForwardDiff.jacobian(rz, z)
	drdθ = ForwardDiff.jacobian(rθ, θ)
	# drz(z, θ)
	# drθ(z, θ)
	# norm(drdz - drz(z, θ))
	# norm(drdθ - drθ(z, θ))
	# rank(drdz)
	# eigen(drdz).values
	#
	# rank(drz(z, θ))
	# eigen(drz(z, θ)).values

	# x1 = (-drdz \ drdθ)[1:2,:]
	# x3 = (-(drdz' * drdz) \ (drdz' * drdθ))[1:2,:]

	ρ = 1.0e-5
	sol = (-(drdz' * drdz + ρ * I) \ (drdz' * drdθ))[1:2,:]
	# x4 = (-drdz' * ((drdz * drdz' + ρ * I) \ drdθ))[1:2,:]
	dλ = sol[:, 1] .* model.μ
	dq2 = -1.0 * sol[:, 2:3] ./ h
	dq3 = sol[:, 2:3] ./ h
	# print(b)
	return b, dλ, dq2, dq3
end

struct ContactDiffConstraints <: Constraints
	n
	ineq
end

function contact_diff_constraints(model, T)
	n = model.nc * (T + 1) + model.nb * (T - 1) + 1 * (T - 1)
	ineq = con_ineq_contact(model, T)

	return ContactDiffConstraints(n, ineq)
end

function constraints!(c, Z, con::ContactDiffConstraints, model, idx, h, T)
	shift = 0

	# signed-distance function
	for t = 1:T
		x = view(Z, idx.x[t])

		if t == 1
			q = view(x, 1:model.nq)
			c[1:model.nc] = ϕ_func(model, q)
		end

		q⁺ = view(x, model.nq .+ (1:model.nq))
		c[model.nc + (t-1) * model.nc .+ (1:model.nc)] = ϕ_func(model, q⁺)
	end

	shift += model.nc * (T + 1)

	# maximum dissipation
	for t = 1:T-1
		x⁺ = view(Z, idx.x[t+1])
		u = view(Z, idx.u[t])
		b = view(u, model.idx_b)

		b_sol, dλ, dq2, dq3 = friction_socp(model, x⁺, u, h)
		c[shift + (t - 1) * model.nb .+ (1:model.nb)] = b_sol - b
	end

	shift += model.nb * (T - 1)

	# impact complementarity
	for t = 1:T-1
		x⁺ = view(Z, idx.x[t + 1])
		u = view(Z, idx.u[t])

		q = view(x⁺, model.nq .+ (1:model.nq))
		s = view(u, model.idx_s)
		λ = view(u, model.idx_λ)

		c[shift + (t-1) * 1 + 1] = s[1] - (λ' * ϕ_func(model, q))[1]
	end

	shift += (T - 1)

    return nothing
end

function constraints_jacobian!(∇c, Z, con::ContactDiffConstraints, model, idx, h, T)
	shift = 0
	c_shift = 0

	# signed-distance function
	ϕ(y) = ϕ_func(model, y)

	for t = 1:T
		x = view(Z, idx.x[t])

		if t == 1
			q = view(x, 1:model.nq)
			r_idx = 1:model.nc
			c_idx = idx.x[t][1:model.nq]
			len = length(r_idx) * length(c_idx)
			∇c[shift .+ (1:len)] = vec(ForwardDiff.jacobian(ϕ, q))
			shift += len
		end

		q⁺ = view(x, model.nq .+ (1:model.nq))
		r_idx = model.nc + (t-1) * model.nc .+ (1:model.nc)
		c_idx = idx.x[t][model.nq .+ (1:model.nq)]
		len = length(r_idx) * length(c_idx)
		∇c[shift .+ (1:len)] = vec(ForwardDiff.jacobian(ϕ, q⁺))
		shift += len
	end

	c_shift += model.nc * (T + 1)

	# friction socp
	for t = 1:T-1
		x⁺ = view(Z, idx.x[t+1])
		u = view(Z, idx.u[t])

		b_sol, dλ, dq2, dq3 = friction_socp(model, x⁺, u, h)

		r_idx = c_shift + (t - 1) * model.nb .+ (1:model.nb)
		c_idx = idx.x[t + 1][collect([(1:2)..., (4:5)...])]
		len = length(r_idx) * length(c_idx)
		∇c[shift .+ (1:len)] = vec([dq2; dq3])
		shift += len

		c_idx = idx.u[t][model.idx_λ]
		len = length(r_idx) * length(c_idx)
		∇c[shift .+ (1:len)] = vec(dλ)
		shift += len

		c_idx = idx.u[t][model.idx_b]
		len = length(r_idx) * length(c_idx)
		∇c[shift .+ (1:len)] = vec(Diagonal(-1.0 * ones(model.nb)))
		shift += len
	end

	c_shift += model.nb * (T - 1)

	# impact complementarity
	for t = 1:T-1
		x⁺ = view(Z, idx.x[t + 1])
		u = view(Z, idx.u[t])

		q = view(x⁺, model.nq .+ (1:model.nq))
		s = view(u, model.idx_s)
		λ = view(u, model.idx_λ)

		cq(y) = s - [λ' * ϕ_func(model, y)]
		cu(y) = y[model.idx_s] - [y[model.idx_λ]' * ϕ_func(model, q)]

		r_idx = c_shift + (t-1) * 1 + 1
		c_idx = idx.x[t + 1][model.nq .+ (1:model.nq)]
		len = length(r_idx) * length(c_idx)
		∇c[shift .+ (1:len)] = vec(ForwardDiff.jacobian(cq, q))
		shift += len

		c_idx = idx.u[t]
		len = length(r_idx) * length(c_idx)
		∇c[shift .+ (1:len)] = vec(ForwardDiff.jacobian(cu, u))
		shift += len
	end

	c_shift += (T - 1)

	return nothing
end

function constraints_sparsity(con::ContactDiffConstraints, model, idx, T;
	shift_row = 0, shift_col = 0)

	row = []
    col = []

	con_shift = 0

	# signed-distance function
	for t = 1:T

		if t == 1
			r_idx = shift_row .+ (1:model.nc)
			c_idx = shift_col .+ idx.x[t][1:model.nq]
			row_col!(row, col, r_idx, c_idx)
		end

		r_idx = shift_row + model.nc + (t-1) * model.nc .+ (1:model.nc)
		c_idx = shift_col .+ idx.x[t][model.nq .+ (1:model.nq)]
		row_col!(row, col, r_idx, c_idx)
	end

	con_shift += model.nc * (T + 1)

	# friction socp
	for t = 1:T-1
		r_idx = shift_row + con_shift + (t - 1) * model.nb .+ (1:model.nb)
		c_idx = shift_col .+ idx.x[t + 1][collect([(1:2)..., (4:5)...])]
		row_col!(row, col, r_idx, c_idx)

		c_idx = shift_col .+ idx.u[t][model.idx_λ]
		row_col!(row, col, r_idx, c_idx)

		c_idx = idx.u[t][model.idx_b]
		row_col!(row, col, r_idx, c_idx)
	end

	con_shift += model.nb * (T - 1)

	# impact complementarity
	for t = 1:T-1
		r_idx = shift_row + con_shift + (t-1) * 1 + 1
		c_idx = shift_col .+ idx.x[t + 1][model.nq .+ (1:model.nq)]
		row_col!(row, col, r_idx, c_idx)

		c_idx = shift_col .+ idx.u[t]
		row_col!(row, col, r_idx, c_idx)
	end

	con_shift += (T - 1)

    return collect(zip(row, col))
end

function con_ineq_contact(model, T)

	con_ineq = []
	shift = 0

	# signed-distance function
	push!(con_ineq, [i for i = 1:model.nc * (T + 1)])
	shift += model.nc * (T + 1)

	# maximum dissipation
	# push!(con_ineq, [i for i = shift .+ (1:model.nb * (T - 1))])
	shift += model.nb * (T - 1)

	# impact complementarity
	push!(con_ineq, [i for i = shift .+ (1:(T - 1))])
	shift += (T - 1)

    return vcat(con_ineq...)
end


using LowRankModels
using Base.Test

# Parameters
n = 200
m = 200
r = 5
eta = 0.01
delta = 1e-3

# Generate problem
srand(1)
Um = randn(r, n)
Vm = randn(r, m)
U = Um .+ sqrt(eta) * randn(r, n)
V = Vm .+ sqrt(eta) * randn(r, m)
Y = U' * V + sqrt(delta) * randn(n, m)

# Run algorithm
glrm = GLRM(Y, QuadLoss(), [RemQuadReg(50, Um[:, i]) for i = 1:n],
    [RemQuadReg(50, Vm[:, j]) for j = 1:m], r)
Uh, Vh, iter_info = fit!(glrm)

mseU, mseV, mseY = mean((U - Uh).^2), mean((V - Vh).^2), mean((Y - Uh' * Vh).^2)
@printf("MSE(U) = %.4g, MSE(V) = %.4g, MSE(Y) = %.4g\n", mseU, mseV, mseY)

# Perform some tests
@test mseU < 1e-3
@test mseV < 1e-3