include("../aimajulia.jl");

using Test;
using Printf;

using Main.aimajulia;

using Main.aimajulia.utils;

#The following text tests are from the aima-python doctests

# Base.Test tests for unigram and n-gram of words
flatland = String(read("./aima-data/EN-text/flatland.txt"));
word_sequence = extract_words(flatland);
P1 = UnigramWordModel(word_sequence);
P2 = NgramWordModel(2, word_sequence);
P3 = NgramWordModel(3, word_sequence);

@test (top(P1, 5) == [(2081,"the"),(1479,"of"),(1021,"and"),(1008,"to"),(850,"a")]);

@test (top(P2, 5) == [(368,("of","the")),(152,("to","the")),(152,("in","the")),(86,("of","a")),(80,("it","is"))]);

@test (top(P3, 5) == [(30,("a","straight","line")),
                        (19,("of","three","dimensions")),
                        (16,("the","sense","of")),
                        (13,("by","the","sense")),
                        (13,("as","well","as"))]);

@test (abs(P1["the"] - 0.0611) <= (0.001 * max(abs(P1["the"]), abs(0.0611))));

@test (abs(P2[("of", "the")] - 0.0108) <= (0.01 * max(abs(P2[("of", "the")]), abs(0.0108))));

@test (abs(P3[("so", "as", "to")] - 0.000323) <= (0.001 * max(abs(P3[("so", "as", "to")]), abs(0.000323))));

@test (!haskey(P2.conditional_probabilities, ("went",)));

@test (P3.conditional_probabilities[("in", "order")].dict == Dict([Pair("to", 6)]));

test_string = "unigram";
word_sequence = extract_words(test_string);
P1 = UnigramWordModel(word_sequence);

@test (P1.dict == Dict([Pair("unigram", 1)]));

test_string = "bigram text";
word_sequence = extract_words(test_string);
P2 = NgramWordModel(2, word_sequence);

@test (P2.dict == Dict([Pair(("bigram", "text"), 1)]));

test_string = "test trigram text here";
word_sequence = extract_words(test_string);
P3 = NgramWordModel(3, word_sequence);

@test (haskey(P3.dict, ("test", "trigram", "text")));

@test (haskey(P3.dict, ("trigram", "text", "here")));

# Base.Test tests for canonicalizing text
@test (extract_words("``EGAD'' Edgar cried.") == ["egad", "edgar", "cried"]);

@test (canonicalize_text("``EGAD'' Edgar cried.") == "egad edgar cried");

# Base.Test tests for samples() methods
story = String(read("./aima-data/EN-text/flatland.txt"));
story = story*String(read("./aima-data/gutenberg.txt"));
word_sequence = extract_words(story);
P1 = UnigramWordModel(word_sequence);
P2 = NgramWordModel(2, word_sequence);
P3 = NgramWordModel(3, word_sequence);

@test (length(split(samples(P1, 10))) == 10);

@test (length(split(samples(P2, 10))) == 10);

@test (length(split(samples(P3, 10))) == 10);

# Base.Test tests for unigram and n-gram of characters/letters
test_string = "test unigram";
word_sequence = extract_words(test_string);
P1 = UnigramCharModel(word_sequence);
expected_unigrams = Dict([Pair('n',1),
                        Pair('g', 1),
                        Pair('t', 2),
                        Pair('a', 1),
                        Pair('u', 1),
                        Pair('i', 1),
                        Pair('m', 1),
                        Pair('e', 1),
                        Pair('s', 1),
                        Pair('r', 1)]);

@test (length(P1.dict) == length(expected_unigrams));

@test (all(haskey(P1.dict, character) for character in setdiff(Set(test_string), Set(" "))));

test_string = "alpha beta";
word_sequence = extract_words(test_string);
P1 = NgramCharModel(1, word_sequence);

@test (length(P1.dict) == length(Set(collect(test_string))));

@test (all((function(c::Char)
                return haskey(P1.dict, (c,));
            end),
            Set(collect(test_string))));

test_string = "bigram";
word_sequence = extract_words(test_string);
P2 = NgramCharModel(2, word_sequence);
expected_bigrams = Dict([Pair((' ', 'b'), 1),
                        Pair(('b', 'i'), 1),
                        Pair(('i', 'g'), 1),
                        Pair(('g', 'r'), 1),
                        Pair(('r', 'a'), 1),
                        Pair(('a', 'm'), 1)]);

@test (length(P2.dict) == length(expected_bigrams));

@test (all(haskey(P2.dict, key) for key in keys(expected_bigrams)));

@test (all((P2.dict[key] == expected_bigrams[key]) for key in keys(expected_bigrams)));

test_string = "trigram";
word_sequence = extract_words(test_string);
P3 = NgramCharModel(3, word_sequence);
expected_trigrams = Dict([Pair((' ', 't', 'r'), 1),
                        Pair(('t', 'r', 'i'), 1),
                        Pair(('r', 'i', 'g'), 1),
                        Pair(('i', 'g', 'r'), 1),
                        Pair(('g', 'r', 'a'), 1),
                        Pair(('r', 'a', 'm'), 1)]);

@test (length(P3.dict) == length(expected_trigrams));

@test (all(haskey(P3.dict, key) for key in keys(expected_trigrams)));

@test (all((P3.dict[key] == expected_trigrams[key]) for key in keys(expected_trigrams)));

test_string = "trigram trigram trigram";
word_sequence = extract_words(test_string);
P3 = NgramCharModel(3, word_sequence);
expected_trigrams = Dict([Pair((' ', 't', 'r'), 3),
                        Pair(('t', 'r', 'i'), 3),
                        Pair(('r', 'i', 'g'), 3),
                        Pair(('i', 'g', 'r'), 3),
                        Pair(('g', 'r', 'a'), 3),
                        Pair(('r', 'a', 'm'), 3)]);

@test (length(P3.dict) == length(expected_trigrams));

@test (all(haskey(P3.dict, key) for key in keys(expected_trigrams)));

@test (all((P3.dict[key] == expected_trigrams[key]) for key in keys(expected_trigrams)));

# Base.Test tests for encoding
@test (shift_encode("This is a secret message.", 17) == "Kyzj zj r jvtivk dvjjrxv.");

@test (rot13("Hello, world!") == "Uryyb, jbeyq!");

@test (reduce(*, map((function(c::Char)
                            if (c == ' ')
                                return String(['s', ' ', c]);
                            else
                                return String([c]);
                            end
                        end),
                        collect("orange apple lemon "))) == "oranges  apples  lemons  ");

# Base.Test tests for decoding
flatland = read("./aima-data/EN-text/flatland.txt", String);
ring = ShiftCipherDecoder(flatland);

@test (decode_text(ring, "Kyzj zj r jvtivk dvjjrxv.") == "This is a secret message.");

@test (decode_text(ring, rot13("Hello, world!")) == "Hello, world!");

gutenberg = read("./aima-data/gutenberg.txt", String);
pd = PermutationCipherDecoder(canonicalize_text(gutenberg));

@test (decode_text(pd, "aba") in ("ece", "ete", "tat", "tit", "txt"));

pd = PermutationCipherDecoder(canonicalize_text(flatland));

@test (decode_text(pd, "aba") in ("ded", "did", "ece", "ele", "eme", "ere", "eve", "eye", "iti", "mom", "ses", "tat", "tit"));

# Base.Test tests for generating arrays of bigrams
@test (bigrams("this") == [('t', 'h'), ('h', 'i'), ('i', 's')]);

@test (bigrams(["this", "is", "a", "test"]) == [("this", "is"), ("is", "a"), ("a", "test")]);

flatland = String(read("./aima-data/EN-text/flatland.txt"));
word_sequence = extract_words(flatland);
P = UnigramWordModel(word_sequence);
segmented_text, p = viterbi_text_segmentation("itiseasytoreadwordswithoutspaces", P);

@test (segmented_text == ["it", "is", "easy", "to", "read", "words", "without", "spaces"]);

# Base.Test tests for IR systems
uc = UnixConsultant();

function check_query(irs::T, results::AbstractVector, expected::AbstractVector) where {T <: AbstractInformationRetrievalSystem}
    @test (length(results) == length(expected));
    for (i, (score, id)) in enumerate(results)
        expected_score, expected_url = expected[i];
        @test (@sprintf("%.4f", score) == @sprintf("%.4f", expected_score));
        @test (basename(irs.documents[id].url) == basename(expected_url));
    end
    nothing;
end

check_query(uc,
            execute_query(uc, "how do I remove a file"),
            [(0.7683, "aima-data/MAN/rm.txt"),
            (0.6783, "aima-data/MAN/tar.txt"),
            (0.6779, "aima-data/MAN/cp.txt"),
            (0.6658, "aima-data/MAN/zip.txt"),
            (0.6458, "aima-data/MAN/gzip.txt"),
            (0.6374, "aima-data/MAN/pine.txt"),
            (0.6295, "aima-data/MAN/shred.txt"),
            (0.5746, "aima-data/MAN/pico.txt"),
            (0.4338, "aima-data/MAN/login.txt"),
            (0.4193, "aima-data/MAN/ln.txt")]);

check_query(uc,
            execute_query(uc, "how do I delete a file"),
            [(0.7547, "aima-data/MAN/diff.txt"),
            (0.6912, "aima-data/MAN/pine.txt"),
            (0.6356, "aima-data/MAN/tar.txt"),
            (0.6063, "aima-data/MAN/zip.txt"),
            (0.5746, "aima-data/MAN/pico.txt"),
            (0.5128, "aima-data/MAN/shred.txt"),
            (0.2672, "aima-data/MAN/tr.txt")]);

check_query(uc,
            execute_query(uc, "email"),
            [(0.1839, "aima-data/MAN/pine.txt"),
            (0.1201, "aima-data/MAN/info.txt"),
            (0.0989, "aima-data/MAN/pico.txt"),
            (0.0873, "aima-data/MAN/grep.txt"),
            (0.0807, "aima-data/MAN/zip.txt")]);

check_query(uc,
            execute_query(uc, "word count for files"),
            [(1.2815, "aima-data/MAN/grep.txt"),
            (0.9420, "aima-data/MAN/find.txt"),
            (0.8171, "aima-data/MAN/du.txt"),
            (0.5545, "aima-data/MAN/ps.txt"),
            (0.5342, "aima-data/MAN/more.txt"),
            (0.4200, "aima-data/MAN/dd.txt"),
            (0.1285, "aima-data/MAN/who.txt")]);

if (!Sys.iswindows())  # Windows 7/8 does not install a date executable by default
    check_query(uc, execute_query(uc, "learn: date"), []);
end

check_query(uc,
            execute_query(uc, "2003"),
            [(0.1458, "aima-data/MAN/pine.txt"),
            (0.1162, "aima-data/MAN/jar.txt")]);


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.6.2
#     language: julia
#     name: julia-1.6
# ---

# %%
using Plots, StaticArrays, Parameters
default(colorbar=false)

rotate(u, v, w) = let c = cosd, s = sind
    SMatrix{3, 3}(
        c(u)*c(v), c(u)*s(v)*s(w)-s(u)*c(w), c(u)*s(v)*c(w)+s(u)*s(w),
        s(u)*c(v), s(u)*s(v)*s(w)+c(u)*c(w), c(u)*s(v)*s(w)-c(u)*s(w),
            -s(v),      c(v)*s(w),                c(v)*c(w))'
end

"""Assume `surffunc(u, v)` returns `(x, y, z, h)`."""
function plot_surface(u, v, surffunc, param; R = rotate(0, 0, 0), kwargs...)
    xyzh = surffunc.(u', v, Ref(param))
    xyz = (((x, y, z, h),) -> R * SVector(x, y, z)).(xyzh)
    x, y, z = ((a -> a[i]).(xyz) for i in 1:3)
    h = (((x, y, z, h),) -> h).(xyzh)
    surface(x, y, z; fill_z = h, kwargs...)
end

# %%
pyplot(fmt=:png)

function torusfunc(u, v, param)
    @unpack a, b, d = param
    x = (b + a * cos(u)) * cos(v)
    y = (b + a * cos(u)) * sin(v)
    z =      a * sin(u)
    h = d' * SVector(x, y, z)
    x, y, z, h
end

torusparam = (a = 5, b = 10, d = SVector(1, 0, 1))
n = 50
u_torus = v_torus = range(0, 2π; length = n + 1)
plot_surface(u_torus, v_torus, torusfunc, torusparam; size=(500, 400),
    lims=(-20, 20), color=:CMRmap, R=rotate(30, 30, 30))

# %%
pyplot(fmt=:png)
@time anim = @animate for t in range(0, 360; length=41)[1:end-1]
    plot_surface(u_torus, v_torus, torusfunc, torusparam; size=(400, 300),
        lims=(-20, 20), color=:CMRmap, R=rotate(30, 30, 30),
        camera=(t, 20), ticks=false)
end
PyPlot.clf()
gif(anim, "torus.gif")

# %%
pyplot(fmt=:png)

function spherefunc(u, v, param)
    @unpack r, d, f = param
    x = r * cos(u) * cos(v)
    y = r * cos(u) * sin(v)
    z = r * sin(u)
    h = f(d' * SVector(x, y, z))
    x, y, z, h
end

sphereparam = (
    r = 20,
    d = SVector(0, 0, 1),
    f = h -> (1 + abs(h - 1)) * sin(h)
)
n = 50
u_sphere = range(-π/2, π/2; length = n + 1)
v_sphere = range(0, 2π; length = 2n + 1)
plot_surface(u_sphere, v_sphere, spherefunc, sphereparam; size=(500, 400),
    lims=(-20, 20), color=:gist_earth, R=rotate(30, 30, 30),
    camera=(30, 20))

# %%
pyplot(fmt=:png)
@time anim = @animate for t in range(0, 360; length=41)[1:end-1]
    plot_surface(u_sphere, v_sphere, spherefunc, sphereparam; size=(400, 300),
        lims=(-20, 20), color=:gist_earth, R=rotate(30, 30, 30),
        camera=(t, 20), ticks=false)
end
PyPlot.clf()
gif(anim, "sphere.gif")

# %%
plotly(fmt=:auto)
plot_surface(u_torus, v_torus, torusfunc, torusparam; size=(500, 500),
    lims=(-20, 20), color=:CMRmap, R=rotate(30, 30, 30))

# %%
plotly(fmt=:auto)
plot_surface(u_sphere, v_sphere, spherefunc, sphereparam; size=(500, 500),
    lims=(-20, 20), color=:gist_earth, R=rotate(30, 30, 30))

# %%
using Pkg
println("Julia v", VERSION)
Pkg.status("IJulia")
Pkg.status("PyCall"; mode = PKGMODE_MANIFEST)
Pkg.status("Plots")
Pkg.status("PyPlot")
Pkg.status("Plotly")
Pkg.status("StaticArrays")
Pkg.status("Parameters")

# %%


using BenchmarkTools
using Printf
using Transducers

benchmark = true

readData(filename) = readlines(filename) |> Map(line -> parse(Int64, line)) |> collect

example1Data = readData("src/advent/day10/example1.txt")
example2Data = readData("src/advent/day10/example2.txt")
inputData = readData("src/advent/day10/input.txt")

#### part 1

function part1(data)
    differenceCounts = zeros(Int64, 3)
    data1 = sort(data)
    current = 0
    for value ∈ data1
        differenceCounts[value - current] += 1
        current = value
    end
    differenceCounts[3] += 1
    differenceCounts[1] * differenceCounts[3]
end

@assert part1(example1Data) == 35
@assert part1(example2Data) == 220

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

#### part 2

function part2(data)
    function collate(x::Array{Tuple{Int64, Int64}, 1}, y::Int64)
        if isempty(x)
            push!(x, (y, 1))
        else
            r = last(x)
            if r[1] == y
                x[length(x)] = (r[1], r[2] + 1)
            else
                push!(x, (y, 1))
            end
        end
        x
    end

    # permutations per consecutive positions
    multipliers = [1, 2, 4, 7]

    data1 = sort(data)
    pushfirst!(data1, 0)
    x = @view data1[1:length(data1)-1]
    y = @view data1[2:length(data1)]

    zip(x, y) |>
    Map(r -> r[2] - r[1]) |>
    foldxl(collate; init = Array{Tuple{Int64, Int64}, 1}()) |>
    Filter(r -> r[1] == 1 && r[2] > 1) |>
    Map(r -> multipliers[r[2]]) |>
    foldxl(*; init = 1)
end

@assert part2(example1Data) == 8
@assert part2(example2Data) == 19208

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


### A Pluto.jl notebook ###
# v0.14.8

using Markdown
using InteractiveUtils

# ╔═╡ 79168b5e-0805-4413-848a-8c501d81d91c
md"""
## 3.2
 $N$個の整数$a_0, a_1, \dots, a_{N-1}$のうち, 整数値$v$が何個含まれるかを求める$O(N)$のアルゴリズムを設計してください.
"""

# ╔═╡ e76f50bc-5cd6-481f-8fe8-b523534c1e11
function f₂(as, v)
	cnt = 0
	for a ∈ as
		if a == v
			cnt += 1
		end
	end
	return cnt
end

# ╔═╡ add92227-31f6-4408-a527-8f46f586c531
f₂([1 2 3 4 5 3], 3)

# ╔═╡ db1a3c61-5a3d-4431-b3e1-786415736801
f₂([1 2 3 4 5 3], 100)

# ╔═╡ 4349dc9c-cfcc-472e-b611-f87f22c9d4ee
md"""
## 3.3
 $N(\ge 2)$個の相異なる整数$a_0, a_1, \dots, a_{N-1}$が与えられます. このうち2番目に小さい値を求める$O(N)$のアルゴリズムを設計してください.
"""

# ╔═╡ 01f68ae8-e149-44dd-bf11-44e428f29500
function f₃(as)
	max₁, max₂ = maximum(as[1:2]), minimum(as[1:2])
	for a ∈ as
		if a > max₁
			max₂ = max₁
			max₁ = a
		elseif max₁ > a > max₂
			max₂ = a
		end
	end

	return max₂
end

# ╔═╡ b82ccb0e-c956-4e57-9470-7bd5ef4df4b3
f₃([1 2 3 4 5 6])

# ╔═╡ 8df5aae2-5299-40b0-a0db-ac83badd21b1
f₃([1 100 3 4 105 6])

# ╔═╡ f8afba4c-cf89-11eb-0c82-0fe26b9b6ff9
md"""
## 3.4
 $N$個の整数$a_0, a_1, \dots, a_{N-1}$が与えられます. この中から2つ選んで差をとります. その差の最大値を求める$O(N)$のアルゴリズムを設計してください.
"""

# ╔═╡ 6abeb865-69a0-43c9-bff3-03b17d2d6dfe
f₄(as) = maximum(as) - minimum(as)

# ╔═╡ 72bb5735-f769-4043-9305-41dd590f95e5
f₄([1 2 3 4 5 6])

# ╔═╡ f1b037f4-be79-4563-9e5f-97e2a3105d63
f₄([1 100 3 4 105 6])

# ╔═╡ Cell order:
# ╟─79168b5e-0805-4413-848a-8c501d81d91c
# ╠═e76f50bc-5cd6-481f-8fe8-b523534c1e11
# ╠═add92227-31f6-4408-a527-8f46f586c531
# ╠═db1a3c61-5a3d-4431-b3e1-786415736801
# ╟─4349dc9c-cfcc-472e-b611-f87f22c9d4ee
# ╠═01f68ae8-e149-44dd-bf11-44e428f29500
# ╠═b82ccb0e-c956-4e57-9470-7bd5ef4df4b3
# ╠═8df5aae2-5299-40b0-a0db-ac83badd21b1
# ╟─f8afba4c-cf89-11eb-0c82-0fe26b9b6ff9
# ╠═6abeb865-69a0-43c9-bff3-03b17d2d6dfe
# ╠═72bb5735-f769-4043-9305-41dd590f95e5
# ╠═f1b037f4-be79-4563-9e5f-97e2a3105d63


# indicator of the free cone

"""
  IndFree()

Returns the indicator function of the whole space, or "free cone", i.e.,
a function which is identically zero.
"""

immutable IndFree <: IndicatorConvexCone end

"""
  Zero()

Returns the identically zero function.
"""

typealias Zero IndFree

function (f::IndFree)(x::AbstractArray)
  return 0.0
end

function prox!(f::IndFree, x::AbstractArray, y::AbstractArray, gamma::Real=1.0)
  y[:] = x
  return 0.0
end

fun_name(f::IndFree) = "indicator of the free cone"
fun_dom(f::IndFree) = "AbstractArray{Real}, AbstractArray{Complex}"
fun_expr(f::IndFree) = "x ↦ 0"
fun_params(f::IndFree) = "none"

function prox_naive(f::IndFree, x::AbstractArray, gamma::Real=1.0)
  return x, 0.0
end


using factade
using Test

factade.my_f(2,1)
der_of_my_f(2,1)

@testset "factade.jl" begin
    @test my_f(2,1)==7
    @test my_f(2,3)==13
end

``` create another test set for the new function added```
@testset "Derivative Tests" begin
    @test der_of_my_f(2,1)==2
end


using MOBC, vOptGeneric, CPLEX, PyPlot, Suppressor
using Base.Test

function random_instance(range, n, rng)
	MT = MersenneTwister(rng)
	m = vModel(solver = CplexSolver(CPX_PARAM_SCRIND = 0))
	p1 = rand(MT, range, n)
	p2 = rand(MT, range, n)
	w = [rand(MT, range, n) for i = 1:3]
	c = sum.(w).÷2

	@variable(m, x[1:length(p1)], Bin)
	@addobjective(m, Max, dot(x, p1))
	@addobjective(m, Max, dot(x, p2))
	@constraint(m, dot(x, w[1]) <= c[1])
	@constraint(m, dot(x, w[2]) <= c[2])
	@constraint(m, dot(x, w[3]) <= c[3])
	m
end

function hard_instance(solver=CplexSolver(CPX_PARAM_SCRIND = 0))
    n = 30
    p1 = [85 ,88 ,82 ,64 ,55 ,63 ,77 ,92 ,52 ,64 ,91 ,59 ,90 ,74 ,81 ,80 ,75 ,95 ,52 ,83 ,88 ,50 ,94 ,67 ,91 ,95 ,50 ,74 ,56 ,76]
    p2 = [56 ,58 ,78 ,55 ,69 ,86 ,92 ,84 ,88 ,60 ,62 ,99 ,91 ,62 ,56 ,96 ,75 ,71 ,77 ,87 ,80 ,87 ,72 ,99 ,91 ,74 ,94 ,50 ,98 ,100]
    w1 = [79 ,72 ,60 ,72 ,53 ,53 ,56 ,69 ,57 ,73 ,55 ,66 ,51 ,67 ,74 ,57 ,85 ,98 ,73 ,77 ,58 ,69 ,70 ,76 ,72 ,71 ,87 ,55 ,51 ,83]
    w2 = [67 ,67 ,96 ,88 ,83 ,52 ,60 ,77 ,60 ,75 ,79 ,66 ,71 ,73 ,65 ,58 ,69 ,100 ,67 ,99 ,59 ,69 ,81 ,95 ,59 ,79 ,86 ,56 ,65 ,73]
    w3 = [55 ,72 ,77 ,62 ,51 ,58 ,85 ,76 ,57 ,52 ,83 ,74 ,69 ,89 ,61 ,66 ,66 ,71 ,94 ,61 ,59 ,60 ,69 ,51 ,64 ,84 ,92 ,62 ,67 ,50]
    b = [1019, 1097, 1018]

    m = vModel(solver = solver)
    @variable(m, x[1:n], Bin)
    @addobjective(m, Max, dot(x,p1))
    @addobjective(m, Max, dot(x,p2))
    @constraint(m, dot(x, w1) <= b[1])
    @constraint(m, dot(x, w2) <= b[2])
    @constraint(m, dot(x, w3) <= b[3])

    m
end

function benchmark(itemrange, nrange, runpersize ; time_limit=300, parragh=true, epsilon=true, args...)
	rng = 0
	res_stidsen=Dict{Int, Vector{Float64}}() ; nb_nodes_stidsen=Dict{Int, Vector{Int}}()
	res_parragh=Dict{Int, Vector{Float64}}() ; nb_nodes_parragh=Dict{Int, Vector{Int}}()
	res_eps=Dict{Int, Vector{Float64}}()
	for n in nrange
		res_stidsen[n]=Float64[] ; res_parragh[n]=Float64[] ; res_eps[n]=Float64[]
		nb_nodes_stidsen[n]=Int[] ; nb_nodes_parragh[n]=Int[]
		for i = 1:runpersize
			m = random_instance(itemrange, n, rng)
			rng += 1
			global m_current = m
			m_copy = copy(m)
			status,tEPS,_ = @suppress @timed solve(m_copy, method=:epsilon, round_results=true);
			valBC, tBC, _ = @timed solve_stidsen(m ; time_limit=time_limit, args...);
			valPAR, tPAR, _ = 	if parragh
									@timed solve_parragh(m ; time_limit=time_limit, args...);
								else
									valBC, tBC, _
								end

			if (!(valBC.timeout) && length(getY_N(m_copy)) != length(valBC.YN)) || (!(valPAR.timeout) && length(valPAR.YN) != length(getY_N(m_copy)))
				figure(3)
				clf()
				plot(map(first, valPAR.YN), map(last,valPAR.YN), "b>", markersize="5", label="parragh")
				plot(map(first, valBC.YN), map(last, valBC.YN), "r<", markersize="5", label="stidsen")
				plot(map(first, getY_N(m)), map(last, getY_N(m)), "go", markersize="2", label="epsilon")
				legend()
				error()
			end

			push!(res_stidsen[n], valBC.timeout ? time_limit : tBC) ; push!(res_parragh[n], valPAR.timeout ? time_limit : tPAR) ; push!(res_eps[n], tEPS)
			!(valPAR.timeout) && push!(nb_nodes_parragh[n], valPAR.nodes)
			!(valBC.timeout) && push!(nb_nodes_stidsen[n], valBC.nodes)

			#figure(1) ; clf()
			#xlabel("n") ; ylabel("t(s)") ; title("time per instance")
			###parragh || epsilon && plot([],[],"gx",markersize=3,label="λ-scalarisation")
			###parragh && plot([],[],"bx",markersize=3,label="parragh")
			##epsilon && plot([],[],"k.",markersize=3,label="epsilon")
			#for k in keys(res_stidsen)
			#	plot(fill(k, length(res_stidsen[k])), res_stidsen[k], "gx", markersize=3)
				#parragh && plot(fill(k, length(res_stidsen[k])), res_parragh[k], "bx", markersize=3)
	#		#	epsilon && plot(fill(k, length(res_stidsen[k])), res_eps[k], "k.", markersize=3)
			#end
	#		legend()

			figure(2) ; clf()
			xlabel("n") ; ylabel("#noeuds") ; title("temps et nombre de noeuds en moyenne")
			k = sort(collect(keys(res_stidsen)))
			plot(k, [mean(nb_nodes_stidsen[a]) for a in k], "gs", markersize=3) ; plot(k, [mean(nb_nodes_stidsen[a]) for a in k], "g--", linewidth=1)
			if parragh
				plot(k, [mean(nb_nodes_parragh[a]) for a in k], "bs", markersize=3) ; plot(k, [mean(nb_nodes_parragh[a]) for a in k], "b--", linewidth=1)
			end
			twinx() ; ylabel("temps moyen (s)")
			plot([],[],"g--",markersize=3,label="#noeuds λ-scalarisation") #labels dont work after twinx(), had to put a dummy plot
			parragh && plot([],[],"b--",markersize=3,label="#noeuds parragh")
			plot(k, [mean(res_stidsen[a]) for a in k], "gx", markersize=3) ; plot(k, [mean(res_stidsen[a]) for a in k], "g-", linewidth=1, label="tmoy λ-scalarisation")
			if parragh
				plot(k, [mean(res_parragh[a]) for a in k], "bx", markersize=3) ; plot(k, [mean(res_parragh[a]) for a in k], "b-", linewidth=1, label="tmoy parragh")
			end
			if epsilon
				plot(k, [mean(res_eps[a])for a in k], "k-", linewidth=1, label="tmoy epsilon")
			end
			legend()
		end
		println("\n####################################\nn = $n")
		println("tmoy eps : $(mean(res_eps[n]))") ; println("tmoy stidsen : $(mean(res_stidsen[n]))") ; parragh && println("tmoy parragh : $(mean(res_parragh[n]))")
		println("moy : $(round(mean(nb_nodes_stidsen[n]),1)) noeuds par BC Stidsen") ; parragh && println("moy : $(round(mean(nb_nodes_parragh[n]),1)) noeuds par BC Parragh")
	end
	res_stidsen, res_parragh, res_eps, nb_nodes_stidsen, nb_nodes_parragh
end

function benchmark_stidsen(itemrange, nrange, runpersize ; time_limit=300,  epsilon=true)
	rng = 0
	config_1 = "avec cover cuts"
	config_2 = "sans cover cuts"
	res_1=Dict{Int, Vector{Float64}}() ; nb_nodes_1=Dict{Int, Vector{Int}}()
	res_2=Dict{Int, Vector{Float64}}() ; nb_nodes_2=Dict{Int, Vector{Int}}()
	res_eps=Dict{Int, Vector{Float64}}()
	for n in nrange
		res_1[n]=Float64[] ; res_2[n]=Float64[] ; res_eps[n]=Float64[]
		nb_nodes_1[n]=Int[] ; nb_nodes_2[n]=Int[]
		for i = 1:runpersize
			m = random_instance(itemrange, n, rng)
			rng += 1
			global m_current = m
			m_copy = copy(m)
			status,tEPS,_ = @suppress @timed solve(m_copy, method=:epsilon, round_results=true);
			val2, t2, _ = @timed solve_stidsen(m ; time_limit=time_limit, docovercuts=false, lift_covers=false, use_nsga=false, global_nsga=false, global_branch=false, preprocess=false, fill_triangles=false);
			val1, t1, _ = @timed solve_stidsen(m ; time_limit=time_limit, docovercuts=true, lift_covers=true, use_nsga=false, global_nsga=false, global_branch=false, preprocess=false, fill_triangles=false);

			if (!(val1.timeout) && !(val2.timeout) && length(val1.YN) != length(val2.YN))
				figure(3)
				clf()
				plot(map(first, val1.YN), map(last,val1.YN), "b>", markersize="5", label=config_1)
				plot(map(first, val1.YN), map(last, val1.YN), "r<", markersize="5", label=config_2)
				plot(map(first, getY_N(m)), map(last, getY_N(m)), "go", markersize="2", label="epsilon")
				legend()
				error()
			end

			push!(res_1[n], val1.timeout ? time_limit : t1) ; push!(res_2[n], val2.timeout ? time_limit : t2) ; push!(res_eps[n], tEPS)
			!(val1.timeout) && push!(nb_nodes_1[n], val1.nodes)
			!(val2.timeout) && push!(nb_nodes_2[n], val2.nodes)

			figure(1) ; clf()
			xlabel("n") ; ylabel("#noeuds") ; title("temps et nombre de noeuds en moyenne")
			k = sort(collect(keys(res_1)))
			plot(k, [mean(nb_nodes_1[a]) for a in k], "gs", markersize=3) ; plot(k, [mean(nb_nodes_1[a]) for a in k], "g--", linewidth=1)
			plot(k, [mean(nb_nodes_2[a]) for a in k], "bs", markersize=3) ; plot(k, [mean(nb_nodes_2[a]) for a in k], "b--", linewidth=1)

			twinx() ; ylabel("temps moyen (s)")
			plot([],[],"g--",markersize=3,label="#noeuds $config_1") #labels dont work after twinx(), had to put a dummy plot
			plot([],[],"b--",markersize=3,label="#noeuds $config_2")
			plot(k, [mean(res_1[a]) for a in k], "gx", markersize=3) ; plot(k, [mean(res_1[a]) for a in k], "g-", linewidth=1, label="tmoy $config_1")
			plot(k, [mean(res_2[a]) for a in k], "gx", markersize=3) ; plot(k, [mean(res_2[a]) for a in k], "b-", linewidth=1, label="tmoy $config_2")
			if epsilon
				plot(k, [mean(res_eps[a])for a in k], "k-", linewidth=1, label="tmoy epsilon")
			end
			legend()
		end
		println("\n####################################\nn = $n")
		println("tmoy eps : $(mean(res_eps[n]))") ; println("tmoy $config_1 : $(mean(res_1[n]))") ; println("tmoy $config_2 : $(mean(res_2[n]))")
		println("moy : $(round(mean(nb_nodes_1[n]),1)) noeuds $config_1") ; println("moy : $(round(mean(nb_nodes_2[n]),1)) noeuds $config_2")
	end
end

benchmark(1:20, 10:11, 1, use_nsga=true, global_branch=false, docovercuts=true, lift_covers=true)
# inst = hard_instance2(CplexSolver(CPX_PARAM_SCRIND = 0))
srand(0)
benchmark(1:20, 10:5:60, 10, use_nsga=true, global_branch=false, docovercuts=true, lift_covers=true, time_limit=30, parragh=false)


using Xsum, Test, Random

Random.seed!(314159)
setprecision(65536)

@testset "xsum" begin
    for n in (100, 10^4)
        a = randn(n)
        exact = Float64(sum(big.(a)))
        @test exact == xsum(a)

        A = [a a]
        @test exact == xsum(@view A[:,1])
        @test exact == xsum(@view Array(A')[1,:])

        @test 2exact == xsum(2x for x in a) == xsum(x -> 2x, a)

        @test Complex(2exact, 4exact) == xsum(Complex(2x,4x) for x in a)
    end
end


"""
Modia module with rotational component models (inspired from Modelica Standard Library).

* Developer: Hilding Elmqvist, Mogram AB, Martin Otter, DLR
* Copyright (c) 2016-2021: Hilding Elmqvist, Martin Otter
* License: MIT (expat)

"""
#module Rotational

#export Flange, Inertia, Spring, SpringDamper, EMF, IdealGear, Torque, CurrentSensor, Fixed, Damper, IdealGear_withSupport, SpeedSensor

using TinyModia
using Unitful

# Connector for 1D rotational systems
Flange = Model(
    phi = potential,
    tau = flow
)

# Flange fixed in housing at a given angle
#=
Fixed = Model(
    flange = Flange,
    phi0   = 0.0u"rad",
    equations = :[
        flange.phi = 0.0u"rad"]
)
=#
Fixed = Model(
    flange = Flange,
    equations = :[
        flange.phi = 0.0]
)

# 1D-rotational component with inertia
Inertia = Model(
    flange_a = Flange, # (info = "Left flange of shaft")
    flange_b = Flange, # (info = "Right flange of shaft")
    J = 1.0u"kg*m^2", # (0, min=0, info = "Moment of inertia") #, T = u"kg*m^2")
    phi = Var(init=0.0u"rad"),
    w = Var(init=0.0u"rad/s"),
    equations = :[
        phi = flange_a.phi
        phi = flange_b.phi
        w = der(phi)
        a = der(w)
        J * a = flange_a.tau + flange_b.tau ]
)

# Partial model for the compliant connection of two rotational 1-dim. shaft flanges
PartialCompliant = Model(
    flange_a = Flange,
    flange_b = Flange,
    partialEquations = :[
        phi_rel = flange_b.phi - flange_a.phi
        flange_b.tau = tau
        flange_a.tau = -tau ]
)

# Linear 1D rotational spring
Spring = PartialCompliant | Model(
    c = 1.0u"N*m/rad",   # (min = 0, info = "Spring constant")
    phi_rel0 = 0u"rad", # (info = "Unstretched spring angle")
    equations = :[
        tau = c * (phi_rel - phi_rel0) ]
)

# Linear 1D rotational spring with damper
SpringDamper = PartialCompliant | Model(
    c = 1.0*u"N*m/rad", # (min = 0, info = "Spring constant")
    d = 0.0u"N*m*s/rad", # (info = "Damping constant")
    phi_rel0 = 0u"rad", # (info = "Unstretched spring angle")
    equations = :[
        tau = c * (phi_rel - phi_rel0) + d * der(phi_rel) ]
)

# Electromotoric force (electric/mechanic) transformer
EMF = Model(
    k = 1.0u"N*m/A", # (info = "Transformation coefficient")
    p = Pin,
    n = Pin,
    flange = Flange,

    equations = :[
        v = p.v - n.v
        0 = p.i + n.i
        i = p.i

        phi = flange.phi
        w = der(phi)
        k * w = v
        flange.tau = -k * i ]
)

# Ideal gear
IdealGear = Model(
    flange_a = Flange, # "Left flange of shaft"
    flange_b = Flange, # "Right flange of shaft"
    ratio    = 1.0, # "Transmission ratio (flange_a.phi/flange_b.phi)"
    equations = :[
        phi_a = flange_a.phi
        phi_b = flange_b.phi
        phi_a = ratio * phi_b
        0 = ratio * flange_a.tau + flange_b.tau ]
)

# Ideal gear with support
IdealGear_withSupport = Model(
    flange_a = Flange, # "Left flange of shaft"
    flange_b = Flange, # "Right flange of shaft"
    support  = Flange, # "Support flange"
    ratio    = 1.0,  # "Transmission ratio"
    equations = :[
        phi_a = flange_a.phi - support.phi
        phi_b = flange_b.phi - support.phi
        phi_a = ratio * phi_b
        0 = ratio * flange_a.tau + flange_b.tau
        0 = flange_a.tau + flange_b.tau + support.tau ]
)

# Partial input signal acting as external torque on a flange
PartialTorque = Model(
    tau = input,
    flange = Flange
)

# Input signal acting as external torque on a flange
Torque         = PartialTorque | Model(equations = :[flange.tau = -tau])
UnitlessTorque = PartialTorque | Model(equations = :[flange.tau = -tau*u"N*m"])

#=
# Partial model for the compliant connection of two rotational 1-dim. shaft flanges where the relative angle and speed are used as preferred states
PartialCompliantWithRelativeStates = Model(
    flange_a = Flange,
    flange_b = Flange,
    init     = Map(phi_rel=0.0u"rad", w_rel=0.0u"rad/s"),
    equations = :[
        phi_rel      = flange_b.phi - flange_a.phi
        w_rel        = der(phi_rel)
        a_rel        = der(w_rel)
        flange_b.tau = tau
        flange_a.tau = -tau ]
)

# Linear 1D rotational damper
Damper = PartialCompliantWithRelativeStates | Model(
    d = 1.0u"N*m*s/rad", # (info = "Damping constant"),
    equation = :[
        tau = d * w_rel ]
)
=#
# Linear 1D rotational damper
Damper = Model(
    flange_a = Flange,
    flange_b = Flange,
    phi_rel  = Var(start=0.0u"rad"),
    d        = 1.0u"N*m*s/rad", # (info = "Damping constant"),
    equation = :[
        phi_rel      = flange_b.phi - flange_a.phi
        w_rel        = der(phi_rel)
        flange_b.tau = tau
        flange_a.tau = -tau
        tau          = d * w_rel ]
)

# Partial model to measure a single absolute flange variable
PartialAbsoluteSensor = Model(
    flange   = Flange,
    equation = :[flange.tau = 0]
)

# Ideal sensor to measure the absolute flange angular velocity
SpeedSensor         = PartialAbsoluteSensor | Model(w = output, equations = :[w = der(flange.phi)])
UnitlessSpeedSensor = PartialAbsoluteSensor | Model(w = output, equations = :[w = der(flange.phi)*u"s/rad"])

#end


using DelimitedFiles
using Plots; gr();
using LaTeXStrings
# export DelimitedFiles, readdlm, writedlm

l25 = readdlm("25ncells0.2t.txt", Float64)
l50 = readdlm("50ncells0.2t.txt", Float64)
l100 = readdlm("100ncells0.2t.txt", Float64)

nl25 = readdlm("25ncells0.2tnosl.txt", Float64)
nl50 = readdlm("50ncells0.2tnosl.txt", Float64)
nl100 = readdlm("100ncells0.2tnosl.txt", Float64)

as25 = readdlm("25ncells0.8t.txt", Float64)
as50 = readdlm("50ncells0.8t.txt", Float64)
as100 = readdlm("100ncells0.8t.txt", Float64)

l50r = zeros(25)
l100r = zeros(25)
nl50r = zeros(25)
nl100r = zeros(25)
as50r = zeros(25)
as100r = zeros(25)
for i in 1:length(l25)
    l50r[i] = l50[2i-1]
    l100r[i] = l100[4i-3]
    nl50r[i] = nl50[2i-1]
    nl100r[i] = nl100[4i-3]
    as50r[i] = as50[2i-1]
    as100r[i] = as100[4i-3]
end
xt = range(0,stop=0.96,length=25)

plot(xt,[l25,l50r,l100r],label=["25Cells" "50Cells" "100Cells"],title="Plot at 3 different resolution at time = 0.2 with the slope limiter")
savefig("3plotwithlimiter.png")

plot(xt,[nl25,nl50r,nl100r],label=["25Cells" "50Cells" "100Cells"],title="Plot at 3 different resolution at time = 0.2 without the slope limiter")
savefig("3plotwithlimiternolimiter.png")

plot(xt,[l50r-l25,2(l100r-l50r)],label=["dx/2-dx" "2(dx/4-dx/2)"],title="order of convergence at time = 0.2 with the slope limiter, a=2")
savefig("orderofconvergencelimiter.png")

plot(xt,[nl50r-nl25,2(nl100r-nl50r)],label=["dx/2-dx" "2(dx/4-dx/2)"],title="order of convergence at time = 0.2 without the slope limiter, a=2")
savefig("orderofconvergencenolimiter.png")

plot(xt,[as25,as50r,as100r],label=["25Cells" "50Cells" "100Cells"],title="Plot at 3 different resolution at time = 0.8")
savefig("3plotlatertime.png")

plot(xt,[as50r-as25,2(as100r-as50r)],label=["dx/2-dx" "2(dx/4-dx/2)"],title="order of convergence at time = 0.8, with a=2")
savefig("orderofconvergencelatertime.png")
# l


using Distributions: Gaussian
using Test

@testset "Events" begin include("events.jl") end
@testset "TrajectoryTests" begin include("trajectories.jl") end
@testset "TrajectoryProbability" begin include("probability.jl") end
@testset "SimpleSystem" begin include("SimpleSystem.jl") end
@testset "ComplexSystem" begin include("ComplexSystem.jl") end
@testset "SMCEstimate" begin include("SMCEstimate.jl") end
@testset "MCMCMoves" begin include("mcmc_moves.jl") end
@testset "Gaussian" begin include("gaussian.jl") end


"""
Caputo sense fractional derivative algorithms, please refer to [Caputo derivative](https://en.wikipedia.org/wiki/Fractional_calculus#Caputo_fractional_derivative) for more details.
"""
abstract type Caputo <: FracDiffAlg end

"""
# Caputo sense fractional derivative.

    fracdiff(f::Function, α, start_point, end_point, step_size, ::Caputo)

### Example:

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

Returns a tuple (**result**, **error**), which means the value of this derivative is 141.59714979764541, and the error estimate is 1.1532243848672914e-6.

Refer to [Caputo derivative](https://en.wikipedia.org/wiki/Fractional_calculus#Caputo_fractional_derivative)

!!! note
    ```Caputo Direct``` algorithms belong to direct computing, precise are ensured, but maybe cause more memory allocation and take more compilation time.

Using the direct mathematic expression:

```math
^CD_t^α=\\frac{1}{\\Gamma(n-α)}\\int_0^t\\frac{f^{(n)}(τ)}{(t-τ)^{α+1-n}}
```

As for the derivative inside the integral, we use the **Complex Step Differentiation** to obtain a more accurate value.
"""
struct CaputoDirect <: Caputo end

"""
# Caputo sense fractional derivative with first derivative known.

    fracdiff(fd, α, start_point, end_point, CaputoDirectFirstDiffKnown())

If the first order derivative of a function is already known, we can use this method to compute the fractional order derivative more precisely.

The inout function should be the first order derivative of a function

### Example

```julia-repl
julia> fracdiff(x->5*x^4, 0.5, 0, 2.5, CaputoDirectFirstDiffKnown())
```
Return the semi-derivative of ``f(x)=x^5`` at ``x=2.5``.

Compared with **CaputoDirect** method, this method don't need to specify step size, more precision are guaranteed.
"""
struct CaputoDirectFirstDiffKnown <: Caputo end

"""
# Caputo sense fractional derivative with first and second derivative known.

    fracdiff(fd1, fd2, α, start_point, end_point, Caputo_Direct_First_Second_Diff_Known)

If the first and second order derivative of a function is already known, we can use this method to compute the fractional order derivative more precisely.

### Example

```julia-repl
julia> fracdiff(x->5*x^4, x->20*x^3, 0.5, 0, 2.5, Caputo_Direct_First_Second_Diff_known())
```
Return the semi-derivative of ``f(x)=x^5`` at ``x=2.5``.

Compared with **CaputoDirect** method, this method don't need to specify step size, more precision are guaranteed.
"""
struct Caputo_Direct_First_Second_Diff_Known <: Caputo end

"""
# Caputo sense Piecewise algorithm

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

Using piecewise linear interpolation function to approximate input function and combining Caputo derivative then implement summation.

### Example

```julia-repl
julia> fracdiff(x->x^5, 0.5, 2.5, 0.001, CaputoPiecewise())
```

Return the fractional derivative of ``f(x)=x^5`` at point ``x=2.5``.

```tex
@article{LI20113352,
title = {Numerical approaches to fractional calculus and fractional ordinary differential equation},
author = {Changpin Li and An Chen and Junjie Ye},
}
```

"""
struct CaputoPiecewise <: Caputo end

"""
# Caputo sense Diethelm computation

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

Using quadrature weights(derived from product trapezoidal rule) to approximate the derivative.

### Example

```julia-repl
julia> fracdiff(x->x, 0.5, 1, 0.007, CaputoDiethelm())
1.128378318687192
```

!!! info "Scope"
    0 < α < 1

Diethelm's method for computingn Caputo sense fractional derivative using [product trapezoidal rule](https://en.wikipedia.org/wiki/Trapezoidal_rule) and [Richardsin extrapolation](https://en.wikipedia.org/wiki/Richardson_extrapolation)

```tex
@article{10.1093/imanum/17.3.479,
author = {DIETTELM, KAI},
title = "{Generalized compound quadrature formulae for finite-part integrals}",
journal = {IMA Journal of Numerical Analysis},
doi = {10.1093/imanum/17.3.479},
}
```

"""
struct CaputoDiethelm <: Caputo end

"""
# Caputo sense fractioal derivative with p-th order precision.

    fracdiff(f, α, t, p, CaputoHighPrecision())

Use the high precision algorithm to compute the Caputo sense fractional derivative.

The **p** here is the grade of precision.
"""
struct CaputoHighPrecision <: Caputo end

"""
# Caputo sense fractional derivative high order approximation.

!!! info
    By using ```CaputoHighOrder``` method, we can set ``0<\\alpha<2``

### Example

```julia-repl
julia> fracdiff(x->x, 0.5, 1, 0.01, 2, CaputoHighOrder())
1.1283791670955123
```

```tex
@article{Li2017HighOrderAT,
  title={High-Order Approximation to Caputo Derivatives and Caputo-type Advection–Diffusion Equations: Revisited},
  author={Changpin Li and Minhao Cai},
  journal={Numerical Functional Analysis and Optimization},
  year={2017},
  volume={38},
  pages={861 - 890}
}
```
"""
struct CaputoHighOrder <: Caputo end
################################################################
###                    Type definition done                  ###
################################################################

function fracdiff(f::Union{Function, Number}, α::Float64, start_point, end_point, h::Float64, ::CaputoDirect)
    #checks(f, α, start_point, end_point)
    typeof(f) <: Number ? (end_point == 0 ? 0 : f/sqrt(pi*end_point)) : nothing
    #Using complex step differentiation to calculate the first order differentiation
    g(τ) = imag(f(τ+1*im*h) ./ h) ./ ((end_point-τ) .^α)
    result = quadgk(g, start_point, end_point) ./gamma(1-α)
    return result
end

function fracdiff(f::FunctionAndNumber, α::Float64, start_point, end_point::AbstractArray, h::Float64, ::CaputoDirect)::Vector
    #=
    ResultArray = Float64[]
    for (_, value) in enumerate(end_point)
        append!(ResultArray, fracdiff(f, α, start_point, value, h, CaputoDirect()))
    end
    return ResultArray
    =#
    result = map(x->fracdiff(f, α, start_point, x, h, CaputoDirect())[1], end_point)
    return result
end

function fracdiff(f::FunctionAndNumber, α::Float64, end_point, h::Float64, ::CaputoPiecewise)
    typeof(f) <: Number ? (end_point == 0 ? (return 0) : (return f/sqrt(pi*end_point))) : nothing
    end_point == 0 ? (return 0) : nothing
    m = floor(Int, α)+1

    summation = zero(Float64)
    n = floor(Int, end_point/h)

    @fastmath @inbounds @simd for i ∈ 0:n
        summation += W₅(i, n, m, α)*first_order(f, i*h, h)
    end

    result = summation*h^(m-α)/gamma(m-α+2)
    return result
end
function W₅(i, n, m, α)
    if i == 0
        return n^(m-α)*(m-α+1-n) + (n-1)^(m-α+1)
    elseif i == n
        return 1
    else
        return (n-i-1)^(m-α+1) + (n-i+1)^(m-α+1) - 2*(n-i)^(m-α+1)
    end
end
# Deploy Complex Step Differentiation to compute the first order derivative.
first_order(f, point, h::Float64) = imag(f(point + im*h))/h

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

#=
Caputo Diethelm algorithm
=#
function fracdiff(f::FunctionAndNumber, α::Float64, end_point::Number, h::Float64, ::CaputoDiethelm)
    #checks(f, α, 0, end_point)
    typeof(f) <: Number ? (end_point == 0 ? 0 : f/sqrt(pi*end_point)) : nothing
    N = round(Int, end_point/h)
    result = zero(Float64)

    @fastmath @inbounds @simd for i ∈ 0:N
        result += quadweights(i, N, α)*(f(end_point-i*h) - f(0))
    end

    return result*h^(-α)/gamma(2-α)
end
function quadweights(n, N, α)
    if n == 0
        return 1
    elseif  0 < n < N
        return (n+1)^(1-α) - 2*n^(1-α) + (n-1)^(1-α)
    elseif n == N
        return (1-α)*N^(-α) - N^(1-α) + (N-1)^(1-α)
    end
end

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

#=
Caputo sense high precision algorithm
=#
function fracdiff(y::FunctionAndNumber, α, t, p::Integer, ::CaputoHighPrecision)
    h = t[2]-t[1]
    t = t[:]
    n = length(t)
    y = y.(t)
    q = ceil(Int, α)
    r::Int = max(p, q)
    R = reverse(Vandermonde(collect(0:(r-1)).*h))
    c = inv(R)*y[1:r]
    u = 0
    du = 0
    for i = 1:r
        u = u.+c[i]*t.^(i-1)
    end
    if q < r
        for i = (q+1):p
            du = du.+c[i]*t.^(i-1-α)*gamma(1)/gamma(i-α)
        end
    end

    v = y[:] - u[:]
    dv = fracdiff(v, α, t, p, GL_High_Precision())
    dy = dv[:] + du[:]
    return dy
end

function fracdiff(f::FunctionAndNumber, α, T, h, p::Integer, ::CaputoHighOrder)
    n::Int64 = round(Int, T/h)
    A = wj(n, p, α)
    B = fj(n, T, f)
    C = ones(1, n)
    hh = h^(-α)/(gamma(1-α))
    D = C*A*B*hh
    return D[1]
end

function wj(n::Int64, r::Int64, α)
    A=zeros(n, n+1)
    for iw=1:r-2
        for jw=1:iw+1
            A[iw, jw]=w(iw+1-jw, iw+1, iw, n, α)
        end
    end
    for iw=r-1:n
        for jw=iw-r+2:iw+1
            A[iw, jw]=w(iw+1-jw, r, iw, n, α)
        end
    end
    return A
end

function w(i::Int64, r, j, n, a)
    ar = ones(r-1)
    br = ones(r-1)
    for lj = 1:r-2
        jj = r-lj-1
        kj = r-2
        tj = i-1
        aj = collect(Int64, 0:tj)
        bj = collect(Int64, tj+2:kj+1)
        cj = [aj; bj]
        dj = collect(Int64, -1:tj-1)
        ej = collect(Int64, tj+1:kj)
        fj = [dj; ej]
        yj = binomial.(cj, jj)
        pj = binomial.(fj, jj)
        sj = 1
        tj = 1
        for m = 1:jj
            sj = sj.*yj[:, m]
            tj = tj.*pj[:, m]
            ar[lj] = sum(sj)
            br[lj] = sum(tj)
        end
    end
    s=0
    for l=1:r-1
        k=1
        for m1=1:l
            k=k/(m1-a)
        end
        k=k*factorial(l)
        k=k*(ar[l]*((n-j)^(l-a))-br[l]*((n-j+1)^(l-a)))
        s=s+k
    end
    s=s*((-1)^(i+1))/(factorial(i)*factorial(r-1-i))
    return s
end

function fj(n::Int64, T, fy)
    B = zeros(n+1)
    for i2 = 1:n+1
        B[i2] = fy(T*(i2-1)/n)
    end
    return B
end


# Generate some synthetic data
#
using DelimitedFiles,Random
using NLS_Fit

Random.seed!(1234)

n = 50
model = Gaussian_Peak()
θ = Float64[2,n/2,3]

X=Float64[1:n;]
Y=eval_y(model,X,θ) + 0.1*(rand(n) .- 0.5)

writedlm("simple_gaussian.txt",hcat(X,Y))


# Deep Learning with Flux: A 60 Minute Blitz
# =====================

# This is a quick intro to [Flux](https://github.com/FluxML/Flux.jl) loosely
# based on [PyTorch's
# tutorial](https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html).
# It introduces basic Julia programming, as well Zygote, a source-to-source
# automatic differentiation (AD) framework in Julia.
# We'll use these tools to build a very simple neural network.

# Arrays
# -------

# The starting point for all of our models is the `Array` (sometimes referred to
# as a `Tensor` in other frameworks). This is really just a list of numbers,
# which might be arranged into a shape like a square. Let's write down an array
# with three elements.

x = [1, 2, 3]

# Here's a matrix – a square array with four elements.

x = [1 2; 3 4]

# We often work with arrays of thousands of elements, and don't usually write
# them down by hand. Here's how we can create an array of 5×3 = 15 elements,
# each a random number from zero to one.

x = rand(5, 3)

# There's a few functions like this; try replacing `rand` with `ones`, `zeros`,
# or `randn` to see what they do.

# By default, Julia works stores numbers is a high-precision format called
# `Float64`. In ML we often don't need all those digits, and can ask Julia to
# work with `Float32` instead. We can even ask for more digits using `BigFloat`.

x = rand(BigFloat, 5, 3)
#-
x = rand(Float32, 5, 3)

# We can ask the array how many elements it has.

length(x)

# Or, more specifically, what size it has.

size(x)

# We sometimes want to see some elements of the array on their own.

x
#-
x[2, 3]

# This means get the second row and the third column. We can also get every row
# of the third column.

x[:, 3]

# We can add arrays, and subtract them, which adds or subtracts each element of
# the array.

x + x
#-
x - x

# Julia supports a feature called *broadcasting*, using the `.` syntax. This
# tiles small arrays (or single numbers) to fill bigger ones.

x .+ 1

# We can see Julia tile the column vector `1:5` across all rows of the larger
# array.

zeros(5,5) .+ (1:5)

# The x' syntax is used to transpose a column `1:5` into an equivalent row, and
# Julia will tile that across columns.

zeros(5,5) .+ (1:5)'

# We can use this to make a times table.

(1:5) .* (1:5)'

# Finally, and importantly for machine learning, we can conveniently do things like
# matrix multiply.

W = randn(5, 10)
x = rand(10)
W * x

# Julia's arrays are very powerful, and you can learn more about what they can
# do [here](https://docs.julialang.org/en/v1/manual/arrays/).

# ### CUDA Arrays

# CUDA functionality is provided separately by the [CUDA
# package](https://github.com/JuliaGPU/CUDA.jl). If you have a GPU and CUDA
# available, you can run `] add CUDA` in a REPL or IJulia to get it.

# Once CUDA is loaded you can move any array to the GPU with the `cu`
# function, and it supports all of the above operations with the same syntax.

## using CUDA
## x = cu(rand(5, 3))

# Automatic Differentiation
# -------------------------

# You probably learned to take derivatives in school. We start with a simple
# mathematical function like

f(x) = 3x^2 + 2x + 1

f(5)

# In simple cases it's pretty easy to work out the gradient by hand – here it's
# `6x+2`. But it's much easier to make Flux do the work for us!

using Flux: gradient

df(x) = gradient(f, x)[1]

df(5)

# You can try this with a few different inputs to make sure it's really the same
# as `6x+2`. We can even do this multiple times (but the second derivative is a
# fairly boring `6`).

ddf(x) = gradient(df, x)[1]

ddf(5)

# Flux's AD can handle any Julia code you throw at it, including loops,
# recursion and custom layers, so long as the mathematical functions you call
# are differentiable. For example, we can differentiate a Taylor approximation
# to the `sin` function.

mysin(x) = sum((-1)^k*x^(1+2k)/factorial(1+2k) for k in 0:5)

x = 0.5

mysin(x), gradient(mysin, x)
#-
sin(x), cos(x)

# You can see that the derivative we calculated is very close to `cos(x)`, as we
# expect.

# This gets more interesting when we consider functions that take *arrays* as
# inputs, rather than just a single number. For example, here's a function that
# takes a matrix and two vectors (the definition itself is arbitrary)

myloss(W, b, x) = sum(W * x .+ b)

W = randn(3, 5)
b = zeros(3)
x = rand(5)

gradient(myloss, W, b, x)

# Now we get gradients for each of the inputs `W`, `b` and `x`, which will come
# in handy when we want to train models.

# Because ML models can contain hundreds of parameters, Flux provides a slightly
# different way of writing `gradient`. We instead mark arrays with `param` to
# indicate that we want their derivatives. `W` and `b` represent the weight and
# bias respectively.

using Flux: params

W = randn(3, 5)
b = zeros(3)
x = rand(5)

y(x) = sum(W * x .+ b)

grads = gradient(()->y(x), params([W, b]))

grads[W], grads[b]

# We can now grab the gradients of `W` and `b` directly from those parameters.

# This comes in handy when working with *layers*. A layer is just a handy
# container for some parameters. For example, `Dense` does a linear transform
# for you.

using Flux

m = Dense(10, 5)

# We can easily get the parameters of any layer or model with params with
# `params`.

params(m)

# This makes it very easy to calculate the gradient for all
# parameters in a network, even if it has many parameters.
x = rand(Float32, 10)
m = Chain(Dense(10, 5, relu), Dense(5, 2), softmax)
l(x) = Flux.Losses.crossentropy(m(x), [0.5, 0.5])
grads = gradient(params(m)) do
    l(x)
end
for p in params(m)
    println(grads[p])
end

# You don't have to use layers, but they can be convenient for many simple kinds
# of models and fast iteration.

# The next step is to update our weights and perform optimisation. As you might be
# familiar, *Gradient Descent* is a simple algorithm that takes the weights and steps
# using a learning rate and the gradients. `weights = weights - learning_rate * gradient`
# (note that `Flux.Optimise.update!(x, x̄)` already updates with the negative of x̄`).
using Flux.Optimise: update!, Descent
η = 0.1
for p in params(m)
  update!(p, η * grads[p])
end

# While this is a valid way of updating our weights, it can get more complicated as the
# algorithms we use get more involved.

# Flux comes with a bunch of pre-defined optimisers and makes writing our own really simple.
# We just give it the learning rate η

opt = Descent(0.01)

# `Training` a network reduces down to iterating on a dataset multiple times, performing these
# steps in order. Just for a quick implementation, let’s train a network that learns to predict
# `0.5` for every input of 10 floats. `Flux` defines the `train!` function to do it for us.

data, labels = rand(10, 100), fill(0.5, 2, 100)
loss(x, y) = Flux.Losses.crossentropy(m(x), y)
Flux.train!(loss, params(m), [(data,labels)], opt)
# You don't have to use `train!`. In cases where arbitrary logic might be better suited,
# you could open up this training loop like so:

# ```julia
#   for d in training_set # assuming d looks like (data, labels)
#     # our super logic
#     gs = gradient(params(m)) do #m is our model
#       l = loss(d...)
#     end
#     update!(opt, params(m), gs)
#   end
# ```

# Training a Classifier
# ---------------------

# Getting a real classifier to work might help cement the workflow a bit more.
# [CIFAR10](url) is a dataset of 50k tiny training images split into 10 classes.

# We will do the following steps in order:

# * Load CIFAR10 training and test datasets
# * Define a Convolution Neural Network
# * Define a loss function
# * Train the network on the training data
# * Test the network on the test data

# Loading the Dataset

# [Metalhead.jl](https://github.com/FluxML/Metalhead.jl) is an excellent package
# that has a number of predefined and pretrained computer vision models.
# It also has a number of dataloaders that come in handy to load datasets.

using Statistics
using Flux, Flux.Optimise
using Images: channelview
using Metalhead
using Metalhead: trainimgs, valimgs
using Images.ImageCore
using Flux: onehotbatch, onecold, flatten
using Base.Iterators: partition
# using CUDA

# The image will give us an idea of what we are dealing with.
# ![title](https://pytorch.org/tutorials/_images/cifar10.png)

Metalhead.download(CIFAR10)
X = trainimgs(CIFAR10)
labels = onehotbatch([X[i].ground_truth.class for i in 1:50000],1:10)

# Let's take a look at a random image from the dataset

image(x) = x.img # handy for use later
ground_truth(x) = x.ground_truth
image.(X[rand(1:end, 10)])

# The images are simply 32 X 32 matrices of numbers in 3 channels (R,G,B). We can now
# arrange them in batches of say, 1000 and keep a validation set to track our progress.
# This process is called minibatch learning, which is a popular method of training
# large neural networks. Rather that sending the entire dataset at once, we break it
# down into smaller chunks (called minibatches) that are typically chosen at random,
# and train only on them. It is shown to help with escaping
# [saddle points](https://en.wikipedia.org/wiki/Saddle_point).

# Defining a `getarray` function would help in converting the matrices to `Float` type.

getarray(X) = float.(permutedims(channelview(X), (3, 2, 1)))
imgs = [getarray(X[i].img) for i in 1:50000]

# The first 49k images (in batches of 1000) will be our training set, and the rest is
# for validation. `partition` handily breaks down the set we give it in consecutive parts
# (1000 in this case). `cat` is a shorthand for concatenating multi-dimensional arrays along
# any dimension.

train = ([(cat(imgs[i]..., dims = 4), labels[:,i]) for i in partition(1:49000, 1000)]) |> gpu
valset = 49001:50000
valX = cat(imgs[valset]..., dims = 4) |> gpu
valY = labels[:, valset] |> gpu

# ## Defining the Classifier
# --------------------------
# Now we can define our Convolutional Neural Network (CNN).

# A convolutional neural network is one which defines a kernel and slides it across a matrix
# to create an intermediate representation to extract features from. It creates higher order
# features as it goes into deeper layers, making it suitable for images, where the structure of
# the subject is what will help us determine which class it belongs to.

m = Chain(
  Conv((5,5), 3=>16, relu),
  MaxPool((2,2)),
  Conv((5,5), 16=>8, relu),
  MaxPool((2,2)),
  flatten,
  Dense(200, 120),
  Dense(120, 84),
  Dense(84, 10),
  softmax) |> gpu

#-
# We will use a crossentropy loss and the Momentum optimiser here. Crossentropy will be a
# good option when it comes to working with multiple independent classes. Momentum smooths out
# the noisy gradients and helps towards a smooth convergence. Gradually lowering the
# learning rate along with momentum helps to maintain a bit of adaptivity in our optimisation,
# preventing us from overshooting our desired destination.
#-

using Flux: Momentum

loss(x, y) = Flux.Losses.crossentropy(m(x), y)
opt = Momentum(0.01)

# We can start writing our train loop where we will keep track of some basic accuracy
# numbers about our model. We can define an `accuracy` function for it like so.

accuracy(x, y) = mean(onecold(m(x), 1:10) .== onecold(y, 1:10))

# ## Training
# -----------

# Training is where we do a bunch of the interesting operations we defined earlier,
# and see what our net is capable of. We will loop over the dataset 10 times and
# feed the inputs to the neural network and optimise.

epochs = 10

for epoch = 1:epochs
  for d in train
    gs = gradient(params(m)) do
      l = loss(d...)
    end
    update!(opt, params(m), gs)
  end
  @show accuracy(valX, valY)
end

# Seeing our training routine unfold gives us an idea of how the network learnt the
# This is not bad for a small hand-written network, trained for a limited time.

# Training on a GPU
# -----------------

# The `gpu` functions you see sprinkled through this bit of the code tell Flux to move
# these entities to an available GPU, and subsequently train on it. No extra faffing
# about required! The same bit of code would work on any hardware with some small
# annotations like you saw here.

# ## Testing the Network
# ----------------------

# We have trained the network for 10 passes over the training dataset. But we need to
# check if the network has learnt anything at all.

# We will check this by predicting the class label that the neural network outputs, and
# checking it against the ground-truth. If the prediction is correct, we add the sample
# to the list of correct predictions. This will be done on a yet unseen section of data.

# Okay, first step. Let us perform the exact same preprocessing on this set, as we did
# on our training set.

valset = valimgs(CIFAR10)
valimg = [getarray(valset[i].img) for i in 1:10000]
labels = onehotbatch([valset[i].ground_truth.class for i in 1:10000],1:10)
test = gpu.([(cat(valimg[i]..., dims = 4), labels[:,i]) for i in partition(1:10000, 1000)])

# Next, display some of the images from the test set.

ids = rand(1:10000, 10)
image.(valset[ids])

# The outputs are energies for the 10 classes. Higher the energy for a class, the more the
# network thinks that the image is of the particular class. Every column corresponds to the
# output of one image, with the 10 floats in the column being the energies.

# Let's see how the model fared.

rand_test = getarray.(image.(valset[ids]))
rand_test = cat(rand_test..., dims = 4) |> gpu
rand_truth = ground_truth.(valset[ids])
m(rand_test)

# This looks similar to how we would expect the results to be. At this point, it's a good
# idea to see how our net actually performs on new data, that we have prepared.

accuracy(test[1]...)

# This is much better than random chance set at 10% (since we only have 10 classes), and
# not bad at all for a small hand written network like ours.

# Let's take a look at how the net performed on all the classes performed individually.

class_correct = zeros(10)
class_total = zeros(10)
for i in 1:10
  preds = m(test[i][1])
  lab = test[i][2]
  for j = 1:1000
    pred_class = findmax(preds[:, j])[2]
    actual_class = findmax(lab[:, j])[2]
    if pred_class == actual_class
      class_correct[pred_class] += 1
    end
    class_total[actual_class] += 1
  end
end

class_correct ./ class_total

# The spread seems pretty good, with certain classes performing significantly better than the others.
# Why should that be?


"""
    ischordal(g)
return true if `g` is chordal, false otherwise.
"""
function ischordal(g)
    !is_connected(g) && return false
    ne(g) == nv(g) * (nv(g) - 1) / 2 && return true # if g is complete
    ordering = lbfs(g)
    for i in 3:length(ordering)
        index_neighbor = get_closest_neighbor(g, ordering, i)
        if index_neighbor == -1
            continue
        else
            prior_current = get_prior_neighbors(g, ordering, i, [index_neighbor])
            prior_closest = get_prior_neighbors(g, ordering, index_neighbor)
            if !issubset(prior_current, prior_closest)
                return false
            end
        end
    end
    return true
end


"""
    DifferentialOperator(x̄, bc::Tuple{BoundaryCondition, BoundaryCondition}, method::DifferenceMethod)

Returns a discretized differential operator of `length(x̄)` by `length(x̄)` matrix
under mixed boundary conditions from `bc` using finite difference method specified by `method`.

# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> DifferentialOperator(x̄, (Reflecting(), Reflecting()), BackwardFirstDifference())
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
  0.0   0.0    ⋅    ⋅
 -1.0   1.0   0.0   ⋅
   ⋅   -1.0   1.0  0.0
   ⋅     ⋅   -1.0  1.0

julia> DifferentialOperator(x̄, (Reflecting(), Reflecting()), ForwardFirstDifference())
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 -1.0   1.0    ⋅    ⋅
  0.0  -1.0   1.0   ⋅
   ⋅    0.0  -1.0  1.0
   ⋅     ⋅    0.0  0.0

julia> DifferentialOperator(x̄, (Reflecting(), Reflecting()), CentralSecondDifference())
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 -1.0   1.0    ⋅     ⋅
  1.0  -2.0   1.0    ⋅
   ⋅    1.0  -2.0   1.0
   ⋅     ⋅    1.0  -1.0

julia> x̄ = 0:5
0:5

julia> DifferentialOperator(x̄, (Mixed(ξ = 1.0), Mixed(ξ = 1.0)), BackwardFirstDifference())
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 -1.0   0.0    ⋅    ⋅
 -1.0   1.0   0.0   ⋅
   ⋅   -1.0   1.0  0.0
   ⋅     ⋅   -1.0  1.0

julia> DifferentialOperator(x̄, (Mixed(ξ = 1.0), Mixed(ξ = 1.0)), ForwardFirstDifference())
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 -1.0   1.0    ⋅     ⋅
  0.0  -1.0   1.0    ⋅
   ⋅    0.0  -1.0   1.0
   ⋅     ⋅    0.0  -1.0

julia> DifferentialOperator(x̄, (Mixed(ξ = 1.0), Mixed(ξ = 1.0)), CentralSecondDifference())
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 0.0   1.0    ⋅     ⋅
 1.0  -2.0   1.0    ⋅
  ⋅    1.0  -2.0   1.0
  ⋅     ⋅    1.0  -2.0
```
"""
function DifferentialOperator(x̄, bc::Tuple{BoundaryCondition, BoundaryCondition}, method::BackwardFirstDifference)
    # reflecting bcs are special cases of mixed bcs with ξ = 0
    if (typeof(bc[1]) <: Reflecting)
        return DifferentialOperator(x̄, (Mixed(ξ = 0), bc[2]), method)
    end
    if (typeof(bc[2]) <: Reflecting)
        return DifferentialOperator(x̄, (bc[1], Mixed(ξ = 0)), method)
    end

    # setup for operator
    M = length(x̄) - 2
    d = diff(x̄)
    Δ₋⁻¹ = 1 ./ d[1:end-1] # (1 ./ Δ₋)
    T = eltype(Δ₋⁻¹)

    # construct the operator
    L = Tridiagonal(-Δ₋⁻¹[2:M], Δ₋⁻¹, zeros(T, M-1))

    # setup for boundary conditions
    Δ_1m = x̄[2] - x̄[1]

    # apply boundary conditions
    # (under homogeneous absorbing bc on lb, the first column in invariant)
    if !(typeof(bc[1]) <: Absorbing)
        ξ_lb = bc[1].ξ
        L[1,1] += (bc[1].direction == :backward) ? (-1/Δ_1m - ξ_lb) : 1/(-1+ξ_lb*Δ_1m)/Δ_1m
    end

    return L
end

function DifferentialOperator(x̄, bc::Tuple{BoundaryCondition, BoundaryCondition}, method::ForwardFirstDifference)
    # reflecting bcs are special cases of mixed bcs with ξ = 0
    if (typeof(bc[1]) <: Reflecting)
        return DifferentialOperator(x̄, (Mixed(ξ = 0), bc[2]), method)
    end
    if (typeof(bc[2]) <: Reflecting)
        return DifferentialOperator(x̄, (bc[1], Mixed(ξ = 0)), method)
    end

    # setup for operator
    M = length(x̄) - 2
    d = diff(x̄)
    Δ₊⁻¹ = 1 ./ d[2:end] # (1 ./ Δ₊), extracting elements on the interior
    T = eltype(Δ₊⁻¹)

    # construct the operator
    L = Tridiagonal(zeros(T, M-1), -Δ₊⁻¹, Δ₊⁻¹[1:M-1])

    # setup for boundary conditions
    Δ_Mp = x̄[end] - x̄[end-1]

    # apply boundary conditions
    # (under homogeneous absorbing bc on ub, the last column in invariant)
    if !(typeof(bc[2]) <: Absorbing)
        ξ_ub = bc[2].ξ
        L[end,end] += (bc[2].direction == :forward) ? (1/Δ_Mp - ξ_ub) : 1/(1+ξ_ub*Δ_Mp)/Δ_Mp
    end

    return L
end

function DifferentialOperator(x̄, bc::Tuple{BoundaryCondition, BoundaryCondition}, method::CentralSecondDifference)
    # reflecting bcs are special cases of mixed bcs with ξ = 0
    if (typeof(bc[1]) <: Reflecting)
        return DifferentialOperator(x̄, (Mixed(ξ = 0), bc[2]), method)
    end
    if (typeof(bc[2]) <: Reflecting)
        return DifferentialOperator(x̄, (bc[1], Mixed(ξ = 0)), method)
    end

    # setup for operators
    M = length(x̄) - 2
    d = diff(x̄)
    Δ₋⁻¹ = 1 ./ d[1:end-1] # 1 ./ Δ₋
    Δ₊⁻¹ = 1 ./ d[2:end] # 1 ./ Δ₊
    Δ⁻¹ = 1 ./ (d[1:end-1] + d[2:end]) # 1 ./ (Δ₋ + Δ₊)

    # construct the operator
    L = 2*Tridiagonal((Δ⁻¹.*Δ₋⁻¹)[2:M], -Δ₋⁻¹ .* Δ₊⁻¹, (Δ⁻¹.*Δ₊⁻¹)[1:M-1])

    # setup for boundary conditions
    Δ_1p = x̄[3] - x̄[2]
    Δ_1m = x̄[2] - x̄[1]
    Δ_Mp = x̄[end] - x̄[end-1]
    Δ_Mm = x̄[end-1] - x̄[end-2]

    # apply boundary conditions
    # (under homogeneous absorbing bc on lb, the first column in invariant)
    if !(typeof(bc[1]) <: Absorbing)
        ξ_lb = bc[1].ξ
        Ξ_1p = L[1,1] - 2/((-1+ξ_lb*Δ_1m)*(Δ_1p+Δ_1m)*(Δ_1m))
        Ξ_1m = 2*(-1/(Δ_1p*Δ_1m) + (1+ξ_lb*Δ_1m)/(Δ_1p+Δ_1m)/Δ_1m)
        L[1,1] = (bc[1].direction == :backward) ? Ξ_1m : Ξ_1p
    end
    # (under homogeneous absorbing bc on ub, the last column in invariant)
    if !(typeof(bc[2]) <: Absorbing)
        ξ_ub = bc[2].ξ
        Ξ_Mm = L[end,end] + 2/((1+ξ_ub*Δ_Mp)*(Δ_Mp+Δ_Mm)*(Δ_Mp))
        Ξ_Mp = 2*(-1/(Δ_Mp*Δ_Mm) - (-1+ξ_ub*Δ_Mp)/(Δ_Mp+Δ_Mm)/Δ_Mp)
        L[end,end] = (bc[2].direction == :forward) ? Ξ_Mp : Ξ_Mm
    end
    return L
end

# Convenience calls
"""
    L₁₋bc(x̄, bc::Tuple{BoundaryCondition, BoundaryCondition})

Returns a discretized first-order differential operator of `length(x̄)` by `length(x̄)` matrix
using backward difference under boundary conditions specified by `bc`.

The first element of `bc` is applied to the lower bound, and second element of `bc` to the upper.

# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> L₁₋bc(x̄, (Reflecting(), Reflecting()))
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
  0.0   0.0    ⋅    ⋅
 -1.0   1.0   0.0   ⋅
   ⋅   -1.0   1.0  0.0
   ⋅     ⋅   -1.0  1.0
```
"""
L₁₋bc(x̄, bc) = DifferentialOperator(x̄, bc, BackwardFirstDifference())

"""
    L₁₊bc(x̄, bc::Tuple{BoundaryCondition, BoundaryCondition})

Returns a discretized first-order differential operator of `length(x̄)` by `length(x̄)` matrix
using forward difference under boundary conditions specified by `bc`.

The first element of `bc` is applied to the lower bound, and second element of `bc` to the upper.

# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> L₁₊bc(x̄, (Reflecting(), Reflecting()))
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 -1.0   1.0    ⋅    ⋅
  0.0  -1.0   1.0   ⋅
   ⋅    0.0  -1.0  1.0
   ⋅     ⋅    0.0  0.0
```
"""
L₁₊bc(x̄, bc) = DifferentialOperator(x̄, bc, ForwardFirstDifference())

"""
    L₂bc(x̄, bc::Tuple{BoundaryCondition, BoundaryCondition})

Returns a discretized second-order differential operator of `length(x̄)` by `length(x̄)` matrix
using central difference under boundary conditions specified by `bc`.

The first element of `bc` is applied to the lower bound, and second element of `bc` to the upper.
# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> L₂bc(x̄, (Reflecting(), Reflecting()))
4×4 LinearAlgebra.Tridiagonal{Float64,Array{Float64,1}}:
 -1.0   1.0    ⋅     ⋅
  1.0  -2.0   1.0    ⋅
   ⋅    1.0  -2.0   1.0
   ⋅     ⋅    1.0  -1.0
```
"""
L₂bc(x̄, bc) = DifferentialOperator(x̄, bc, CentralSecondDifference())

"""
    L₁₋(x̄)

Returns a discretized first-order differential operator of `length(x̄)` by `length(x̄) + 2` matrix
using backward difference under no boundary condition.

The first and last columns are applied to the ghost nodes just before `x̄[1]` and `x̄[end]` respectively.

# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 1:3
1:3

julia> Array(L₁₋(x̄))
1×3 Array{Float64,2}:
 -1.0  1.0  0.0
```
"""
L₁₋(x̄) = ExtensionDifferentialOperator(x̄, BackwardFirstDifference())

"""
    L₁₊(x̄)

Returns a discretized first-order differential operator of `length(x̄)` by `length(x̄) + 2` matrix using
forward difference under no boundary condition.

The first and last columns are applied to the ghost nodes just before `x̄[1]` and `x̄[end]` respectively.

# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> Array(L₁₊(x̄))
4×6 Array{Float64,2}:
 0.0  -1.0   1.0   0.0   0.0  0.0
 0.0   0.0  -1.0   1.0   0.0  0.0
 0.0   0.0   0.0  -1.0   1.0  0.0
 0.0   0.0   0.0   0.0  -1.0  1.0
```
"""
L₁₊(x̄) = ExtensionDifferentialOperator(x̄, ForwardFirstDifference())

"""
    L₂(x̄)

Returns a discretized second-order differential operator of `length(x̄)` by `length(x̄) + 2` matrix
using central difference under no boundary condition.

The first and last columns are applied to the ghost nodes just before `x̄[1]` and `x̄[end]` respectively.

# Examples
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> Array(L₂(x̄))
4×6 Array{Float64,2}:
 1.0  -2.0   1.0   0.0   0.0  0.0
 0.0   1.0  -2.0   1.0   0.0  0.0
 0.0   0.0   1.0  -2.0   1.0  0.0
 0.0   0.0   0.0   1.0  -2.0  1.0
```
"""
L₂(x̄)  = ExtensionDifferentialOperator(x̄, CentralSecondDifference())

"""
    interiornodes(x̄)

Returns an interior grid of length `length(x̄)-2` given extended grid `x̄`.
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> interiornodes(x̄)
1:4

julia> x̄ = [1.0; 1.5; 1.7]
3-element Array{Float64,1}:
 1.0
 1.5
 1.7

julia> interiornodes(x̄)
1-element Array{Float64,1}:
 1.5
```
"""
interiornodes(x̄) = x̄[2:end-1]

"""
    interiornodes(x̄, bc)

Returns an interior grid corresponding to the boundary condition `bc` given extended grid `x̄`.
```jldoctest; setup = :(using SimpleDifferentialOperators)
julia> x̄ = 0:5
0:5

julia> interiornodes(x̄, (Reflecting(), Reflecting()))
1:4

julia> x̄ = [1.0; 1.5; 1.7]
3-element Array{Float64,1}:
 1.0
 1.5
 1.7

julia> interiornodes(x̄, (Mixed(ξ = 1.0), Mixed(ξ = 1.0)))
1-element Array{Float64,1}:
 1.5
```
"""
interiornodes(x̄, bc) = interiornodes(x̄)


export wrand, wrandn, wrandnp, wsobol, sobol, soboln, sobolnp, wgrid, xgrid, near

##### random

"""
    wrand(d, k) = Weighted(rand(d, k))
    wrand(T, d, k)

Uniformly distributed vectors in `[0,1]^d`, as columns of a `Weighted{Matrix}`
which knows to `clamp` them to this box. `T = Float64` by default.

Keyword `weights=true` gives weights ∝ `1 .+ rand(k)` rather than constant.

    wrandn(d, k) = scale .* Weighted(randn(d, k))

Normally distributed `d`-vectors, of mean zero and std. dev. `scale=1` by default.
Keyword `max=10` clamps absolute values to be less than this.

    wrandnp(d, k)

Absolute value of normally distributed...
"""
wrand(d::Int, k::Int=1; kw...) = wrand(Float64,d,k; kw...)
wrand(T::Type, d::Int, k::Int=1; weights=true) = Weighted(rand(T,d,k), weights ? 1 .+ rand(T,k) : ones(T,k) ,0,1)

@doc @doc(wrand)
wrandn(d::Int, k::Int=1; kw...) = wrandn(Float64,d,k; kw...)
function wrandn(T::Type, d::Int, k::Int=1; scale=one(T), weights=true, max=typemax(T))
    arr = clamp.(scale .* randn(T,d,k), -max, max)
    opt = max==Inf ? WeightOpt() : WeightOpt(clamp=true, lo=-max, hi=max)
    Weighted(arr, normalise(weights ? 1 .+ rand(T,k) : ones(T,k)), opt)
end

@doc @doc(wrand)
wrandnp(d::Int, k::Int=1; kw...) = wrandnp(Float64,d,k; kw...)
function wrandnp(T::Type, d::Int, k::Int=1; scale=one(T), weights=true, max=typemax(T))
    arr = clamp.(abs.(scale .* randn(T,d,k)), 0, max)
    opt = WeightOpt(clamp=true, lo=0, hi=max)
    Weighted(arr, normalise(weights ? 1 .+ rand(T,k) : ones(T,k)), opt)
end

for f in [:wrand, :wrandn, :wrandnp]
    @eval function $f(::Union{typeof(big), Type{BigFloat}}, d::Int, k::Int=1; kw...)
        r64 = $f(d,k;kw...)
        Weighted(big.(r64.array), big.(r64.weights), r64.opt) |> normalise!
    end
end

##### sub-random

import Sobol

"""
    sobol(d, k)

First `k` entries from the `d`-dimensional Sobol sequence, as columns of a `Weighted{Matrix}`.
"""
function sobol(d::Int, k::Int=1)
    s = Sobol.SobolSeq(d)
    cols = reduce(hcat, [Sobol.next!(s) for i=1:k])
    Weighted(cols, ones(k), 0,1)
end

using SpecialFunctions: erfinv

"""
    soboln(d, k)

Normally distributed `d`-vectors, of mean zero and std. dev. 1, generated from Sobol sequence,
as columns of a `Weighted{Matrix}`.
"""
function soboln(d::Int, k::Int=1; scale=1)
    boxed = sobol(d,k)
    Weighted(scale .* erfinv.( -1 .+ 2 .* array(boxed)), boxed.weights)
end

function sobolnp(d::Int, k::Int=1; scale=π)
    boxed = sobol(d,k)
    Weighted(scale .* erfinv.(boxed.array), boxed.weights, 0, Inf)
end

sobol(T::Type, d::Int, k::Int=1) = Weighted(map(T, sobol(d,k).array), ones(T,k), 0,1)
soboln(T::Type, d::Int, k::Int=1) = Weighted(map(T, soboln(d,k).array), ones(T,k))
sobolnp(T::Type, d::Int, k::Int=1) = Weighted(map(T, sobolnp(d,k).array), ones(T,k))

##### ordered!

wgrid(d::Int, range::AbstractRange) = Weighted(xgrid(d,range), extrema(range)...)

##### not Weighted

"""
    xgrid(d, 0:0.1:5)

Gives the matrix whose colums are `d`-vectors, forming a grid of the given range in all dimensions.
"""
xgrid(d::Int, n::Int) = xgrid(d, 0:n)

function (xgrid(d::Int, range::AbstractRange{T}; big=false)::Matrix{T}) where T
    d==1 && return Matrix(collect(range)')

    n = length(range)
    !big && n^d > 4_000_000 && @error "xgrid is happiest with n^d = < 4×10^6, you have $n^$d... keyword big=true will disable this check"

    intcols = zeros(Int,d,n^d)
    @inbounds for i=1:n^d
        @views digits!(intcols[:,i], i-1; base=n)
    end

    range[1] .+ step(range) .* intcols
end

function near(x::Matrix, y, dist::Real; verbose=false)
    yesno = minimum( pairwise2(x,array(y)) ,dims=2) .<= (dist^2)
    sum(yesno)==0 && begin @warn("no nearby points, returning complete matrix"); return x end
    verbose && @info string("kept ",sum(yesno)," / ",size(x,2)," columns: ",round(100*sum(yesno)/size(x,2),2)," percent")
    x[:,vec(yesno)]
end

using Distances

"""
    pairwise2(x, y=x) = Distances.pairwise(SqEuclidean(), x, y)

Resulting `mat[i,j]` is distance sqared from `x[:,i]` to `y[:,j]`. Implementation varies.
"""
pairwise2(x::Matrix, y::Matrix) = pairwise(SqEuclidean(), x, y, dims=2)
pairwise2(x::Matrix) = pairwise(SqEuclidean(), x)

pairwise2(x::AbsMat, y::AbsMat) = diag(x'*x) .+ diag(y'*y)' .- 2x'*y
function pairwise2(x::AbsMat) # = diag(x'*x) .+ diag(x'*x)' .- 2x'*x
    mat = x'*x
    vec = diag(mat)
    vec .+ vec' .- 2 .* mat
end


# If the numbers 1 to 5 are written out in words: one, two, three, four, five,
# then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.
#
# If all the numbers from 1 to 1000 (one thousand) inclusive were written out
# in words, how many letters would be used?
#
# NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and
# forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20
# letters. The use of "and" when writing out numbers is in compliance with
# British usage.

using ProjectEulerSolutions

# Convert integer to string, remove hyphens and spaces, then add up lengths.
function p017solution(n::Integer=2)::Integer

    total_length = 0
    for i = 1:n
        total_length += length(replace(integer_to_string(i), r"[- ]" => s""))
    end

    return total_length
end

p017 = Problems.Problem(p017solution)

Problems.benchmark(p017, 1_000)


# day 03 ---------------------------------------------
# transform vector of string in matrix of bits
vecofvec = map(readlines(joinpath(@__DIR__, "inputs", "input_03.txt"))) do line
    parse.(Bool, split(line, ""))
end
data = permutedims(hcat(vecofvec...))

# 03a ------------------------------------------------
function sol_03a(data)

   # get the most repeated bit, for each collumn, as a binary string
   gamma_rate   = "0b"
   epsilon_rate = "0b"
   for col in eachcol(data)
      if mean(col) ≥ 0.5
         gamma_rate   *= "1"
         epsilon_rate *= "0"
      else
         gamma_rate   *= "0"
         epsilon_rate *= "1"
      end
   end

   # convert binary string to Int
   gamma_rate   = parse(Int, gamma_rate)
   epsilon_rate = parse(Int, epsilon_rate)

   return gamma_rate * epsilon_rate
end

sol_03a(data)

# 03b ------------------------------------------------

function filtermatrix(data, idx; reverse=false)
   if mean(idx) ≥ 0.5
      if reverse
         return data[(.!idx), :]
      else
         return data[idx, :]
      end
   else
      if reverse
         return data[idx, :]
      else
         return data[(.!idx), :]
      end
   end
end

function getdiagnostic(data; order=:mostcommon)

   order == :mostcommon  ? (rev = false) :
   order == :leastcommon ? (rev = true)  :
   throw(ArgumentError("wrong argument. Use :mostcommon or :leastcommon"))

   # apply the rule
   idx = data[:, 1]
   dt = filtermatrix(data, idx, reverse=rev)
   for j in 2:size(data, 2)
      idx = dt[:, j]
      dt = filtermatrix(dt, idx, reverse=rev)
      size(dt, 1) == 1 && break
   end

   # get the most repeated bit, for each collumn, as a binary string
   bin = "0b"
   for i in dt
      i == true ? (bin *= "1") : (bin *= "0")
   end

   # convert binary string to Int
   return parse(Int, bin)
end

function sol_03b(data)
   oxygen_rate = getdiagnostic(data, order=:mostcommon)
   co2_rate    = getdiagnostic(data, order=:leastcommon)
   return oxygen_rate * co2_rate
end

sol_03b(data)


module TensorLab
	import MATLAB: mxarray
	using MATLAB
	include("matlabTypes.jl")
	export Options, MatFcn

	# Create a table of functions and the number of outputs they return.
	# TODO: These functions have a variable number of outputs
	# - cpd (probably variants as well) (1 or 2)
	# - similar mechanism for lmlra
	# - mlsvd and variants (2 or 3)
	functions = [
		(:cpd, 1),
		(:cpd_als, 1),
		(:cpd_els, 1),
		(:cpd_gevd, 1),
		(:cpd_minf, 1),
		(:cpd_rbs, 1),
		(:cpd_rnd, 1),
		(:cpd3_sd, 1),
		(:cpd3_sgsd, 1),
		(:cpderr, 1),
		(:cpdgen, 1),
		(:cpdres, 1),
		(:fmt, 1),
		(:frob, 1),
		(:frobcpdres, 1),
		(:frobmlrares, 1),
		(:fromll1res, 1),
		(:ful, 1),
		(:hankelize, 1),
		(:isvalidtensor, 1),
		(:kr, 1),
		(:kron, 1),
		(:ll1, 1),
		(:ll1_gevd, 1),
		(:ll1_minf, 1),
		(:ll1_rnd, 1),
		(:ll1convert, 1),
		(:ll1gen, 1),
		(:ll1res, 1),
		(:lmlra, 2),
		(:lmlra3_dgn, 2),
		(:lmlra3_rtr, 2),
		(:lmlra_aca, 2),
		(:lmlra_hooi, 2),
		(:lmlra_minf, 2),
		(:lmlra_nls, 2),
		(:lmlra_rnd, 2),
		(:lmlraerr, 1),
		(:lmlragen, 1),
		(:lmlrares, 1),
		(:mat2tens, 1),
		(:mlrank, 1),
		(:mlrankest, 0),
		(:mlsvd, 3),
		(:mlsvd_rsi, 3),
		(:mlsvds, 3),
		(:rankest, 0),
		(:surf3,0),
		(:slice3,0),
		(:tens2mat, 1),
		(:tmprod, 1),
		(:visualize, 0),
		(:voxel3,0),
	]

	for (fcn, numout) in functions
		@eval $fcn(args...) = mxcall(Symbol($fcn), $numout, args...)
		@eval export $fcn
	end
end


# Example
#function ebreg_example(n=100, p=500, r=0.25, beta=0.6 * (1:5), M=2000)

n = 100
p = 500
r = 0.25
beta = 0.5 * (1:5)
numSamples = 2000
burnIn = 0.25
sampleCoefs = false

sigma2 = 1
alpha = 1
gamma = 0
logF = (x) -> dcomplex(x, n, p, 0.05, 1)

# Generate problem
s0 = length(beta)
beta0 = [beta, zeros(p-s0)]
S0 = float(beta0 .!= 0)
sig2_hat = nothing
X = randn(n, p)
#if(r != 0)
#  R = (1 - r) * diag(p) + array(r, c(p, p))
#  e = eigen(R)
#  sqR = e$vectors %*% diag(sqrt(e$values)) %*% t(e$vectors)
#  X = X * sqR
#end
y = X * beta0 + sqrt(sigma2) * randn(n)

# Call
@time models, sampledmodels = empirical_bayes_linreg(X, y; sigma2 = sigma2, alpha = alpha, gamma = gamma,
  numSamples = int(1e4), logF = logF)

smodels = models[unique(sampledmodels)]

lengths = map((e) -> length(e[2]), models)
edges, counts = hist(float(lengths), 20)

logposteriors = map((e) -> e[6], models)
highestlogposterior = maximum(logposteriors)
bestmodelindex = find((m) -> m[6] == highestlogposterior, models)[1]
bestmodelentry = models[bestmodelindex]
bestmodel = bestmodelentry[3]

sses = map((e) -> e[5], models)
lowestsse = minimum(sses)
lowestssemodelindex = find((m) -> m[5] == lowestsse, models)[1]
lowestssemodelentry = models[lowestssemodelindex]

println("Number of sampled models: ", length(smodels))

println("Model with highest posterior: ", bestmodelentry)
println("\nModel with lowest sse: ", lowestssemodelentry)


module ThreeBody

    ######
    using TaylorSeries, TaylorIntegration

    export M_1, M_2, μ, mu,
        L_1,L_2,L_3,L_x4,L_y4,L_x5,L_y5,
        L1,L2,L3,Lx4,Lx5,
        C_L1,C_L2,C_L3,C_L4,C_L5,
        mass_moon, mass_sun, mass_earth,
        _V_pseudo, _CR3BP_ode!, _C_Jacobi, _vy,
        collision_approach, JT_accuracy

    ######

    ### Earth, Moon, Sun masses ###
    const mass_moon  = 7.347673e22 #kg
    const mass_earth = 5.9722e24 #kg
    const mass_sun = 1.9855e30 #kg
    # mass_satelite ≈ 1e3 kg
    ### Earth, Moon, Sun distances ###
    const radius_earth_moon = 3.85e5 #km
    const radius_earth_sun  = 1.496e8 #km

    ### Mass parameters (with distance normalized to 1)
    M_1 = mass_earth
    M_2 = mass_moon
    mu(M_1,M_2) = M_2/(M_1 + M_2)
    μ = mu(M_1,M_2)

    ### Lagrangian Points ###
    # L1, L2, L3 can be obtained numerically more precisely
    """
    \$ 1 - ( μ /3 )^{1/3} \$
    """
    L1(μ)  =  1 - (μ/3)^(1/3)
    """
    \$ 1 + ( μ /3 )^{1/3} \$
    """
    L2(μ)  =  1 + (μ/3)^(1/3)
    """
    \$ - 1 - 5μ /12  \$
    """
    L3(μ)  =  -(1 + (5μ/12))
    """
    \$  (1 - 2μ)/2  \$
    """
    Lx4(μ) = 1/2 * (1 - 2μ)
    """
    \$  (1 - 2μ)/2  \$
    """
    Lx5(μ) = 1/2 * (1 - 2μ)

    L_1  = L1(μ)
    L_2  = L2(μ)
    L_3  = L3(μ)
    L_x4 = Lx4(μ)
    L_x5 = Lx5(μ)
    L_y4 = √3/2
    L_y5 = -√3/2

    ####  Potentials ###
    V_G(r, μ) = -(1-μ)/norm(r-[-μ,0.0]) - μ/norm(r-[1-μ,0.0])
    V_Ω(r) = -norm(r)^2/2

    """
    Returns the no coriolis potential for the CR3BP at value `r`.
    """
    _V_pseudo(r, μ) = V_G(r,μ) + V_Ω(r)

    # V_pseudo(r) = _V_pseudo(r,μ)

    ### Jacobi Constants ###
    C_L1 = _V_pseudo([L_1,0.0],μ)
    C_L2 = _V_pseudo([L_2,0.0],μ)
    C_L3 = _V_pseudo([L_3,0.0],μ)
    C_L4 = _V_pseudo([L_x4,L_y4],μ)
    C_L5 = _V_pseudo([L_x5,L_y5],μ);

    ### Eqs of motion ###
    """
    Equations of motion for CR3BP ready for integrating via `TaylorIntegration`
    """
    function _CR3BP_ode!(t, x, dx, μ)

        β = 1.0-μ
        r_13_cubed = ( (x[1]+μ)^2 + x[2]^2 )^(3/2)
        r_23_cubed = ( (x[1]-β)^2 + x[2]^2 )^(3/2)

        dx[1] = x[3]
        dx[2] = x[4]
        dx[3] = -( β*(x[1]+μ)/r_13_cubed + μ*(x[1]-β)/r_23_cubed ) + x[1] + 2x[4]
        dx[4] = -(β/r_13_cubed + μ/r_23_cubed - 1)*x[2] - 2x[3]

        nothing
    end

    # CR3BP_ode!(t, x, dx) = _CR3BP_ode!(t, x, dx, μ)

    """
    Distance between two 2-dimn solutions.
    """
    function JT_accuracy(ϕ,ϕ_JT)
        dims = size(ϕ)[2]
        diffs = zero(ϕ[:,1])
        for dim in 1:dims
            diffs += (ϕ[:,dim] - ϕ_JT[:,dim]).^2
        end
        return sqrt.(diffs)
    end

    """
    Returns the indexes for which two trajectories differ in less than `d_min`.
    """
    function collision_approach(ϕ1, ϕ2; d_min=0)
        d_ϕ1ϕ2 = JT_accuracy(ϕ1[:,1:2], ϕ2[:,1:2])
        if d_min == 0
            d_min = minimum(d_ϕ1ϕ2)
        end
        #approached_distance = Vector{eltype(d_A1_A2)}()
        #approached_time = Vector{eltype(tv)}()
        approached_index = Vector{Int64}()
        for i in eachindex(d_ϕ1ϕ2)
            if d_ϕ1ϕ2[i] <= d_min
                #push!(approached_distance, d_ϕ1ϕ2[i])
                #push!(approached_time, tv[i])
                push!(approached_index, i)
            end
        end

        return approached_index
    end

    ### Jacobi Constant ###
    """
    Computes Jacobi Energy \$ V(r) + v^2/2 \$.
    """
    _C_Jacobi(r,v,μ) = _V_pseudo(r,μ) + (v⋅v)/2.0

    ### implicit y-velocity ###
    """
    Obtains y-velocity for initial conditions to be at energy level `E`
    """
    _vy(r, vx, μ; E=C_L1) = √( 2*( E - _V_pseudo(r,μ) ) - vx^2 )

    println("M_1: $M_1 , M_2: $M_2, μ: $μ")
    println("L_1: $(round(L_1,3)), L_2: $(round(L_2,3)), L_3: $(round(L_1,3)), L_4x: $(round(L_x4,3)), L_4y: $(round(L_y4,3))")
    println("C_L1: $(round(C_L1,3))")
end


"""
    get_W_R(f::TopHatFilter, domain, M, N; kwargs...)

Construct `W` and `R` using a simplified counter rule.

    get_W_R(f, domain, M, N; kwargs...)

Fallback function for the general case. Calls `filter_matrix` and
`reconstruction_matrix` separately.
"""
function get_W_R end

function get_W_R(f, domain, M, N; kwargs...)
    W = filter_matrix(f, domain, M, N; kwargs...)
    R = reconstruction_matrix(f, domain, M, N; kwargs...)

    W, R
end

function get_W_R(f::TopHatFilter, domain::AbstractIntervalDomain, M, N; kwargs...)
    h = f.width
    L = domain.right - domain.left
    ℒ = domain isa PeriodicIntervalDomain ? [-L, 0, L] : [0]
    x = discretize(domain, M)
    ξ = discretize(domain, N)
    W = spzeros(eltype(x), length(x), length(ξ))
    NN = zeros(Int, M)
    for m = 1:length(x)
        xₘ = x[m]
        # nₘ = @. (-h₀ < (ξ - x[m]) ≤ h₀)
        nₘ = mapreduce(ℓ -> -h(xₘ) .< (ξ .+ ℓ .- xₘ) .≤ h(xₘ), .|, ℒ)
        Nₘ = sum(nₘ)
        W[m, nₘ] .= 1 // Nₘ
        NN[m] = Nₘ
    end

    hx = h.(x)
    R = spzeros(eltype(x), length(ξ), length(x))
    for n = 1:length(ξ)
        ξₙ = ξ[n]
        mₙ = mapreduce(ℓ -> -hx .< (ξₙ .+ ℓ .- x) .≤ hx, .|, ℒ)
        Nₘ = NN[mₙ]
        # R[n, mₙ] .= 1 .// Nₘ / sum(1 .// Nₘ)
        R[n, mₙ] .= 1 ./ Nₘ / sum(1 ./ Nₘ)
    end

    W, R
end


"""
Base type of fractional differentiation algorithms, in FractionalCalculus.jl, all of the fractional derivative algorithms belong to ```FracDiffAlg```
"""
abstract type FracDiffAlg end

const FunctionAndNumber = Union{Function, Number}

include("Caputo.jl")
include("GL.jl")
include("Hadamard.jl")
include("Riesz.jl")
include("RL.jl")

include("SymbolicDiff.jl")

"""
    fracdiff(f, α, point, h, FracDiffAlg())

```fracdiff``` is the general function for computing fractional derivative.

### Example

```julia-repl
julia> fracdiff(x->x^2, 0.5, 1, 0.001, RLDiff_Approx())
1.5044908143658473
```

There are many algorithms can be used to computing fractional derivative, please refer to our manual for more details: http://scifracx.org/FractionalCalculus.jl/dev/Derivative/derivativeapi/.
"""
function fracdiff(f, α, point, h, ::FracDiffAlg)

end

function checkfracdiff(f, α, point, h)
    point > 0 ? nothing : DomainError("Only positive points")
end

## Macros for convenient computing.
"""
    @fracdiff(f, α, point)

Return the α-order derivative of **f** at specific point.

```julia-repl
julia> @fracdiff(x->x, 0.5, 1)
1.1283791670955188
```
"""
macro fracdiff(f, α, point)
    return :(fracdiff($f, $α, $point, 0.0001, RLDiffApprox()))
end

"""
    @semifracdiff(f, point)

Return the semi-derivative of **f** at spedific point.

```julia-repl
julia> @semifracdiff(x->x, 1)
1.1283791670955188
```
"""
macro semifracdiff(f::Union{Number, Function}, point)
    return :(fracdiff($f, 0.5, $point, 0.0001, RLDiffApprox()))
end


immutable OptimGARCH <: MathProgBase.SolverInterface.AbstractNLPEvaluator
    x::Array{Float64, 1}
    p::Int
    q::Int
    objval::Array{Float64,1}
    status::Array{Symbol, 1}
    solver::Array{MathProgBase.SolverInterface.AbstractMathProgSolver, 1}
end

OptimGARCH(p, q) = OptimGARCH(Array(Float64, 1), p, q, [0.0], [:Unsolved],
                              [FinMetricsDefaultSolver()])

immutable GARCH <: ContinuousUnivariateDistribution
    ω::Array{Float64, 1}
    α::Array{Float64, 1}
    β::Array{Float64, 1}
    x0::Array{Float64, 1}
    σ²₀::Array{Float64, 1}
    o::OptimGARCH
    dist::Function
    fitted::Array{Bool, 1}
    p::Int
    q::Int
end

function GARCH(p, q)
    ## Init is the vector
    ## init = [1 eps_{t-1} eps_{t-2} ... eps_{}]
    GARCH(Array(Float64, 1),
          Array(Float64, p),
          Array(Float64, q),
          zeros(p),
          zeros(q),
          OptimGARCH(p, q),
          s -> Normal(0,s),
          [false],
          p, q)
end

size(g::GARCH) = (g.p, g.q)

function update_parms!(g::GARCH, theta)
    ## Writes the parameters in theta into GARCH
    ## [ω; α; β]
    p, q = size(g)
    @assert length(theta) == q + p + 1 "length of theta inconsistent"
    copy!(g.ω, theta[1])
    copy!(g.α, theta[2:2+p-1])
    copy!(g.β, theta[2+p:end])
end

function getgarchparms(g)
    (g.p, g.q, g.σ²₀[1], g.ω[1], g.α, g.β)
end

function simulate(g::GARCH, n; d::Function = g.dist, burnin::Int = 0)
    (p, q, σ²₀, ω, α, β) = getgarchparms(g)
    maxpq = max(p, q)

    σ² = zeros(Float64, n + burnin)
    x  = Array(Float64, n + burnin)

    sigma2 = sum(α) + sum(β)

    @assert sigma2 <1 "GARCH does not have finite variance"

    sigma2 = ω/(1-sigma2)

    x[1:maxpq]  = rand(d(√sigma2), maxpq)
    σ²[1:maxpq] = sigma2

    @inbounds for j = maxpq+1:n + burnin
            for i = 1:q
                σ²[j] = σ²[j] + β[i]*σ²[j-i]
            end
            for i = 1:p
                σ²[j] = σ²[j] + α[i]*x[j-i]^2
            end
            σ²[j] += ω
            x[j] = Distributions.rand(d(√σ²[j]))
        end
        x[burnin+1:end]
end

function calculateVolatilityProcess(ϵ, omega, a, b)
    p = length(a)
    q = length(b)
    vare = var(ϵ)
    σ² = zeros(eltype(a), length(ϵ))
    @inbounds for j = 1:length(ϵ)
        for i = 1:q
            if j-i > 0
                σ²[j] = σ²[j] + b[i]*σ²[j - i]
            else
                σ²[j] = σ²[j] + b[i]*vare
            end
        end
        for i = 1:p
            if j - i > 0
                σ²[j] = σ²[j] + a[i]*ϵ[j - i]^2
            else
                σ²[j] = σ²[j] + a[i]*vare
            end
        end
        σ²[j] += omega[1]
    end
    σ²
end

function StatsBase.loglikelihood(g::GARCH, x, theta)
    n = length(x)
    p = g.p
    q = g.q
    omega = theta[1]
    a = theta[2:1+p]
    b = theta[p+2:end]
    σ² = calculateVolatilityProcess(x, omega, a, b)
    l = zero(eltype(theta))
    @simd for j = eachindex(x)
        @inbounds l += -x[j]^2/σ²[j] - log(σ²[j])
    end
    (l  - n*log2π)/2
end

function StatsBase.loglikelihood(g::OptimGARCH, theta)
    x = g.x
    p = g.p
    q = g.q
    n = length(x)
    omega = theta[1]
    a = theta[2:1+p]
    b = theta[p+2:end]
    σ² = calculateVolatilityProcess(x, omega, a, b)
    l = zero(eltype(theta))
    @simd for j = eachindex(x)
        @inbounds l += -x[j]^2/σ²[j] - log(σ²[j])
    end
    (l  - n*log2π)/2
end

function garch(y, theta; p=1, q=1, solver = FinMetricsDefaultSolver())
    @assert length(theta) == p + q + 1 "parameter size inconsistent with
    GARCH($p, $q)"
    g = GARCH(p, q)
    update_parms!(g, theta)
    mod = fit(g, y, theta, solver)
    # println(MathProgBase.status(mod))
    theta = MathProgBase.SolverInterface.getsolution(mod)
    update_parms!(g, theta)
    g.o.status[1] = MathProgBase.SolverInterface.status(mod)
    g.o.objval[1] = MathProgBase.SolverInterface.getobjval(mod)
    g.o.solver[1] = solver
    g.fitted[1] = g.o.status[1] == :Optimal ? true : false
    g
end

function garch(y; p=1, q=1, solver = FinMetricsDefaultSolver())
    theta = [var(y); fill(.3/p, p); fill(0.6/q, q)]
    g = GARCH(p, q)
    update_parms!(g, theta)
    mod = fit(g, y, theta, solver)
    # println(MathProgBase.status(mod))
    theta = MathProgBase.SolverInterface.getsolution(mod)
    update_parms!(g, theta)
    g.o.status[1] = MathProgBase.SolverInterface.status(mod)
    g.o.objval[1] = MathProgBase.SolverInterface.getobjval(mod)
    g.o.solver[1] = solver
    g.fitted[1] = g.o.status[1] == :Optimal ? true : false
    g
end

function MathProgBase.initialize(d::OptimGARCH, rf::Vector{Symbol})
    for feat in rf
        if !(feat in [:Grad, :Jac])
            error("Unsupported feature $feat")
        end
    end
end

MathProgBase.features_available(d::OptimGARCH) = [:Grad, :Jac]

function MathProgBase.eval_f(d::OptimGARCH, theta)
    loglikelihood(d, theta)
end

function MathProgBase.eval_grad_f(d::OptimGARCH, g, theta)
    function obj(par)
        loglikelihood(d, par)
    end
    ForwardDiff.gradient!(g, obj, theta, Chunk{min(length(theta), 10)}())
end

MathProgBase.jac_structure(d::OptimGARCH) =  Int[],Int[]
MathProgBase.eval_jac_g(d::OptimGARCH, J, x) =  nothing

MathProgBase.eval_g(d::OptimGARCH, x, y) = nothing

function StatsBase.fit(g::GARCH, x, theta, solver = IpoptSolver(print_level = 4))
    push!(g.o.x, x...)
    shift!(g.o.x)
    p = g.p
    q = g.q
    np = length(theta)
    lb = [1e-10;
          zeros(p);
          zeros(q)]
    ub = [+10;
          ones(p).*.999;
          ones(q).*.999]

    mod = MathProgBase.NonlinearModel(solver)
    MathProgBase.loadproblem!(mod, np, 0, lb, ub, Float64[], Float64[], :Max, g.o)
    MathProgBase.setwarmstart!(mod, theta)
    MathProgBase.optimize!(mod)
    mod
end

getgarchparms(g) = (g.p, g.q, g.σ²₀[1], g.ω[1], g.α, g.β)
setinitialvar(g, s0::Float64) = g.σ²₀[:] = s0

export garch, GARCH, setinitialvar, getgarchparms, OptimGARCH


using ForwardDiff

abstract type JacobianParameters{T} end

struct JacobianParametersUser{T, JT <: Function} <: JacobianParameters{T}
    J::JT
end

struct JacobianParametersAD{T, FT <: Function, JT <: ForwardDiff.JacobianConfig} <: JacobianParameters{T}
    F!::FT
    Jconfig::JT
    tx::Vector{T}
    ty::Vector{T}
end

function JacobianParametersAD(F!::FT, Jconfig::JT, tx::Vector{T}, ty::Vector{T}) where {T, FT, JT}
    @assert length(tx) == length(ty)
    JacobianParametersAD{T, FT, JT}(F!, Jconfig, tx, ty)
end

function JacobianParametersAD(F!::FT, T, n::Int) where {FT <: Function}
    F!rev = (y,x) -> F!(x,y)
    tx = zeros(T, n)
    ty = zeros(T, n)
    Jconfig = ForwardDiff.JacobianConfig(F!rev, ty, tx)
    Jparams = JacobianParametersAD(F!rev, Jconfig, tx, ty)
end

struct JacobianParametersFD{T, FT} <: JacobianParameters{T}
    ϵ::T
    F!::FT
    f1::Vector{T}
    f2::Vector{T}
    e::Vector{T}
    tx::Vector{T}
end

function JacobianParametersFD(F!::FT, ϵ, T, n::Int) where {FT <: Function}
    f1 = zeros(T, n)
    f2 = zeros(T, n)
    e  = zeros(T, n)
    tx = zeros(T, n)
    Jparams = JacobianParametersFD{T, typeof(F!)}(ϵ, F!, f1, f2, e, tx)
end

function computeJacobian(x::Vector{T}, J::Matrix{T}, params::JacobianParametersUser{T}) where {T}
    params.J(x, J)
end

function computeJacobian(x::Vector{T}, J::Matrix{T}, params::JacobianParametersFD{T}) where {T}
    @assert length(x) == size(J, 1) == size(J, 2)

    local ϵⱼ::T

    for j in eachindex(x)
        ϵⱼ = params.ϵ * x[j] + params.ϵ
        fill!(params.e, 0)
        params.e[j] = 1
        params.tx .= x .- ϵⱼ .* params.e
        params.F!(params.tx, params.f1)
        params.tx .= x .+ ϵⱼ .* params.e
        params.F!(params.tx, params.f2)
        for i in eachindex(x)
            J[i,j] = (params.f2[i]-params.f1[i])/(2ϵⱼ)
        end
    end
end

function computeJacobian(x::Vector{T}, J::Matrix{T}, Jparams::JacobianParametersAD{T}) where {T}
    ForwardDiff.jacobian!(J, Jparams.F!, Jparams.ty, x, Jparams.Jconfig)
end

function computeJacobianFD(x::Vector{T}, J::Matrix{T}, F!::FT, ϵ::T) where{T, FT <: Function}
    params = JacobianParametersFD(F!, ϵ, T, length(x))
    computeJacobian(x, J, params)
end

function computeJacobianAD(x::Vector{T}, J::Matrix{T}, F!::FT) where{T, FT <: Function}
    params = JacobianParametersAD(F!, T, length(x))
    computeJacobian(x, J, params)
end

function getJacobianParameters(J, F!, T, n)
    if J == nothing
        if get_config(:jacobian_autodiff)
            Jparams = JacobianParametersAD(F!, T, n)
        else
            Jparams = JacobianParametersFD(F!, get_config(:jacobian_fd_ϵ), T, n)
        end
    else
        Jparams = JacobianParametersUser{T, typeof(J)}(J)
    end
    return Jparams
end


#floyd(n) creates an n-row floyd's triangle counting from 1 to (n/2+.5)*n
function floyd(n)
	x = 1
	dig(x,line,n) = (while line < n; x+=line; line+= 1 end; return ndigits(x)+1)
	for line = 1:n, i = 1:line; print(lpad(x,dig(x,line,n)," ")); x+=1; i==line && print("\n") end
end


#TIME SERIES FUNCTIONS FOR MC HAMMER
#by Eric Torkia, April 2019

# mch_timeseries contains functions to create simulated times series with mc_hammer. Current implementation supports GBM only. Other methods should be added

#--------------------------------------
"""
    GBMMfit(HistoricalData, PeriodsToForecast)

GBMMfit uses a vector of historical data to calculate the log returns and use the mean and standard deviation to project a random walk. It the uses the last datapoint in the set as the starting point for the new forecast.

**HistoricalData**: Vector containing historical data

**PeriodsToForecast**: integer >1

"""
function GBMMfit(HistoricalData, PeriodsToForecast)
#calculate returns
Returns_Arr = []
for i = 1:size(HistoricalData,1)-1
    T2_cursor = 1
    Period_T1 = HistoricalData[i,1]
    Period_T2 = HistoricalData[i+T2_cursor,1]
    if  ismissing(HistoricalData[i+T2_cursor,1])==1
        Period_T2 = Period_T1
                #T2_cursor += 1
    else
        Period_T2 = HistoricalData[i+T2_cursor,1]
    end

    returns =
    if isinf(log(Period_T1 / Period_T2))==1
        returns = 0
    elseif isnan(log(Period_T1 / Period_T2))==1
        returns = 0
    else
        log(Period_T1 / Period_T2)
    end
    push!(Returns_Arr, returns)
end
push!(Returns_Arr, 0 )

# project Series
    frct_multiplier = cumprod(rand(Normal(mean(Returns_Arr),std(Returns_Arr)),PeriodsToForecast,1) + fill(1,PeriodsToForecast,1), dims=1)
    LastValue = HistoricalData[size(HistoricalData,1)]
    result = LastValue .* frct_multiplier
    return result
end

"""
    GBMM(LastValue, ReturnsMean, ReturnsStd, PeriodsToForecast)

GBMM produces a random wlak using the last data point and requires a mean and standard deviation to be provided.

**LastValue**: The most recent data point on which to base your random walk.

**ReturnsMean and ReturnsStd** : Historical Mean and Standard Deviation of Returns

**PeriodsToForecast** is an integer >1
"""
function GBMM(LastValue, ReturnsMean, ReturnsStd, PeriodsToForecast)
    # project Series
        frct_multiplier = cumprod(rand(Normal(ReturnsMean,ReturnsStd),PeriodsToForecast,1) + fill(1,PeriodsToForecast,1), dims=1)
        result = LastValue .* frct_multiplier
        return result
    end

    """
        GBMA_d(price_0, t, rf, exp_vol)

    GBMA_d allows you to forecast the stock price at a given day in the future.

    This function uses a multiplicative Geometric Brownian Motion to forecast

    """
    function GBMA_d(price_0, t, rf, exp_vol)
    forecast = price_0 * exp((rf - ((exp_vol^2)/2) *t/252)+ (exp_vol*rand(Normal(0,1))*sqrt(t/252)))
    return forecast
    end