module Permutations
using LinearAlgebra, Combinatorics

import Base:
    length,
    show,
    inv,
    reverse,
    ==,
    getindex,
    *,
    ^,
    sign,
    hash,
    getindex,
    Matrix,
    Array,
    AbstractMatrix,
    AbstractArray,
    Array,
    adjoint

import Combinatorics: nthperm
import Random: randperm

export Permutation, RandomPermutation
export length, getindex, array, two_row
export inv, cycles, cycle_string
export order, matrix, fixed_points
export longest_increasing, longest_decreasing, reverse, sign
export hash, dict, Transposition
export CoxeterDecomposition  # no reason to expose CoxeterGenerator

# Defines the Permutation class. Permutations are bijections of 1:n.

abstract type AbstractPermutation end

"""
    Permutation

* `Permutation(list)` creates a new `Permutation`. Here `list` must be a rearrangement of `1:n`.
* `Permutation(n)` creates the identity `Permutation` of `1:n`.
* `Permutation(n,k)` creates the `k`'th `Permutation` of `1:n`.
* `Permutation(P::Matrix{Int})` creates a `Permutation` from a permutation matrix.
* `Permutation(cycles::Vector{Vector{Int}})` creates a `Permutation` from a list of disjoint cycles.
"""
struct Permutation <: AbstractPermutation
    data::Vector{Int}
    function Permutation(dat::Vector{Int})
        n = length(dat)
        if sort(dat) != collect(1:n)
            error("Improper array: must be a permutation of 1:n")
        end
        new(dat)
    end
end

Permutation(v::AbstractVector) = Permutation(Vector{Int}(v))

# create the k'th permutation of 1:n
function Permutation(n, k)
    return Permutation(nthperm(collect(1:n), k))
end

# create the identity permutation
Permutation(n::Int) = Permutation(collect(1:n))

"""
    Transposition(n,a,b)

Create a permutation of `1:n` swapping `a` for `b`.
"""
function Transposition(n::Int, a::Int, b::Int)::Permutation
    @assert 0 < a ≤ n && 0 < b ≤ n && a ≠ b "Require n,a,b to be positive, with 0 ≤ a ≠ b ≤ n, got n=$n, a=$a, and b=$b"
    list = collect(1:n)
    @inbounds list[a] = b
    @inbounds list[b] = a
    return Permutation(list)
end

"""
    RandomPermutation(n::Int)

Create a random permutation of `1:n`, each with probability `1/factorial(n)`.
"""
RandomPermutation(n::Int) = Permutation(randperm(n))

# Returns the number of elements in the Permtuation

"""
    length(p::Permutation)

Return the number of elements in `p`.
"""
function length(p::Permutation)::Int
    return length(p.data)
end

# Check for equality of permutations
==(p::Permutation, q::Permutation) = p.data == q.data

# Apply the Permutation to an element: p[k] or p(k)
getindex(p::Permutation, k::Int) = p.data[k]
(p::Permutation)(k::Int) = p.data[k]

# Create a two-row representation of this permutation
"""
    two_row(p::Permutation)

Createa a two-row representation of the `Permutation` `p`
in which the first row is `1:n` and the second row are the
values `p(1), p(2), ..., p(n)`.
"""
function two_row(p::Permutation)::Matrix{Int}
    n = length(p)
    return [(1:n)'; p.data']
end

# Composition of two permutations
*(p::Permutation) = p
function *(p::Permutation, q::Permutation)
    n = length(p)
    if n != length(q)
        error("Cannot compose Permutations of different lengths")
    end
    @inbounds dat = [p.data[q.data[k]] for k = 1:n]
    return Permutation(dat)
end

# Inverse of a permutation
"""
    inv(p::Permutation)

Give the inverse of `Permutation` `p`. This may
also be computed with `p'`.
"""
function inv(p::Permutation)
    n = length(p)
    data = zeros(Int, n)
    for k = 1:n
        @inbounds j = p.data[k]
        @inbounds data[j] = k
    end
    return Permutation(data)
end

adjoint(p::Permutation) = inv(p)

# Find the cycles in a permutation
"""
    cycles(p::Permutation)

Return a list of the cycles in `Permutation` `p`.
"""
function cycles(p::Permutation)
    n = length(p)
    result = Array{Int,1}[]
    todo = trues(n)
    while any(todo)
        k = findfirst(todo)
        todo[k] = false
        cycle = [k]
        j = p[k]
        while j != k
            push!(cycle, j)
            todo[j] = false
            j = p[j]
        end
        push!(result, cycle)
    end
    return result
end

"""
    cycle_string(p::Permutation)

Create a nice, prinatble string representation
from the cycle structure of the permutation `p`.
"""
function cycle_string(p::Permutation)::String
    if length(p) == 0
        return "()"
    end
    str = ""
    cc = cycles(p)
    for c in cc
        str *= array2string(c)
    end
    return str
end

# helper function for cycle_string (not exposed). Converts an integer
# array such as [1,3,5,2] into the string "(1,3,5,2)".
function array2string(a::Array{Int,1})
    n = length(a)
    res = "("
    for k = 1:n
        res *= string(a[k])
        if k < n
            res *= ","
        else
            res *= ")"
        end
    end
    return res
end

"""
    sign(p::Permutation)

Return `+1` if`p` is an even `Permtuation` and `-1` if p is odd.
"""
function sign(p::Permutation)::Int
    n = length(p)
    result = 0
    todo = trues(n)
    while any(todo)
        k = findfirst(todo)
        todo[k] = false
        result += 1 # increment element count
        j = p[k]
        while j != k
            result += 1 # increment element count
            todo[j] = false
            j = p[j]
        end
        result += 1 # increment cycle count
    end
    return isodd(result) ? -1 : 1
end

function Permutation(cc::Vector{Vector{Int}})::Permutation
    # check the list of cycles has exactly one each from 1 to n
    elts = sort(union(cc...))
    n = length(elts)
    plist = collect(1:n)
    @assert elts == plist "Invalid list of cycles"

    for c in cc
        nc = length(c)
        for i = 1:nc
            a = c[i]
            b = c[mod1(i + 1, nc)]
            plist[a] = b
        end
    end
    return Permutation(plist)
end

# When we print a permutation, it appears in disjoint cycle format.
function show(io::IO, p::Permutation)
    print(io, cycle_string(p))
end

# Find the smallest positive n such that p^n is the identity
"""
    order(p::Permutation)

Return the smallest positive integer `n`
such that `p^n` is the identity `Permutation`.
"""
function order(p::Permutation)
    result = 1
    clist = cycles(p)
    for c in clist
        result = lcm(result, length(c))
    end
    return result
end

# Extend p^n so negative exponents work too
function ^(p::Permutation, n::Int)
    if n == 0
        return Permutation(length(p))
    end
    if n < 0
        return inv(p)^(-n)
    end
    if n == 1
        return p
    end

    m::Int = round(Int, floor(n / 2)) # int(floor(n/2))  # m = floor(n/2)
    q::Permutation = p^m

    if n % 2 == 0   # if even
        return q * q
    end

    return p * q * q
end

# find the fixed points of a Permutation
"""
    fixed_points(p::Permutation)

Return the list of values `k` for which `p(k)==k`.
"""
fixed_points(p::Permutation) = findall([p[k] == k for k = 1:length(p)])

# Find a longest monotone subsequence of p in a given direction. This
# is not exposed, but it is used by longest_increasing and
# longest_increasing.
function longest_monotone(p::Permutation, order = <)::Vector{Int}
    n = length(p)
    if n == 0
        return Int[]
    end
    scores = ones(Int, n)
    scores[n] = 1

    for k = n-1:-1:1
        for i = k+1:n
            if order(p[k], p[i]) && scores[k] <= scores[i]
                scores[k] = scores[i] + 1
            end
        end
    end

    seq = Int[]
    mx = maximum(scores)
    a = findfirst([s == mx for s in scores])
    push!(seq, a)

    while scores[a] > 1
        val = scores[a] - 1
        idx = findfirst([scores[k] == val && order(p[a], p[k]) for k = a+1:n])
        a += idx
        push!(seq, a)
    end

    return [p[i] for i in seq]

end

"""
    longest_increasing(p::Permutation)

Thinking of `p` as a list of values, return a longest subsequence of values
that are in ascending order.
"""
longest_increasing(p::Permutation) = longest_monotone(p, <)

"""
    longest_decreasing(p::Permutation)

Thinking of `p` as a list of values, return a longest subsequence of values
that are in descending order.
"""
longest_decreasing(p::Permutation) = longest_monotone(p, >)

"""
    reverse(p::Permutation)

Thinking of `p` as a list of values,
return a `Permutation` with those values in reverse
sequence.
"""
function reverse(p::Permutation)::Permutation
    d = reverse(p.data)
    return Permutation(d)
end

# hash function so Permutations can be keys in dictionaries, etc.
hash(p::Permutation, h::UInt64) = hash(p.data, h)

"""
    dict(p::Permutation)

Convert a permutation into a dictionary. If `d` is the
result then `d[k]` equals `p(k)` for all `k` in the domain of the
permutation.
"""
function dict(p::Permutation)::Dict{Int,Int}
    n = length(p)
    d = Dict{Int,Int}()
    for i = 1:n
        d[i] = p(i)
    end
    return d
end

#####
# Decomposing into Coxeter generators
#####

struct CoxeterGenerator <: AbstractPermutation
    n::Int
    i::Int
    function CoxeterGenerator(n::Int, i::Int)
        1 ≤ i ≤ n - 1 || throw(ArgumentError("$i must be between 1 and $n-1"))
        new(n, i)
    end
end

length(sᵢ::CoxeterGenerator) = sᵢ.n
Permutation(P::CoxeterGenerator) = Permutation([1:P.i-1; P.i + 1; P.i; P.i+2:P.n])

function show(io::IO, p::CoxeterGenerator)
    print(io, "length $(p.n) permutation: s_$(p.i)")
end

# _coxeter_reduce reduces a product of simple transpositions using the relationships
# https://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations
# combined with a sorting for uniqueness
function _coxeter_reduce!(terms::Vector{Int})
    @label start
    for i = 1:length(terms)-1
        # sᵢ^2 = I
        if terms[i] == terms[i+1]
            deleteat!(terms, i:i+1)
            @goto start
        end
        # sort using sᵢsⱼ = sⱼsᵢ
        if terms[i+1] ≠ terms[i] - 1 && terms[i+1] ≠ terms[i] + 1 && terms[i] > terms[i+1]
            terms[i], terms[i+1] = terms[i+1], terms[i]
            @goto start
        end
    end
    # (sᵢsᵢ₊₁)^3 = I
    for i = 1:length(terms)-5
        if terms[i] + 1 ==
           terms[i+1] ==
           terms[i+2] + 1 ==
           terms[i+3] ==
           terms[i+4] + 1 ==
           terms[i+5]
            deleteat!(terms, i:i+5)
            @goto start
        end
    end

    terms
end

"""
    CoxeterDecomposition(p::Permutation)

Express the `Permutation` `p` as a
composition of transpositions of the form `(k,k+1)`.
"""
struct CoxeterDecomposition <: AbstractPermutation
    n::Int
    terms::Vector{Int}
    function CoxeterDecomposition(n::Int, terms::Vector{Int})
        for t in terms
            1 ≤ t ≤ n - 1 || throw(ArgumentError("$t must be between 1 and $n-1"))
        end
        new(n, _coxeter_reduce!(terms))
    end
end

CoxeterDecomposition(n::Int, terms::AbstractVector) =
    CoxeterDecomposition(n, Vector{Int}(terms))
CoxeterDecomposition(sᵢ::CoxeterGenerator) = CoxeterDecomposition(sᵢ.n, [sᵢ.i])

length(P::CoxeterDecomposition) = P.n

function Permutation(P::CoxeterDecomposition)
    if isempty(P.terms)
        Permutation(1:P.n)
    else
        *(Permutation.(CoxeterGenerator.(P.n, P.terms))...)
    end
end
CoxeterDecomposition(P::Permutation) = CoxeterDecomposition!(Permutation(copy(P.data)))
CoxeterDecomposition!(P::Permutation) =
    CoxeterDecomposition(length(P), _coxeterdecomposition!(P))
function _coxeterdecomposition!(P::Permutation)
    n = length(P)
    data = P.data
    ret = Int[]
    while !issorted(data)
        for k = 1:n-1
            if data[k] > data[k+1]
                data[k], data[k+1] = data[k+1], data[k]
                push!(ret, k)
            end
        end
    end
    reverse!(ret)
end

==(A::CoxeterDecomposition, B::CoxeterDecomposition) = A.terms == B.terms

function *(A::CoxeterGenerator, B::CoxeterGenerator)
    length(A) == length(B) ||
        throw(ArgumentError("Permutations must have same length to multiply"))
    CoxeterDecomposition(length(A), [A.i, B.i])
end
function *(A::CoxeterGenerator, B::CoxeterDecomposition)
    length(A) == length(B) ||
        throw(ArgumentError("Permutations must have same length to multiply"))
    CoxeterDecomposition(length(A), [A.i; B.terms])
end
function *(A::CoxeterDecomposition, B::CoxeterGenerator)
    length(A) == length(B) ||
        throw(ArgumentError("Permutations must have same length to multiply"))
    CoxeterDecomposition(length(A), [A.terms; B.i])
end
function *(A::CoxeterDecomposition, B::CoxeterDecomposition)
    length(A) == length(B) ||
        throw(ArgumentError("Permutations must have same length to multiply"))
    CoxeterDecomposition(length(A), [A.terms; B.terms])
end

inv(A::CoxeterDecomposition) = CoxeterDecomposition(A.n, reverse(A.terms))
adjoint(A::CoxeterDecomposition) = inv(A)

function show(io::IO, p::CoxeterDecomposition)
    print(io, "Permutation of 1:$(length(p)): ")
    if length(p.terms) == 0
        print(io, "()")
    end
    for i in p.terms
        # print(io, "s_$(i)")
        print(io, "($i,$(i+1))")
    end
end

# deprecations
@deprecate array(p::Permutation) p.data
@deprecate matrix(p::Permutation, sparse::Bool = false) sparse ? sparse(p) : Matrix(p)

include("matrix.jl")
include("sqrt.jl")
include("perm_gen.jl")

end # end of module Permutations


module BenchDistance
using BenchmarkTools
using ParallelKMeans
using Distances
using Random

suite = BenchmarkGroup()

Random.seed!(2020)
X = rand(100_000, 3)
centroids = rand(2, 3)
d = rand(100_000, 2)
suite["100kx3"] = @benchmarkable ParallelKMeans.pairwise!($d, $X, $centroids)

X = rand(100_000, 10)
centroids = rand(2, 10)
d = rand(100_000, 2)
suite["100kx10"] = @benchmarkable ParallelKMeans.pairwise!($d, $X, $centroids)

# for reference
metric = SqEuclidean()
suite["100kx10_distances"] = @benchmarkable Distances.pairwise!($d, $metric, $X, $centroids, dims = 1)

end # module

BenchDistance.suite


using ExtremeStats
using Base.Test

# Test getSeasStat function
# Two subsequent years with 1st year sin(t) and 2nd year 2*sin(t)
t=linspace(0,2*pi,46)
testx=[sin(t),2*sin(t)]
testx=reshape(testx,1,1,92)
msc=Array(Float64,46)
stdmsc=Array(Float64,46)
n=Array(Int64,46)
ExtremeStats.getSeasStat(testx,1,1,2,46,msc,stdmsc,n)
# msc should be 1.5*si(t)
@test all(msc.==1.5*sin(t))
# std of msc should be 0.5 sin(t)
@test all([isapprox(abs(stdmsc[i]),abs(0.5*sin(t)[i])) for i = 1:46])
#Another test, direct comparison to R results
rtab=readdlm(joinpath(Pkg.dir(),"ExtremeStats/test/anomaly_example.csv"),' ')
r_in=reshape(rtab[:,1],1,1,552)
r_out=reshape(rtab[:,2],1,1,552)
j_out=get_anomalies(r_in,46)
@test all([isapprox(r_out[i],j_out[i]) for i=1:552])

#Test Extreme Cluster detection
a=zeros(Float32,100,100,100)
#Define an area vector
area=Float32[1:100]
#Define 2 clusters of very high values
b=Float32[i*0.1+k for i=1:10,j=1:10,k=1:10]
bweights=[area[j] for i=1:10,j=46:55,k=1:10]
a[91:100,46:55,66:75]=b
a[1:10,46:55,66:75]=b
#Also add a low extreme
c=rand(Float32,10,10,10)-100.0
a[51:60,51:60,51:60]=c
cweights=[area[j] for i=51:60,j=51:60,k=51:60]

#And add some NaNs
a[9:10,9:10,9:10]=NaN32
#Test without circular
el1=label_Extremes(a,quantile=0.95,area=area,circular=false)
f1=f1=getFeatures(el1,Features.Mean,Features.Max_z,Features.Min_z,Features.Duration,Features.Size,Features.NumPixel,
    Features.Min_t,Features.Max_t,Features.Min_lon,Features.Max_lon,Features.Min_lat,Features.Max_lat,Features.Quantile,
    Features.TS_ZValue, Features.TS_Area);

    #Now test all results
@test length(el1.extremes)==2
@test length(el1.extremes[1].zvalues)==length(b)
@test all(sort(el1.extremes[1].zvalues).==sort(b[:]))
@test all(sort(el1.extremes[2].zvalues).==sort(b[:]))
@test el1.extremes[1].tbounds==(66,75)
@test el1.extremes[1].lonbounds==(1,10)
@test el1.extremes[1].latbounds==(46,55)
@test el1.extremes[2].tbounds==(66,75)
@test el1.extremes[2].lonbounds==(91,100)
@test el1.extremes[2].latbounds==(46,55)
#Test first cluster
@test isapprox(f1[1][1],mean(b))
@test isapprox(f1[2][1],maximum(b))
@test isapprox(f1[3][1],minimum(b))
@test isapprox(f1[4][1],9)
@test isapprox(f1[5][1],mean(area[46:55])*1000)
@test f1[6][1]==1000
@test f1[7][1]==66
@test f1[8][1]==75
@test f1[9][1]==1
@test f1[10][1]==10
@test f1[11][1]==46
@test f1[12][1]==55
@test isapprox(f1[13][1][1],quantile(b[:],0.1),rtol=0.05)
@test isapprox(f1[13][1][2],quantile(b[:],0.5),rtol=0.05)
@test isapprox(f1[13][1][3],quantile(b[:],0.9),rtol=0.05)
@test all([isapprox(f1[14][1][i],(sum((b.*bweights),(1,2))./sum(bweights,(1,2)))[i]) for i=1:length(f1[14][1])])
@test all([isapprox(f1[15][1][i],sum(bweights,(1,2))[i]) for i=1:length(f1[15][1])])
#Test second cluster
@test isapprox(f1[1][2],mean(b))
@test isapprox(f1[2][2],maximum(b))
@test isapprox(f1[3][2],minimum(b))
@test isapprox(f1[4][2],9)
@test isapprox(f1[5][2],mean(area[46:55])*1000)
@test f1[6][2]==1000
@test f1[7][2]==66
@test f1[8][2]==75
@test f1[9][2]==91
@test f1[10][2]==100
@test f1[11][2]==46
@test f1[12][2]==55
@test isapprox(f1[13][2][1],quantile(b[:],0.1),rtol=0.05)
@test isapprox(f1[13][2][2],quantile(b[:],0.5),rtol=0.05)
@test isapprox(f1[13][2][3],quantile(b[:],0.9),rtol=0.05)
@test all([isapprox(f1[14][2][i],(sum((b.*bweights),(1,2))./sum(bweights,(1,2)))[i]) for i=1:length(f1[14][2])])
@test all([isapprox(f1[15][2][i],sum(bweights,(1,2))[i]) for i=1:length(f1[15][2])])

#Test with circular
el2 = label_Extremes(a,quantile=0.95,area=area,circular=true)
f2=getFeatures(el2,Features.Mean,Features.Max_z,Features.Min_z,Features.Duration,Features.Size,Features.NumPixel,
    Features.Min_t,Features.Max_t,Features.Min_lon,Features.Max_lon,Features.Min_lat,Features.Max_lat,Features.Quantile,
    Features.TS_ZValue, Features.TS_Area);

b=cat(1,b,b)
bweights=cat(1,bweights,bweights)
@test length(el2.extremes)==1
@test all(sort(el2.extremes[1].zvalues).==sort(b[:]))
@test isapprox(f2[1][1],mean(b))
@test isapprox(f2[2][1],maximum(b))
@test isapprox(f2[3][1],minimum(b))
@test isapprox(f2[4][1],9)
@test isapprox(f2[5][1],mean(area[46:55])*2000)
@test f2[6][1]==2000
@test f2[7][1]==66
@test f2[8][1]==75
@test f2[9][1]==1
@test f2[10][1]==100
@test f2[11][1]==46
@test f2[12][1]==55
@test isapprox(f2[13][1][1],quantile(b[:],0.1),rtol=0.05)
@test isapprox(f2[13][1][2],quantile(b[:],0.5),rtol=0.05)
@test isapprox(f2[13][1][3],quantile(b[:],0.9),rtol=0.05)
@test all([isapprox(f2[14][1][i],(sum((b.*bweights),(1,2))./sum(bweights,(1,2)))[i]) for i=1:length(f2[14][1])])
@test all([isapprox(f2[15][1][i],sum(bweights,(1,2))[i]) for i=1:length(f2[15][1])])

# Now label low extremes
el3=label_Extremes(a,quantile=0.05,area=area,circular=false)
f3=getFeatures(el3,Features.Mean,Features.Max_z,Features.Min_z,Features.Duration,Features.Size,Features.NumPixel,
    Features.Min_t,Features.Max_t,Features.Min_lon,Features.Max_lon,Features.Min_lat,Features.Max_lat,Features.Quantile,
    Features.TS_ZValue, Features.TS_Area);
@test length(el3.extremes)==1
@test all(sort(el3.extremes[1].zvalues).==sort(c[:]))
@test isapprox(f3[1][1],mean(c))
@test isapprox(f3[2][1],maximum(c))
@test isapprox(f3[3][1],minimum(c))
@test isapprox(f3[4][1],9)
@test isapprox(f3[5][1],mean(area[51:60])*1000)
@test f3[6][1]==1000
@test f3[7][1]==51
@test f3[8][1]==60
@test f3[9][1]==51
@test f3[10][1]==60
@test f3[11][1]==51
@test f3[12][1]==60
@test isapprox(f3[13][1][1],quantile(c[:],0.1),rtol=0.05)
@test isapprox(f3[13][1][2],quantile(c[:],0.5),rtol=0.05)
@test isapprox(f3[13][1][3],quantile(c[:],0.9),rtol=0.05)
@test all([isapprox(f3[14][1][i],(sum((c.*cweights),(1,2))./sum(cweights,(1,2)))[i]) for i=1:length(f3[14][1])])
@test all([isapprox(f3[15][1][i],sum(cweights,(1,2))[i]) for i=1:length(f3[15][1])])


using Plots
using LinearAlgebra
using MatrixEquations
using CPUTime
using ArraysOfArrays
using Parameters

const M = 5.0  # 車両質量[kg]
const m = 1.0  # 振子質量[kg]
const L = 1.5  # 振子長さ[m]
const l = 0.75
const D = 0.01  # 車両と地面の動摩擦係数
const d = 0.01  # 振子と車両の動摩擦係数
const g = 9.80665  # 重力加速度[m/s²]

function split_vec_of_arrays(u)
    vec.(u) |>
    x ->
    VectorOfSimilarVectors(x).data |>
    transpose |>
    VectorOfSimilarVectors
end

"""ルンゲクッタ法（4次）"""
function solve_RungeKutta(dx, x₀, t_span, Δt, args...)

    t = range(t_span..., step = Δt)  # 時間軸
    x = Vector{typeof(x₀)}(undef, length(t))  # 解を格納する1次元配列

    x[1] = x₀  # 初期値
    for i in 1:length(x)-1
        k₁ = dx(t[i], x[i], args)
        k₂ = dx(t[i]+Δt/2, x[i]+k₁*Δt/2, args)
        k₃ = dx(t[i]+Δt/2, x[i]+k₂*Δt/2, args)
        k₄ = dx(t[i]+Δt, x[i]+k₃*Δt, args)
        x[i+1] = x[i] .+ (k₁ .+ 2k₂ .+ 2k₃ .+ k₄) .* Δt/6
    end

    t, x
end

"""パラメータ"""
@with_kw struct LQRParam{T}
    Q::Matrix{T}
    R::Matrix{T}
end

function dx(t, x, K::Tuple{Matrix{T}}) where T
    """LQRのときの微分方程式"""

    multi = [
        1.0 0.0 0.0 0.0
        0.0 0.0 1.0 0.0
        0.0 M+m 0.0 m*l*cos(x[3])
        0.0 m*l*cos(x[3]) 0.0 4/3*m*l^2
    ]
    offset_x = [
        x[2]
        x[4]
        m*l*x[4]^2*sin(x[3])-D*x[2]
        m*g*l*sin(x[3])-d*x[4]
    ]
    offset_u = [
        0.0
        0.0
        1.0
        0.0
    ]

    Fa = inv(multi) * offset_x
    Fb = inv(multi) * offset_u

    u = -K[1] * x

    dx = Fa .+ Fb.*u
end

function run_simulation(p::LQRParam{T}, Δt::T=0.01, TIME_SPAN::T=5.0) where T
    """LQRで制御"""

    # 原点近傍での線形化システム
    multi = [
        1.0 0.0 0.0 0.0
        0.0 0.0 1.0 0.0
        0.0 M+m 0.0 m*L
        0.0 m*L 0.0 4/3*m*l^2
    ]
    offset_x = [
        0.0 1.0 0.0 0.0
        0.0 0.0 0.0 1.0
        0.0 -D 0.0 0.0
        0.0 0.0 m*g*l -d
    ]
    offset_u = [
        0.0
        0.0
        1.0
        0.0
    ]

    A = inv(multi) * offset_x
    B = inv(multi) * offset_u

    # リカッチ方程式を解く
    S = zero(B)
    P, _, _ = arec(A, B, p.R, p.Q, S)

    K = inv(p.R) * B' * P  # 最適フィードバックゲイン
    println("K = ", K)

    ### 数値シミュレーション ###
    x0 = [0.0, 0.0, π/6, 0.0]
    tspan = (0.0, TIME_SPAN)  # 範囲を設定
    t, x = solve_RungeKutta(dx, x0, tspan, Δt, K)
    x, v, θ, ω = split_vec_of_arrays(x)

    # plot
    #plot([x, v, θ, ω], label=["x" "v" "θ" "ω"])

    anim = @animate for i in 1:2:length(t)
        now = round(i * Δt, digits=1)
        s = "t = " * string(now) * " [s]"
        plot(
            [x[i], x[i]+L*cos(-θ[i]+π/2)],
            [0, L*sin(-θ[i]+π/2)],
            #size=(400,400),
            linewidth=5,
            xlims = (-3L, 3L),
            ylims = (-1.1L, 1.1L),
            aspect_ratio = 1,
            legend=false,
            annotate=(0, -1, s),
        )
    end
    gif(anim, "example_8_jl.gif", fps = 30)
    #mp4(anim, "example_8_jl.mp4", fps = 30)

end

p = LQRParam(
    diagm(0 => [10.0, 10.0, 100.0, 100.0]),  # 重み行列
    ones((1,1)) .* 0.01  # 重み行列
)

@time run_simulation(p)


module ApproximateGPs

using Reexport
@reexport using AbstractGPs
@reexport using GPLikelihoods
using Distributions
using LinearAlgebra
using Statistics
using StatsBase
using FastGaussQuadrature
using SpecialFunctions
using ChainRulesCore
using FillArrays
using PDMats: chol_lower
using IrrationalConstants: sqrt2, invsqrtπ

using AbstractGPs: AbstractGP, FiniteGP, LatentFiniteGP, ApproxPosteriorGP, At_A, diag_At_A

include("utils.jl")

export DefaultQuadrature, Analytic, GaussHermite, MonteCarlo
include("expected_loglik.jl")

export SparseVariationalApproximation, Centered, NonCentered
include("sparse_variational.jl")

using ForwardDiff

export LaplaceApproximation
export build_laplace_objective, build_laplace_objective!
export approx_lml  # TODO move to AbstractGPs, see https://github.com/JuliaGaussianProcesses/AbstractGPs.jl/issues/221
include("laplace.jl")

include("deprecations.jl")

end


@testset "Katz" begin
    g5 = DiGraph(4)
    add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4)
    for g in testdigraphs(g5)
        z = @inferred(katz_centrality(g, 0.4))
        @test round.(z, 2) == [0.32, 0.44, 0.62, 0.56]
    end
end


using SparseArrays, DiffEqOperators, LinearAlgebra, Random,
      Test, BandedMatrices, FillArrays, LazyArrays, BlockBandedMatrices

# This test file tests for the correctness of higher dimensional concretization.
# The tests verify that multiplication in the concretized case agrees with the matrix-free
# multiplication

@testset "First Dimension" begin

      # Test that even when we have a vector, the concretizations using the higher dimension dispatch still function
      # correctly
      M = rand(22)

      L1 = CenteredDifference(1,2,1.0,20)
      L2 = CenteredDifference(1,2,1.0,20)
      L3 = CenteredDifference(3,3,1.0,20)

      @test L1*M ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*M ≈ BandedMatrix(L1, size(M))*M ≈ BandedBlockBandedMatrix(L1,size(M))*M
      @test L2*M ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*M ≈ BandedMatrix(L2, size(M))*M ≈ BandedBlockBandedMatrix(L2,size(M))*M
      @test L3*M ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*M ≈ BandedMatrix(L3, size(M))*M ≈ BandedBlockBandedMatrix(L3,size(M))*M

      M = rand(22,2,2,2,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

end

@testset "Second Dimension" begin

      M = rand(2,22,2)

      L1 = CenteredDifference{2}(1,2,1.0,20)
      L2 = CenteredDifference{2}(1,2,1.0,20)
      L3 = CenteredDifference{2}(3,3,1.0,20)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

      M = rand(2,22,2,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

      M = rand(2,22,2,2,3)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

end

@testset "Third Dimension" begin

      M = rand(3,2,22)

      L1 = CenteredDifference{3}(1,2,1.0,20)
      L2 = CenteredDifference{3}(1,2,1.0,20)
      L3 = CenteredDifference{3}(3,3,1.0,20)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

      M = rand(3,2,22,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

      M = rand(3,2,22,2,3)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

end

@testset "Fifth Dimension" begin

      M = rand(3,2,3,2,22)

      L1 = CenteredDifference{5}(1,2,1.0,20)
      L2 = CenteredDifference{5}(1,2,1.0,20)
      L3 = CenteredDifference{5}(3,3,1.0,20)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

      M = rand(3,2,3,2,22,3)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

      M = rand(2,2,2,2,22,3,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M)

end

@testset "Biased Upwind : Positive Wind with offside, regular grid" begin

      # Test that Biased Upwind with maximum offside concretizes as CenteredDifference of same order
      M = rand(22)
      P = PeriodicBC(Float64)*M[2:21]

      L1 = CenteredDifference(1,2,1.0,20)
      L2 = CenteredDifference(1,4,1.0,20)
      L3 = CenteredDifference(1,6,1.0,20)
      L4 = CenteredDifference(2,3,1.0,20)
      L5 = CenteredDifference(3,4,1.0,20)

      K1 = UpwindDifference(1,2,1.0,20,offside=1)
      K2 = UpwindDifference(1,4,1.0,20,offside=2)
      K3 = UpwindDifference(1,6,1.0,20,offside=3)
      K4 = UpwindDifference(2,3,1.0,20,offside=2)
      K5 = UpwindDifference(3,4,1.0,20,offside=3)

      @test L1*M ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*M ≈ BandedMatrix(L1, size(M))*M ≈ BandedBlockBandedMatrix(L1,size(M))*M ≈ K1*M ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*M ≈ BandedMatrix(K1, size(M))*M ≈ BandedBlockBandedMatrix(K1,size(M))*M
      @test L2*M ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*M ≈ BandedMatrix(L2, size(M))*M ≈ BandedBlockBandedMatrix(L2,size(M))*M ≈ K2*M ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*M ≈ BandedMatrix(K2, size(M))*M ≈ BandedBlockBandedMatrix(K2,size(M))*M
      @test L2*P ≈ Array(L2, size(P))*vec(P) ≈ sparse(L2,size(P))*P ≈ BandedMatrix(L2, size(P))*P ≈ BandedBlockBandedMatrix(L2,size(P))*P ≈ K2*P ≈ Array(K2, size(P))*vec(P) ≈ sparse(K2,size(P))*P ≈ BandedMatrix(K2, size(P))*P ≈ BandedBlockBandedMatrix(K2,size(P))*P
      @test L3*M ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*M ≈ BandedMatrix(L3, size(M))*M ≈ BandedBlockBandedMatrix(L3,size(M))*M ≈ K3*M ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*M ≈ BandedMatrix(K3, size(M))*M ≈ BandedBlockBandedMatrix(K3,size(M))*M
      @test L4*M ≈ Array(L4, size(M))*vec(M) ≈ sparse(L4,size(M))*M ≈ BandedMatrix(L4, size(M))*M ≈ BandedBlockBandedMatrix(L4,size(M))*M ≈ K4*M ≈ Array(K4, size(M))*vec(M) ≈ sparse(K4,size(M))*M ≈ BandedMatrix(K4, size(M))*M ≈ BandedBlockBandedMatrix(K4,size(M))*M
      @test L5*M ≈ Array(L5, size(M))*vec(M) ≈ sparse(L5,size(M))*M ≈ BandedMatrix(L5, size(M))*M ≈ BandedBlockBandedMatrix(L5,size(M))*M ≈ K5*M ≈ Array(K5, size(M))*vec(M) ≈ sparse(K5,size(M))*M ≈ BandedMatrix(K5, size(M))*M ≈ BandedBlockBandedMatrix(K5,size(M))*M

      M = rand(22,2,2,2,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M) ≈ vec(K1*M) ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*vec(M) ≈ BandedMatrix(K1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M) ≈ vec(K2*M) ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*vec(M) ≈ BandedMatrix(K2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M) ≈ vec(K3*M) ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*vec(M) ≈ BandedMatrix(K3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K3,size(M))*vec(M)
      @test vec(L4*M) ≈ Array(L4, size(M))*vec(M) ≈ sparse(L4,size(M))*vec(M) ≈ BandedMatrix(L4, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L4,size(M))*vec(M) ≈ vec(K4*M) ≈ Array(K4, size(M))*vec(M) ≈ sparse(K4,size(M))*vec(M) ≈ BandedMatrix(K4, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K4,size(M))*vec(M)
      @test vec(L5*M) ≈ Array(L5, size(M))*vec(M) ≈ sparse(L5,size(M))*vec(M) ≈ BandedMatrix(L5, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L5,size(M))*vec(M) ≈ vec(K5*M) ≈ Array(K5, size(M))*vec(M) ≈ sparse(K5,size(M))*vec(M) ≈ BandedMatrix(K5, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K5,size(M))*vec(M)

end

@testset "Biased Upwind : Negative Wind with offside, regular grid" begin

      M = rand(22)

      L1 = CenteredDifference(1,2,1.0,20,-1)
      L2 = CenteredDifference(1,4,1.0,20,-1)
      L3 = CenteredDifference(1,6,1.0,20,-1)
      L4 = CenteredDifference(2,3,1.0,20,-1)
      L5 = CenteredDifference(3,4,1.0,20,-1)

      K1 = UpwindDifference(1,2,1.0,20,-1,offside=1)
      K2 = UpwindDifference(1,4,1.0,20,-1,offside=2)
      K3 = UpwindDifference(1,6,1.0,20,-1,offside=3)
      K4 = UpwindDifference(2,3,1.0,20,-1,offside=2)
      K5 = UpwindDifference(3,4,1.0,20,-1,offside=3)

      @test L1*M ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*M ≈ BandedMatrix(L1, size(M))*M ≈ BandedBlockBandedMatrix(L1,size(M))*M ≈ K1*M ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*M ≈ BandedMatrix(K1, size(M))*M ≈ BandedBlockBandedMatrix(K1,size(M))*M
      @test L2*M ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*M ≈ BandedMatrix(L2, size(M))*M ≈ BandedBlockBandedMatrix(L2,size(M))*M ≈ K2*M ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*M ≈ BandedMatrix(K2, size(M))*M ≈ BandedBlockBandedMatrix(K2,size(M))*M
      @test L3*M ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*M ≈ BandedMatrix(L3, size(M))*M ≈ BandedBlockBandedMatrix(L3,size(M))*M ≈ K3*M ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*M ≈ BandedMatrix(K3, size(M))*M ≈ BandedBlockBandedMatrix(K3,size(M))*M
      @test L4*M ≈ Array(L4, size(M))*vec(M) ≈ sparse(L4,size(M))*M ≈ BandedMatrix(L4, size(M))*M ≈ BandedBlockBandedMatrix(L4,size(M))*M ≈ K4*M ≈ Array(K4, size(M))*vec(M) ≈ sparse(K4,size(M))*M ≈ BandedMatrix(K4, size(M))*M ≈ BandedBlockBandedMatrix(K4,size(M))*M
      @test L5*M ≈ Array(L5, size(M))*vec(M) ≈ sparse(L5,size(M))*M ≈ BandedMatrix(L5, size(M))*M ≈ BandedBlockBandedMatrix(L5,size(M))*M ≈ K5*M ≈ Array(K5, size(M))*vec(M) ≈ sparse(K5,size(M))*M ≈ BandedMatrix(K5, size(M))*M ≈ BandedBlockBandedMatrix(K5,size(M))*M

      M = rand(22,2,2,2,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M) ≈ vec(K1*M) ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*vec(M) ≈ BandedMatrix(K1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M) ≈ vec(K2*M) ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*vec(M) ≈ BandedMatrix(K2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M) ≈ vec(K3*M) ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*vec(M) ≈ BandedMatrix(K3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K3,size(M))*vec(M)
      @test vec(L4*M) ≈ Array(L4, size(M))*vec(M) ≈ sparse(L4,size(M))*vec(M) ≈ BandedMatrix(L4, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L4,size(M))*vec(M) ≈ vec(K4*M) ≈ Array(K4, size(M))*vec(M) ≈ sparse(K4,size(M))*vec(M) ≈ BandedMatrix(K4, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K4,size(M))*vec(M)
      @test vec(L5*M) ≈ Array(L5, size(M))*vec(M) ≈ sparse(L5,size(M))*vec(M) ≈ BandedMatrix(L5, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L5,size(M))*vec(M) ≈ vec(K5*M) ≈ Array(K5, size(M))*vec(M) ≈ sparse(K5,size(M))*vec(M) ≈ BandedMatrix(K5, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K5,size(M))*vec(M)

end

@testset "Biased Upwind : Positive Wind with offside, irregular grid" begin

      M = rand(7)
      P = PeriodicBC(Float64)*M[2:6]

      L1 = CenteredDifference(1,2,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5)
      L2 = CenteredDifference(1,4,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5)
      L3 = CenteredDifference(2,3,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5)

      K1 = UpwindDifference(1,2,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,offside=1)
      K2 = UpwindDifference(1,4,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,offside=2)
      K3 = UpwindDifference(2,3,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,offside=2)

      @test L1*M ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*M ≈ BandedMatrix(L1, size(M))*M ≈ BandedBlockBandedMatrix(L1,size(M))*M ≈ K1*M ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*M ≈ BandedMatrix(K1, size(M))*M ≈ BandedBlockBandedMatrix(K1,size(M))*M
      @test L2*M ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*M ≈ BandedMatrix(L2, size(M))*M ≈ BandedBlockBandedMatrix(L2,size(M))*M ≈ K2*M ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*M ≈ BandedMatrix(K2, size(M))*M ≈ BandedBlockBandedMatrix(K2,size(M))*M
      @test L2*P ≈ Array(L2, size(P))*vec(P) ≈ sparse(L2,size(P))*P ≈ BandedMatrix(L2, size(P))*P ≈ BandedBlockBandedMatrix(L2,size(P))*P ≈ K2*P ≈ Array(K2, size(P))*vec(P) ≈ sparse(K2,size(P))*P ≈ BandedMatrix(K2, size(P))*P ≈ BandedBlockBandedMatrix(K2,size(P))*P
      @test L3*M ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*M ≈ BandedMatrix(L3, size(M))*M ≈ BandedBlockBandedMatrix(L3,size(M))*M ≈ K3*M ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*M ≈ BandedMatrix(K3, size(M))*M ≈ BandedBlockBandedMatrix(K3,size(M))*M

      M = rand(7,2,2,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M) ≈ vec(K1*M) ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*vec(M) ≈ BandedMatrix(K1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M) ≈ vec(K2*M) ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*vec(M) ≈ BandedMatrix(K2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M) ≈ vec(K3*M) ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*vec(M) ≈ BandedMatrix(K3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K3,size(M))*vec(M)

end

@testset "Biased Upwind : Negative Wind with offside, irregular grid" begin

      M = rand(7)

      L1 = CenteredDifference(1,2,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,-1)
      L2 = CenteredDifference(1,4,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,-1)
      L3 = CenteredDifference(2,3,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,-1)

      K1 = UpwindDifference(1,2,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,-1,offside=1)
      K2 = UpwindDifference(1,4,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,-1,offside=2)
      K3 = UpwindDifference(2,3,[0.08, 0.02, 0.05, 0.04, 0.07, 0.03],5,-1,offside=2)

      @test L1*M ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*M ≈ BandedMatrix(L1, size(M))*M ≈ BandedBlockBandedMatrix(L1,size(M))*M ≈ K1*M ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*M ≈ BandedMatrix(K1, size(M))*M ≈ BandedBlockBandedMatrix(K1,size(M))*M
      @test L2*M ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*M ≈ BandedMatrix(L2, size(M))*M ≈ BandedBlockBandedMatrix(L2,size(M))*M ≈ K2*M ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*M ≈ BandedMatrix(K2, size(M))*M ≈ BandedBlockBandedMatrix(K2,size(M))*M
      @test L3*M ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*M ≈ BandedMatrix(L3, size(M))*M ≈ BandedBlockBandedMatrix(L3,size(M))*M ≈ K3*M ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*M ≈ BandedMatrix(K3, size(M))*M ≈ BandedBlockBandedMatrix(K3,size(M))*M

      M = rand(7,2,2,2)

      @test vec(L1*M) ≈ Array(L1, size(M))*vec(M) ≈ sparse(L1,size(M))*vec(M) ≈ BandedMatrix(L1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L1,size(M))*vec(M) ≈ vec(K1*M) ≈ Array(K1, size(M))*vec(M) ≈ sparse(K1,size(M))*vec(M) ≈ BandedMatrix(K1, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K1,size(M))*vec(M)
      @test vec(L2*M) ≈ Array(L2, size(M))*vec(M) ≈ sparse(L2,size(M))*vec(M) ≈ BandedMatrix(L2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L2,size(M))*vec(M) ≈ vec(K2*M) ≈ Array(K2, size(M))*vec(M) ≈ sparse(K2,size(M))*vec(M) ≈ BandedMatrix(K2, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K2,size(M))*vec(M)
      @test vec(L3*M) ≈ Array(L3, size(M))*vec(M) ≈ sparse(L3,size(M))*vec(M) ≈ BandedMatrix(L3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(L3,size(M))*vec(M) ≈ vec(K3*M) ≈ Array(K3, size(M))*vec(M) ≈ sparse(K3,size(M))*vec(M) ≈ BandedMatrix(K3, size(M))*vec(M) ≈ BandedBlockBandedMatrix(K3,size(M))*vec(M)

end

@testset "BC concretizations" begin

      M2 = rand(11)
      M = M2[2:10]
      L1 = CenteredDifference(2,2,1.0,9)
      L2 = CenteredDifference(1,2,1.0,9)

      deriv_start, deriv_end = (L2*M2)[1], (L2*M2)[end]
      params = [1.0,0.5]
      left_RBC = params[1]*M[1] - params[2]*deriv_start
      right_RBC = params[1]*M[end] + params[2]*deriv_end
      bc1 = Dirichlet0BC(Float64)
      bc2 = DirichletBC(0.0,0.0)
      bc3 = NeumannBC((deriv_start,deriv_end),1.0,1)
      bc4 = RobinBC((params[1],-params[2],left_RBC), (params[1],params[2],right_RBC),1.0,1)
      bc5 = GeneralBC([-left_RBC,params[1],-params[2]],[-right_RBC,params[1],params[2]],1.0,1)
      bc6 = PeriodicBC(Float64)

      @test L1*bc1*M ≈ L1*(Array(bc1, length(M))[1]*vec(M) + Array(bc1, length(M))[2]) ≈ L1*(sparse(bc1, length(M))[1]*vec(M) + sparse(bc1, length(M))[2]) ≈ L1*(BandedMatrix(bc1, length(M))[1]*vec(M) + BandedMatrix(bc1, length(M))[2])
      @test L1*bc2*M ≈ L1*(Array(bc2, length(M))[1]*vec(M) + Array(bc2, length(M))[2]) ≈ L1*(sparse(bc2, length(M))[1]*vec(M) + sparse(bc2, length(M))[2]) ≈ L1*(BandedMatrix(bc2, length(M))[1]*vec(M) + BandedMatrix(bc2, length(M))[2])
      @test L1*bc3*M ≈ L1*(Array(bc3, length(M))[1]*vec(M) + Array(bc3, length(M))[2]) ≈ L1*(sparse(bc3, length(M))[1]*vec(M) + sparse(bc3, length(M))[2]) ≈ L1*(BandedMatrix(bc3, length(M))[1]*vec(M) + BandedMatrix(bc3, length(M))[2])
      @test L1*bc4*M ≈ L1*(Array(bc4, length(M))[1]*vec(M) + Array(bc4, length(M))[2]) ≈ L1*(sparse(bc4, length(M))[1]*vec(M) + sparse(bc4, length(M))[2]) ≈ L1*(BandedMatrix(bc4, length(M))[1]*vec(M) + BandedMatrix(bc4, length(M))[2])
      @test L1*bc5*M ≈ L1*(Array(bc5, length(M))[1]*vec(M) + Array(bc5, length(M))[2]) ≈ L1*(sparse(bc5, length(M))[1]*vec(M) + sparse(bc5, length(M))[2]) ≈ L1*(BandedMatrix(bc5, length(M))[1]*vec(M) + BandedMatrix(bc5, length(M))[2])
      @test L1*bc6*M ≈ L1*(Array(bc6, length(M))[1]*vec(M) + Array(bc6, length(M))[2]) ≈ L1*(sparse(bc6, length(M))[1]*vec(M) + sparse(bc6, length(M))[2]) ≈ L1*(BandedMatrix(bc6, length(M))[1]*vec(M) + BandedMatrix(bc6, length(M))[2])
end


######
##FEM Stochastic Poisson Method Tests
######
using FiniteElementDiffEq

prob = prob_poisson_noisywave

sol = solve(prob,FEMDiffEqPoisson())

#plot(sol,title=["True Deterministic Solution" "Stochastic Solution"],plot_analytic=true)
#This condition should be true with really high probability
var(sol[end]) < 8e-4


include("../examples/lorenz.jl")
using LinearAlgebra
using JLD
using SharedArrays
using Distributed
function sens(s,nSteps)
    d = 3
    n_runup = 2000
    u = rand(d)
    for i = 1:n_runup
	u = next(u, s)
    end
    u_trj = step(u, s, nSteps)
    x, y, z = view(u_trj,1,:), view(u_trj,2,:), view(u_trj,3,:)

    du_trj = dstep(u_trj, s)
    ddu_trj = d2step(u_trj,s)
    vs = zeros(d)
    q = rand(d)
    q /= norm(q)

    pp = pert(u_trj, s)
    dpp = dpert(u_trj, s)
    q1 = rand(d)
    vs1 = zeros(d)

    dJds_st = 0.
    dJds_ust = 0.
    N = 12
    nSteps = nSteps + 1
    g = zeros(nSteps)
    J = copy(z)

    # Df represents the derivative of f
    # on the
    # unstable manifold.
    # Xu = a q
    Xu = zeros(d, nSteps)
    a = zeros(nSteps)
    Da = zeros(nSteps)
    Dq = zeros(d)
    Dvs = zeros(d)
    Dvs1 = zeros(d)
    rho = s[2]
    # nSteps large number.
    for i = 1:nSteps-1
	dui = du_trj[:,:,i] # for large systems, can't store Jacobian.
    	ppi = pp[:,i]
	dppi = dpp[:,:,i]
	xi, yi, zi = x[i], y[i], z[i]

    	q1 .= dui*q
    	alpha = norm(q1)
    	q1 ./= alpha
    	alpha2 = alpha*alpha

    	vs1 .= dui*vs + ppi
    	a[i+1] = dot(vs1, q1)
    	vs1 .-= a[i+1]*q1

    	d2q = reshape([dot(ddu_trj[:,j,i],
    				q) for j=1:d*d],d,d)
    	Dq = d2q*q/alpha2 + dui*Dq/alpha2
    	dalphadx = dot(alpha2*Dq, q1)
    	Dq .= Dq .- dot(Dq,q1)*q1

    	Dvs1 .= d2q*vs/alpha + dui*Dvs/alpha + dppi*q1/alpha
    	Da[i+1] = dot(vs1, Dq) + dot(Dvs1, q1)
    	Dvs1 .= Dvs1 - Da[i+1]*q1 - a[i+1]*Dq
    	Delta_Da = dot(Dvs1, q1) + dot(vs1, Dq)
    	Dvs1 .-= Delta_Da*q
    	Da[i+1] += Delta_Da

    	g[i+1] = g[i]/alpha - dalphadx/alpha2
    	dJdu = [0., 0., 1.]
    	dJds_st += dot(dJdu, vs)/nSteps
    	vs .= vs1
    	q .= q1
    	Dvs .= Dvs1
    end

    for n = 1:N
    	J_shift = J[n+1:end]
    	nJ = length(J_shift)
    	g_shift = g[2:nJ+1]
    	Da_shift = Da[2:nJ+1]
    	a_shift = a[2:nJ+1]
    	dJds_ust -= dot(J_shift,Da_shift)/nJ
    	dJds_ust -= dot(J_shift,g_shift.*a_shift)/nJ
    end

    return dJds_st + dJds_ust
end
function get_sens(s)
    nSteps = 500000
    n_exps = size(s)[2]
    dJds = zeros(n_exps)
    var_dJds = zeros(n_exps)
    n_rep = 16
    dJds_proc = SharedArray{Float64}(n_rep)
    for k=1:n_exps
    	sk = s[:,k]
    	dJds_proc .= 0.
    	t = @distributed for j=1:n_rep
    		dJds_proc[j] = sens(sk,nSteps)/n_rep
    	end
    	wait(t)
    	dJds[k] = sum(dJds_proc)
        var_dJds[k] = n_rep*sum(dJds_proc.*dJds_proc) - (dJds[k])^2
    end
    save("../data/sens/lorenz/dJds.jld", "rho",
         s[2,:], "dJds", dJds, "var_dJds", var_dJds)
end


using CompScienceMeshes, BEAST

width, height, h = 1.0, 0.5, 0.05
G1 = meshrectangle(width, height, h)
G2 = CompScienceMeshes.rotate(G1, 0.5π * x̂)
G3 = CompScienceMeshes.rotate(G1, 1.0π * x̂)

G12 = weld(G1,-G2)
G23 = weld(G2,-G3)
G31 = weld(G3,-G1)

X12 = raviartthomas(G12)
X23 = raviartthomas(G23)
X31 = raviartthomas(G31)

Y12 = buffachristiansen(G12)
Y23 = buffachristiansen(G23)
Y31 = buffachristiansen(G31)

κ = 1.0
SL = Maxwell3D.singlelayer(wavenumber=κ)
N = NCross()

X = X12 × X23 × X31
Y = Y12 × Y23 × Y31

E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ)
e = (n × E) × n

@hilbertspace j
@hilbertspace k
mtefie = @discretise(
    SL[k,j] == e[k],
    j ∈ X, k ∈ X)

# u = gmres(mtefie)
#
# offset = 1; u12 = u[offset:offset+numfunctions(X12)-1]
# offset += numfunctions(X12); u23 = u[offset:offset+numfunctions(X23)-1]
# offset += numfunctions(X23); u31 = u[offset:offset+numfunctions(X31)-1]
#
# fcr12, _ = facecurrents(u12, X12)
# fcr23, _ = facecurrents(u23, X23)
# fcr31, _ = facecurrents(u31, X31)

import PlotlyJS
using LinearAlgebra
# p1 = PlotlyJS.Plot(patch(G12, norm.(fcr12)))
# p2 = PlotlyJS.Plot(patch(G23, norm.(fcr23)))
# p3 = PlotlyJS.Plot(patch(G31, norm.(fcr31)))

# PlotlyJS.plot([p1, p2, p3])

function blkdiagm(blks...)
    m = sum(size(b,1) for b in blks)
    n = sum(size(b,2) for b in blks)
    A = zeros(eltype(first(blks)), m, n)
    o1 = 1
    o2 = 1
    for b in blks
        m1 = size(b,1)
        n1 = size(b,2)
        A[o1:o1+m1-1,o2:o2+n1-1] .= b
        o1 += m1
        o2 += n1
    end
    A
end

Sxx = BEAST.sysmatrix(mtefie)
N1 = assemble(N, X12, Y12)
N2 = assemble(N, X23, Y23)
N3 = assemble(N, X31, Y31)
Nxy = blkdiagm(N1,N2,N3)
Syy = assemble(SL, Y, Y)
iNxy = inv(Nxy)

cond(Sxx)
ex = assemble(e,X)
Q = transpose(iNxy) * Syy * iNxy * Sxx;
R = transpose(iNxy) * Syy * iNxy * ex;

u1, ch1 = solve(BEAST.GMRESSolver(Sxx),ex)
u2, ch2 = solve(BEAST.GMRESSolver(Q),R)

@show ch1.iters
@show ch2.iters
# gmres()


"""
A (abstract) `Polytope` describes the combinatorial structure used to discretize a surface from a set of given points (vertices)
linked together with edges, faces, 3D cells and so on. It does not include any location information, and therefore cannot
be represented as geometry. It is usually represented with a Hasse diagram, which provides a way to read off the structure
of a polytope as a partially ordered set.
Only when a polytope is associated with vertex data can we visually represent a possible _realization_ of this particular
polytope. Such a realization is called a geometric polytope.
"""
struct Polytope{N}
    graph::SimpleDiGraph{Int}
    nverts::Int
    start_indices::Vector{Int}
end

const Polygon = Polytope{2}
const Polyhedron = Polytope{3}

Polytope(g::SimpleDiGraph, k::Integer, nverts::Integer, start_indices::AbstractVector{<:Integer}) = Polytope{k}(g, nverts, start_indices)

"""
Build a `Polytope` from the structure provided in `indices`.
The nth argument passed as `indices` describes connectivity from the (n-1)th face to the nth face.

## Example

```julia
julia> Polytope(4,
    [(1, 2), (2, 3), (3, 1), (2, 3), (3, 4), (4, 2)], # edges (segments)
    [(1, 2, 3), (4, 5, 6)] # 2 triangle faces from edges 1, 2, 3 and 4, 5, 6 respectively
)

julia> Polytope(4,
    [(1, 2, 3, 1), (2, 3, 4, 2)], # edges (closed chains)
    [(1,), (2,)]) # faces (made from a closed chain -> PolySurface with no inner chains)
```
"""
function Polytope(connectivity::Vector{<:AbstractVector})
    faces_starts = start_indices(connectivity)
    nnodes = last(faces_starts) + length(last(connectivity))
    g = SimpleDiGraph(nnodes)
    add_faces!.(Ref(g), faces_starts, vcat(0, faces_starts[1:end-1]), connectivity)
    Polytope(g, length(connectivity), first(faces_starts), faces_starts)
end

Polytope(connectivity::AbstractVector...) = Polytope(collect(connectivity))

Base.:(==)(x::Polytope{K1}, y::Polytope{K2}) where {K1,K2} = false
Base.:(==)(x::Polytope{K}, y::Polytope{K}) where {K} = x.graph == y.graph && nverts(x) == nverts(y) && start_indices(x) == start_indices(y)

function add_faces!(g::SimpleDiGraph, src_start::Integer, dst_start::Integer, faces)
    for (src, dst_edge) ∈ enumerate(faces)
        for dst ∈ dst_edge
            add_edge!(g, src + src_start, dst + dst_start)
        end
    end
    g
end

start_indices(connectivity) = cumsum(maximum(maximum.(faces)) for faces ∈ connectivity)
start_indices(p::Polytope) = p.start_indices

nverts(p::Polytope) = p.nverts

rank(::Type{<:Polytope{N}}) where {N} = N
rank(p::Polytope) = rank(typeof(p))

function polytope_range(p::Polytope, k::Integer)
    @assert k ≠ rank(p)
    1:(start_indices(p)[k+1])
end

faces(p::Polytope, k) = k == rank(p) ? p : Polytope(p.graph[polytope_range(p, k)], k, nverts(p), start_indices(p)[1:k])

facets(p::Polytope) = faces(p, rank(p) - 1)

"""
Vertex associated with a specific position in space.
"""
struct Vertex{D,T}
    location::SVector{D,T}
end

Vertex(location::AbstractVector) = Vertex(SVector{length(location)}(location))
Vertex(coords::Number...) = Vertex(collect(coords))

Base.length(::Type{<:Vertex{D}}) where {D} = D

struct GeometricPolytope{D,P<:Polytope,V<:AbstractVector{<:Vertex{D}}}
    polytope::P
    vertices::V
    function GeometricPolytope(p::Polytope, vertices::AbstractVector{<:Vertex})
        @assert nverts(p) == length(vertices) "$(nverts(p)) vertices required for $p, found $(length(vertices))"
        new{length(eltype(vertices)),typeof(p),typeof(vertices)}(p, vertices)
    end
end

embeddim(::Type{<:GeometricPolytope{D}}) where {D} = D
embeddim(gp::GeometricPolytope) = embeddim(typeof(gp))


# FIELDS:
#==============================================================================#
"""
    abstract type Field end

Abstract type at the top of the type hierarchy for denoting fields over which vector spaces
(or more generally, linear categories) can be defined. Two common fields are `ℝ` and `ℂ`,
representing the field of real or complex numbers respectively.
"""
abstract type Field end

struct RealNumbers <: Field end
struct ComplexNumbers <: Field end

const ℝ = RealNumbers()
const ℂ = ComplexNumbers()

Base.show(io::IO, ::RealNumbers) = print(io, "ℝ")
Base.show(io::IO, ::ComplexNumbers) = print(io, "ℂ")

Base.in(::Any, ::Field) = false
Base.in(::Real, ::RealNumbers) = true
Base.in(::Number, ::ComplexNumbers) = true

Base.issubset(::Type, ::Field) = false
Base.issubset(::Type{<:Real}, ::RealNumbers) = true
Base.issubset(::Type{<:Number}, ::ComplexNumbers) = true
Base.issubset(::RealNumbers, ::RealNumbers) = true
Base.issubset(::RealNumbers, ::ComplexNumbers) = true
Base.issubset(::ComplexNumbers, ::RealNumbers) = false
Base.issubset(::ComplexNumbers, ::ComplexNumbers) = true

# VECTOR SPACES:
#==============================================================================#
"""
    abstract type VectorSpace end

Abstract type at the top of the type hierarchy for denoting vector spaces, or, more
accurately, 𝕜-linear categories. All instances of subtypes of VectorSpace will
represent objects in 𝕜-linear monoidal categories.
"""
abstract type VectorSpace end

"""
    field(V::VectorSpace) -> Field

Return the field type over which a vector space is defined.
"""
function field end
field(V::VectorSpace) = field(typeof(V))

# Basic vector space methods
#----------------------------
"""
    space(a) -> VectorSpace

Return the vector space associated to object `a`.
"""
function space end

"""
    dim(V::VectorSpace) -> Int

Return the total dimension of the vector space `V` as an Int.
"""
function dim end

"""
    dual(V::VectorSpace) -> VectorSpace

Return the dual space of `V`; also obtained via `V'`. This should satisfy
`dual(dual(V)) == V`. It is assumed that `typeof(V) == typeof(V')`.
"""
function dual end

# convenience definitions:
Base.adjoint(V::VectorSpace) = dual(V)

"""
    isdual(V::ElementarySpace) -> Bool

Return wether an ElementarySpace `V` is normal or rather a dual space. Always returns
`false` for spaces where `V == dual(V)`.
"""
function isdual end

# Hierarchy of elementary vector spaces
#---------------------------------------
"""
    abstract type ElementarySpace{𝕜} <: VectorSpace end

Elementary finite-dimensional vector space over a field `𝕜` that can be used as the index
space corresponding to the indices of a tensor. ElementarySpace is a super type for all
vector spaces (objects) that can be associated with the individual indices of a tensor,
as hinted to by its alias IndexSpace.

Every elementary vector space should respond to the methods [`conj`](@ref) and
[`dual`](@ref), returning the complex conjugate space and the dual space respectively. The
complex conjugate of the dual space is obtained as `dual(conj(V)) === conj(dual(V))`. These
different spaces should be of the same type, so that a tensor can be defined as an element
of a homogeneous tensor product of these spaces.
"""
abstract type ElementarySpace{𝕜} <: VectorSpace end
const IndexSpace = ElementarySpace

field(::Type{<:ElementarySpace{𝕜}}) where {𝕜} = 𝕜

"""
    oneunit(V::S) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type `S` that represents the trivial
one-dimensional space, i.e. the space that is isomorphic to the corresponding field. Note
that this is different from `one(V::S)`, which returns the empty product space
`ProductSpace{S,0}(())`.
"""
Base.oneunit(V::ElementarySpace) = oneunit(typeof(V))

"""
    ⊕(V1::S, V2::S, V3::S...) where {S<:ElementarySpace} -> S

Return the corresponding vector space of type `S` that represents the direct sum sum of the
spaces `V1`, `V2`, ... Note that all the individual spaces should have the same value for
[`isdual`](@ref), as otherwise the direct sum is not defined.
"""
function ⊕ end
⊕(V1, V2, V3, V4...) = ⊕(⊕(V1, V2), V3, V4...)

"""
    ⊗(V1::S, V2::S, V3::S...) where {S<:ElementarySpace} -> S

Create a [`ProductSpace{S}(V1, V2, V3...)`](@ref) representing the tensor product of several
elementary vector spaces. For convience, Julia's regular multiplication operator `*` applied
to vector spaces has the same effect.

The tensor product structure is preserved, see [`fuse`](@ref) for returning a single
elementary space of type `S` that is isomorphic to this tensor product.
"""
function ⊗ end
⊗(V1, V2, V3, V4...) = ⊗(⊗(V1, V2), V3, V4...)

# convenience definitions:
Base.:*(V1::VectorSpace, V2::VectorSpace) = ⊗(V1, V2)

"""
    fuse(V1::S, V2::S, V3::S...) where {S<:ElementarySpace} -> S
    fuse(P::ProductSpace{S}) where {S<:ElementarySpace} -> S

Return a single vector space of type `S` that is isomorphic to the fusion product of the
individual spaces `V1`, `V2`, ..., or the spaces contained in `P`.
"""
function fuse end
fuse(V::ElementarySpace) = V
fuse(V1::VectorSpace, V2::VectorSpace, V3::VectorSpace...) =
    fuse(fuse(fuse(V1), fuse(V2)), V3...)
    # calling fuse on V1 and V2 will allow these to be `ProductSpace`

"""
    flip(V::S) where {S<:ElementarySpace} -> S

Return a single vector space of type `S` that has the same value of [`isdual`](@ref) as
`dual(V)`, but yet is isomorphic to `V` rather than to `dual(V)`. The spaces `flip(V)` and
`dual(V)` only differ in the case of [`GradedSpace{I}`](@ref).
"""
function flip end

"""
    conj(V::S) where {S<:ElementarySpace} -> S

Return the conjugate space of `V`. This should satisfy `conj(conj(V)) == V`.

For `field(V)==ℝ`, `conj(V) == V`. It is assumed that `typeof(V) == typeof(conj(V))`.
"""
Base.conj(V::ElementarySpace{ℝ}) = V

"""
    abstract type InnerProductSpace{𝕜} <: ElementarySpace{𝕜} end

Abstract type for denoting vector spaces with an inner product, thus a canonical mapping from
`dual(V)` to `V` (for `𝕜 ⊆ ℝ`) or from `dual(V)` to `conj(V)` (otherwise). This mapping
is provided by the metric, but no further support for working with metrics is currently
implemented.
"""
abstract type InnerProductSpace{𝕜} <: ElementarySpace{𝕜} end

"""
    abstract type EuclideanSpace{𝕜} <: InnerProductSpace{𝕜} end

Abstract type for denoting real or complex spaces with a standard Euclidean inner product
(i.e. orthonormal basis, and the metric is identity), such that the dual space is naturally
isomorphic to the conjugate space `dual(V) == conj(V)` (in the complex case) or even to
the space itself `dual(V) == V` (in the real case), also known as the category of
finite-dimensional Hilbert spaces ``FdHilb``. In the language of categories, this subtype
represents dagger or unitary categories, and support an adjoint operation.
"""
abstract type EuclideanSpace{𝕜} <: InnerProductSpace{𝕜} end # 𝕜 should be ℝ or ℂ

dual(V::EuclideanSpace) = conj(V)
isdual(V::EuclideanSpace{ℝ}) = false

"""
    sectortype(a) -> Type{<:Sector}

Return the type of sector over which object `a` (e.g. a representation space or a tensor) is
defined. Also works in type domain.
"""
sectortype(V::VectorSpace) = sectortype(typeof(V))
sectortype(::Type{<:ElementarySpace}) = Trivial

"""
    hassector(V::VectorSpace, a::Sector) -> Bool

Return whether a vector space `V` has a subspace corresponding to sector `a` with non-zero
dimension, i.e. `dim(V, a) > 0`.
"""
hassector(V::ElementarySpace, ::Trivial) = dim(V) != 0
Base.axes(V::ElementarySpace, ::Trivial) = axes(V)

struct TrivialOrEmptyIterator
    isempty::Bool
end
Base.IteratorSize(::TrivialOrEmptyIterator) = Base.HasLength()
Base.IteratorEltype(::TrivialOrEmptyIterator) = Base.HasEltype()
Base.isempty(V::TrivialOrEmptyIterator) = V.isempty
Base.length(V::TrivialOrEmptyIterator) = isempty(V) ? 0 : 1
Base.eltype(::TrivialOrEmptyIterator) = Trivial
function Base.iterate(V::TrivialOrEmptyIterator, state = true)
    return isempty(V) == state ? nothing : (Trivial(), false)
end

"""
    sectors(V::ElementarySpace)

Return an iterator over the different sectors of `V`.
"""
sectors(V::ElementarySpace) = TrivialOrEmptyIterator(dim(V) == 0)
dim(V::ElementarySpace, ::Trivial) =
    sectortype(V) == Trivial ? dim(V) : throw(SectorMismatch())

# Composite vector spaces
#-------------------------
"""
    abstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end

Abstract type for composite spaces that are defined in terms of a number of elementary
vector spaces of a homogeneous type `S<:ElementarySpace{𝕜}`.
"""
abstract type CompositeSpace{S<:ElementarySpace} <: VectorSpace end

spacetype(S::Type{<:ElementarySpace}) = S
spacetype(V::ElementarySpace) = typeof(V) # = spacetype(typeof(V))
spacetype(::Type{<:CompositeSpace{S}}) where S = S
spacetype(V::CompositeSpace) = spacetype(typeof(V)) # = spacetype(typeof(V))

field(P::Type{<:CompositeSpace}) = field(spacetype(P))
sectortype(P::Type{<:CompositeSpace}) = sectortype(spacetype(P))

# make ElementarySpace instances behave similar to ProductSpace instances
blocksectors(V::ElementarySpace) = sectors(V)
blockdim(V::ElementarySpace, c::Sector) = dim(V, c)

# Specific realizations of ElementarySpace types
#------------------------------------------------
# spaces without internal structure
include("cartesianspace.jl")
include("complexspace.jl")
include("generalspace.jl")

# space with internal structure corresponding to the irreducible representations of
# a group, or more generally, the simple objects of a fusion category.
include("gradedspace.jl")

# Specific realizations of CompositeSpace types
#-----------------------------------------------
# a tensor product of N elementary spaces of the same type S
include("productspace.jl")
# deligne tensor product
include("deligne.jl")

# Other examples might include:
# symmetric and antisymmetric subspace of a tensor product of identical vector spaces
# ...

# HomSpace: space of morphisms
#------------------------------
include("homspace.jl")

# Partial order for vector spaces
#---------------------------------
"""
    isisomorphic(V1::VectorSpace, V2::VectorSpace)
    V1 ≅ V2

Return if `V1` and `V2` are isomorphic, meaning that there exists isomorphisms from `V1` to
`V2`, i.e. morphisms with left and right inverses.
"""
function isisomorphic(V1::VectorSpace, V2::VectorSpace)
    spacetype(V1) == spacetype(V2) || return false
    for c in union(blocksectors(V1), blocksectors(V2))
        if blockdim(V1, c) != blockdim(V2, c)
            return false
        end
    end
    return true
end

"""
    ismonomorphic(V1::VectorSpace, V2::VectorSpace)
    V1 ≾ V2

Return whether there exist monomorphisms from `V1` to `V2`, i.e. 'injective' morphisms with
left inverses.
"""
function ismonomorphic(V1::VectorSpace, V2::VectorSpace)
    spacetype(V1) == spacetype(V2) || return false
    for c in blocksectors(V1)
        if blockdim(V1, c) > blockdim(V2, c)
            return false
        end
    end
    return true
end

"""
    isepimorphic(V1::VectorSpace, V2::VectorSpace)
    V1 ≿ V2

Return whether there exist epimorphisms from `V1` to `V2`, i.e. 'surjective' morphisms with
right inverses.
"""
function isepimorphic(V1::VectorSpace, V2::VectorSpace)
    spacetype(V1) == spacetype(V2) || return false
    for c in blocksectors(V2)
        if blockdim(V1, c) < blockdim(V2, c)
            return false
        end
    end
    return true
end

# unicode alternatives
const ≅ = isisomorphic
const ≾ = ismonomorphic
const ≿ = isepimorphic

≺(V1::VectorSpace, V2::VectorSpace) = V1 ≾ V2 && !(V1 ≿ V2)
≻(V1::VectorSpace, V2::VectorSpace) = V1 ≿ V2 && !(V1 ≾ V2)

"""
    infimum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...)

Return the infimum of a number of elementary spaces, i.e. an instance `V::ElementarySpace`
such that `V ≾ V1`, `V ≾ V2`, ... and no other `W ≻ V` has this property. This requires
that all arguments have the same value of `isdual( )`, and also the return value `V` will
have the same value.
"""
infimum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...) =
    infimum(infimum(V1, V2), V3...)

"""
    supremum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...)

Return the supremum of a number of elementary spaces, i.e. an instance `V::ElementarySpace`
such that `V ≿ V1`, `V ≿ V2`, ... and no other `W ≺ V` has this property. This requires
that all arguments have the same value of `isdual( )`, and also the return value `V` will
have the same value.
"""
supremum(V1::ElementarySpace, V2::ElementarySpace, V3::ElementarySpace...) =
    supremum(supremum(V1, V2), V3...)

import Base: min, max


nums = [[parse(Int64, l) for l in line] for line in readlines("inputs/1-5/03.txt")]
lines = readlines("inputs/1-5/03.txt")

function day3(lines)
  nums = [[parse(UInt8, l) for l in line] for line in lines]
  nums_backup = copy(nums)
  oxygen_vec = 0
  for digit in 1:12
    Σ = sum(map(x -> x[digit], nums) / length(nums))
    signal = Σ >= 0.5 ? 1 : 0
    signal = Σ/length(nums) == .5 ? 1 : signal

    filter!(x -> x[digit]==signal,nums)

    if length(nums) == 1
      oxygen_vec = nums[1]
      break
    end
  end

  nums = nums_backup
  co2_vec = 0
  for digit in 1:12
    Σ = sum(map(x -> x[digit], nums) / length(nums))
    signal = Σ >= 0.5 ? 0 : 1
    signal = Σ/length(nums) == .5 ? 0 : signal

    filter!(x -> x[digit]==signal,nums)

    if length(nums) == 1
      co2_vec = nums[1]
      break
    end
  end
  return parse(Int64,join(oxygen_vec),base=2) * parse(Int64,join(co2_vec),base=2)
end

# println(day3(nums))
using BenchmarkTools
@btime day3(lines)

print(day3(lines))


function estimate_v(opt::AbstractSSOptions, m::POMDP, belief::AbstractVector, depth::Int, rng::AbstractRNG)
    if depth == maxdepth(opt)
        return 0.0
    end

    as = actions(m)
    qs = MVector{length(as), Float64}(undef)
    for (i,a) in enumerate(as)
        qs[i] = estimate_q(opt, m, belief, a, depth, rng)
    end
    return maximum(qs)
end

function estimate_q(opt::SSOptions, m::POMDP, belief::AbstractVector, a, depth::Int, rng::AbstractRNG)
    qsum = 0.0
    children = Dict{obstype(m), Vector{statetype(m)}}()

    for i in 1:width(opt)
        s = belief[mod1(i, length(belief))]
        if !isterminal(m, s)
            sp, o, r = gen(DDNOut(:sp,:o,:r), m, s, a, rng)
            qsum += r
            if haskey(children, o)
                push!(children[o], sp)
            else
                children[o] = [sp]
            end
        end
    end

    for c in values(children)
        vp = estimate_v(opt, m, c, depth+1, rng)
        qsum += discount(m)*vp*length(c)
    end
    return qsum/width(opt)
end

function valuepairs(p::SSPlanner{M}, b) where M <: POMDP
    belief = collect(rand(p.rng, b) for i in 1:p.opt.width)
    eq(a) = estimate_q(p.opt, p.m, belief, a, 0, p.rng)
    return (a=>eq(a) for a in actions(p.m))
end

function POMDPs.action(p::SSPlanner{M}, b) where M <:POMDP
    avps = collect(valuepairs(p, b))
    best = avps[1]
    for av in avps[2:end]
        if last(av) > last(best)
            best = av
        end
    end
    return first(best)
end

# for POWSS, belief is either a vector of states, in which case each is equally weighted, or a vector of state=>weight pairs
function estimate_q(opt::POWSSOptions, m::POMDP, belief::AbstractVector, a, depth::Int, rng::AbstractRNG)
    q = 0.0

    predictions = Vector{statetype(m)}(undef, opt.width)
    observations = Vector{obstype(m)}(undef, opt.width)

    allterminal = true
    wsum = 0.0
    for i in 1:width(opt)
        s, w = weighted_state(belief, i)
        if !isterminal(m, s)
            allterminal = false
            sp, o, r = gen(DDNOut(:sp,:o,:r), m, s, a, rng)
            predictions[i] = sp
            observations[i] = o
            q += w*r
        end
        wsum += w
    end
    if allterminal
        return 0.0
    end

    nextbelief = Vector{Pair{statetype(m), Float64}}(undef, opt.width)

    for i in 1:width(opt) # needs to be a separate for loop because it needs all predictions
        s, ow = weighted_state(belief, i)
        if !isterminal(m, s)
            o = observations[i]
            for j in 1:width(opt)
                s, w = weighted_state(belief, j)
                if isterminal(m, s)
                    nextbelief[j] = s=>0.0
                else
                    sp = predictions[j]
                    nextbelief[j] = sp=>w*obs_weight(m, s, a, sp, o)
                end
            end
            vp = estimate_v(opt, m, nextbelief, depth+1, rng)
            q += ow*discount(m)*vp
        end
    end
    return q/wsum
end

weighted_state(b::AbstractVector, i) = b[i]=>1/length(b)
weighted_state(b::AbstractVector{Pair{S,Float64}}, i) where {S} = b[i]


# Import Libraries
using Turing
using Turing: Variational
using Distributions
using AbstractGPs, KernelFunctions
using PyPlot
using StatsFuns
using JSON3
using Flux
using ProgressBars
import Random
import LinearAlgebra

# Define a kernel.
sekernel(alpha, rho) = alpha^2 * transform(SEKernel(), invsqrt2/rho)

function compute_f(kernel, X, eta, beta=0, jitter=0)
  K = kernelmatrix(kernel, X, obsdim=1) + LinearAlgebra.I * jitter
  return LinearAlgebra.cholesky(K).L * eta .+ beta
end

@model function GPClassify(y, X, jitter=1e-6)
    # Priors.
    alpha ~ LogNormal(0, 1)
    rho ~ LogNormal(0, 1)
    beta ~ Normal(0, 1)  # intercept.
    eta ~ filldist(Normal(0, 1), length(y))

    # Latent GP
    kernel = sekernel(alpha, rho)
    f = compute_f(sekernel(alpha, rho), X, eta, beta, jitter)

    # Sampling Distribution.
    y ~ arraydist(Bernoulli.(logistic.(f)))
end;

# To extract parameters from trained variational distribution
# (surrogate posterior).
function make_surrogate_sampler(m, q; nsamples=300)
    qsamples = rand(q, nsamples)
    _, sym2range = Variational.bijector(m; sym_to_ranges=Val(true))
    return sym -> qsamples[collect(sym2range[sym][1]), :]
end;

### Read data ###
# See data in Notebook.
data_path = joinpath(@__DIR__, "../data/gp-classify-data-N50.json")

# Load data in JSON format.
data = let
    x = open(f -> read(f, String), data_path)
    JSON3.read(x)
end

### Name the data (X, y) ###
X = [data["x1"] data["x2"]]
y = Int64.(data["y"])

### Create model.###
m =  GPClassify(Float64.(y), X);
kernel_params = [:alpha, :rho, :beta];

### Fit via ADVI ###
Random.seed!(7)
@time q = vi(m, ADVI(1, 1000))  # num_elbo_samples, num_iterations.

# Get posterior samples
surrogate_sampler = make_surrogate_sampler(m, q, nsamples=500)
advi_samples = Dict{Symbol, Any}(sym => vec(surrogate_sampler(sym))
                                 for sym in kernel_params)
advi_samples[:eta] = extract_gp(:eta);

### Fit via HMC ###
Random.seed!(0)
burn = 500
nsamples = 500
@time hmc_chain = sample(m, HMC(0.05, 20), burn + nsamples);

# Get posterior samples
hmc_samples = Dict{Symbol, Any}([
    sym => vec(group(hmc_chain, sym).value.data)[end-nsamples+1:end]
for sym in kernel_params])
hmc_samples[:eta] = Matrix(group(hmc_chain, :eta).
                           value.data[end-nsamples+1:end, :, 1]')

### Fit via NUTS ###
Random.seed!(0)
burn = 500
nsamples = 500
@time nuts_chain = sample(m, NUTS(burn, 0.8, max_depth=10), burn + nsamples)

# Get posterior samples
nuts_samples = Dict{Symbol, Any}([
    sym => vec(group(nuts_chain, sym).value.data)
for sym in kernel_params])
nuts_samples[:eta] = Matrix(group(nuts_chain, :eta).value.data[:, :, 1]')


function cluster2p(x::SVector{3,T}, cluster::Cluster{T}, particles::Particles, parindices::Vector{Int64}; p_kernel, n, eta, stretch=SVector(1.0,1.0,1.0)) where {T}
    efield = SVector(0.0,0.0,0.0)
    bfield = SVector(0.0,0.0,0.0)
    if cluster.children === nothing
        index_start = cluster.pindex_start
        index_end = index_start + cluster.npar - 1
        efield, bfield = cluster2p_brutal(x, particles, parindices, index_start, index_end)
    elseif admissible(x, cluster.bbox; eta=eta, stretch=stretch)
        xcoords = cluster.xcoords
        ycoords = cluster.ycoords
        zcoords = cluster.zcoords
        gamma_hat = cluster.gamma_hat
        mom_hat = cluster.mom_hat
        for k in 1:(n+1)
            sz = zcoords[k]
            for j in 1:(n+1)
                sy = ycoords[j]
                for i in 1:(n+1)
                    sx = xcoords[i]
                    p_hat = mom_hat[i,j,k]
                    K = kernel_relativity(x, SVector(sx,sy,sz), p_kernel)
                    efield += gamma_hat[i,j,k]*K
                    bfield += cross(p_hat, K)
                end
            end
        end
    else
        efield1, bfield1 = cluster2p(x, cluster.children[1], particles, parindices; p_kernel=p_kernel, n=n, eta=eta, stretch=stretch)
        efield2, bfield2 = cluster2p(x, cluster.children[2], particles, parindices; p_kernel=p_kernel, n=n, eta=eta, stretch=stretch)
        efield = efield1 + efield2
        bfield = bfield1 + bfield2
    end
    return efield, bfield
end

function admissible(x::SVector{3,T}, bbox::BBox{T}; stretch::SVector{3,T}=SVector(1.0,1.0,1.0), eta::T) where {T}
    bmin = bbox.bmin
    bmax = bbox.bmax
    xc = (bmin + bmax)/2.0
    r = norm(stretch .* (bmax - xc))
    R = norm(stretch .* (x - xc))
    return r/R < eta
end

function cluster2p_brutal(x::SVector{3,T}, particles::Particles, parindices::Vector{Int64}, index_start, index_end) where {T}
    efield = SVector(0.0,0.0,0.0)
    bfield = SVector(0.0,0.0,0.0)
    pos = particles.positions
    mom = particles.momentums
    for k in index_start:index_end
        xj = pos[parindices[k]]
        pj = mom[parindices[k]]
        K = kernel_relativity(x, xj, pj)
        efield += sqrt(1.0 + dot(pj, pj))*K
        bfield += cross(pj, K)
    end
    return efield, bfield
end


# Generate the Frequency Array of a String

const NUCLEOTIDE_ORDER = Dict{Char, Int64}('A' => 0, 'C' => 1, 'G' => 2, 'T' => 3)

pattern_to_number(pattern) = foldl((acc, nucleotide) -> 4 * acc + NUCLEOTIDE_ORDER[nucleotide], pattern; init=0)

function computing_frequencies(text, k)
    frequencies = zeros(Int32, 4 ^ k)
    for ix in 1:(length(text)-k+1)
        pattern = text[ix:ix+k-1]
        code = pattern_to_number(pattern)
        frequencies[code+1] += 1
    end
    frequencies
end

function main()
    dna = readline()
    k = parse(Int64, readline())
    result = computing_frequencies(dna, k)
    println(join(result, " "))
end

main()


using DifferentialEquations
using Plots

n_parasites = 100;

c = 4.0;
ux = 0.2;
ux1 = fill(0.2, n_parasites); #le ux et uy 1000 à cause de la β # une autre façon :[0.2 for x in 1:1000]
Random.seed!(1234);
uy = rand(200:1000, n_parasites)/1000;
Random.seed!(1235);
r1 = rand(Float64, n_parasites)
Random.seed!(1236);
r2 = rand(Float64, n_parasites)
Random.seed!(1237);
r3 = rand(0.0:1000, n_parasites)/1000;
bx = 1.0;
by = bx .* r1 .* (1 .- r1 .* r2);
V0 = (by .* ux) ./ (bx .* uy);
α = 1 .- V0;
βy = r1 .-(α.* by)/bx;
ey = bx .* (1 .- r3) .* (1 .- (r1 .* r2));

Y = zeros(Float64, length(ey));
Y[1] = 1.0;
X0 = 10.0;

# differential equations formula for finding densities of each pop at next time step
function fonction(u, p, t)
    x = u[1]
    y = u[2:end]
    regul = (1-(sum(u))/(p.K))
    dx = (p.bx*x + sum(p.ey.*y))*regul - p.ux*x-p.c*sum(p.βy.*y)*x
    dy = p.by .* y .* regul .- p.uy .* y .+ p.c .* p.βy .* x .* y
    return vcat(dx, dy)
end

debut = 0.0;
duree = 1000.0;
fin = debut + duree;
N = zeros(Float64, (n_parasites+1, (n_parasites-1)*Int(duree)+1));
new_U = vcat(X0, Y);
parameters = (bx = bx, βy = βy, ey = ey, c = c, K = 80.0, ux = ux, by = by, uy = uy);

# each strain introduction (1000x)
@progress "Simulation" for i in 2:length(Y)
    # initial conditions
    prob = ODEProblem(fonction, new_U, (debut,fin), parameters)
    solution = solve(prob, saveat=debut:1.0:fin)
    for t in eachindex(solution.t)
        pop = solution.u[t]
        for i in 1:n_parasites
            if (pop.<0)[i]
                pop[i] = 0
            end
        end
        N[:,Int(solution.t[t]+1)] = pop
    end

    # set conditions & new parasite for next loop
    global new_U = solution[end]
    new_y = findfirst(x -> x == 0.0, new_U)
    new_U[new_y] = 1.0

    # set limits for next loop
    global debut = fin
    #global fin = Float64((i+1).* 1000.0)
    global fin = debut + duree
end

Np = N';

#strains that survive at each time step
survival = (Np.>0.0)[:,2:end];

#mean β
survived_βy = survival.*βy';
avg_survived_βy = sum(survived_βy; dims=2)./sum(survival; dims=2);
βy_avg = avg_survived_βy;

#weighted β
avg_w_survived_βy = sum(Np[:,2:end].*βy'; dims=2)./sum(Np[:,2:end]; dims=2);
βi_avg = avg_w_survived_βy;

#mean virulence
Vir = (1 .- (by .+ ey).*ux./(bx.*uy));

survived_Vir = survival.*Vir';
avg_survived_Vir = sum(survived_Vir; dims=2)./sum(survival; dims=2);
Vir_avg = avg_survived_Vir;

#weighted virulence
avg_w_survived_Vir = sum(Np[:,2:end].*Vir'; dims=2)./sum(Np[:,2:end]; dims=2);
Vir_avg_w = avg_w_survived_Vir;

#plot(βy_avg, title = "Mean virulence and beta", label = "beta")
#plot!(Vir_avg, label = "virulence")
plot(βi_avg,c=:black, title = "Virulence and beta", label = "Beta", xlabel = "Time", ylabel = "Mean virulence & \n Mean Beta", ylims = (0,1))
plot!(Vir_avg_w,c=:blue, label = "Virulence")

# png("Figure 3/graph_3h.png")


using SubstitutionTilings
using SubstitutionTilings.Nilpotent

using Test
using LinearAlgebra

bhp = @time Dict(substitute(bhp_subst(), [a()], 3))

center_a = Dict([
    a(-2,-2,-2),
    a(0,-2,-2),
    a(2,-2,-2),
    a(-2,0,-2),
    a(0,0,-2),
    a(2,0,-2),
    a(-2,2,-2),
    a(0,2,-2),
    a(2,2,-2),
    b(-2,-2,0),
    b(0,-2,0),
    b(2,-2,0),
    b(-2,0,0),
    a(0,0,0),
    b(2,0,0),
    b(-2,2,0),
    b(0,2,0),
    b(2,2,0),
    a(-2,-2,2),
    a(0,-2,2),
    a(2,-2,2),
    a(-2,0,2),
    a(0,0,2),
    a(2,0,2),
    a(-2,2,2),
    a(0,2,2),
    a(2,2,2),
])
@time empirical_frequency(center_a, bhp)

patch = Dict([
    b(0,-2,0),
    a(0,0,0),
    b(0,2,0),
])
@time float(empirical_frequency(patch, bhp))

(collars, S) = total_collaring(bhp_subst(), center_a)
# 263 tiles

@time frequency(bhp_subst(), center_a, patch, 2)


@testset "Ideal manifold" begin
    f_0 = 1575420e3
    c₀ = 299_792_458
    λ = c₀ / f_0
    ant_pos = SVector(λ / 4 * SVector(1, 1, 0), λ / 4 * SVector(-1, 1, 0), λ / 4 * SVector(1, -1, 0), λ / 4 * SVector(-1, -1 , 0))
    manifold = @inferred IdealManifold(f_0, ant_pos)

    @test typeof(manifold) <: AbstractManifold{4}
    @test @inferred(get_steer_vec(manifold, SVector(0,0,1))) ≈ [1, 1, 1, 1]
    @test @inferred(get_steer_vec(manifold, SVector(0,1,0))) ≈ [1im, 1im, -1im, -1im]
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0))) ≈ [1im, -1im, 1im, -1im]

    @test @inferred(get_steer_vec(manifold, Spherical(1.0, 0.0, 0.0))) ≈ [1im, -1im, 1im, -1im]
    @test @inferred(get_steer_vec(manifold, Spherical(1.0, 0.0, 0.0), RotXYZ(0.0,0.0,π/2))) ≈ [1im, 1im, -1im, -1im]
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0), RotXYZ(0.0,0.0,π/2))) ≈ [1im, 1im, -1im, -1im]

    manifold = @inferred IdealManifold(f_0, λ / 4 * SVector(1, 1, 0), λ / 4 * SVector(-1, 1, 0), λ / 4 * SVector(1, -1, 0), λ / 4 * SVector(-1, -1 , 0))

    @test typeof(manifold) <: AbstractManifold{4}
    @test @inferred(get_steer_vec(manifold, SVector(0,0,1))) ≈ [1, 1, 1, 1]
    @test @inferred(get_steer_vec(manifold, SVector(0,1,0))) ≈ [1im, 1im, -1im, -1im]
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0))) ≈ [1im, -1im, 1im, -1im]

    @test @inferred(get_steer_vec(manifold, Spherical(1.0, 0.0, 0.0))) ≈ [1im, -1im, 1im, -1im]
    @test @inferred(get_steer_vec(manifold, Spherical(1.0, 0.0, 0.0), RotXYZ(0.0,0.0,π/2))) ≈ [1im, 1im, -1im, -1im]
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0), RotXYZ(0.0,0.0,π/2))) ≈ [1im, 1im, -1im, -1im]

    manifold = @inferred IdealManifold(f_0, λ / 4 * @SMatrix([1 -1 1 -1; 1 1 -1 -1; 0 0 0 0]))

    @test typeof(manifold) <: AbstractManifold{4}
    @test @inferred(get_steer_vec(manifold, SVector(0,0,1))) ≈ [1, 1, 1, 1]
    @test @inferred(get_steer_vec(manifold, SVector(0,1,0))) ≈ [1im, 1im, -1im, -1im]
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0))) ≈ [1im, -1im, 1im, -1im]

    @test @inferred(get_steer_vec(manifold, Spherical(1.0, 0.0, 0.0))) ≈ [1im, -1im, 1im, -1im]
    @test @inferred(get_steer_vec(manifold, Spherical(1.0, 0.0, 0.0), RotXYZ(0.0,0.0,π/2))) ≈ [1im, 1im, -1im, -1im]
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0), RotXYZ(0.0,0.0,π/2))) ≈ [1im, 1im, -1im, -1im]

    manifold = @inferred IdealManifold()
    @test typeof(manifold) <: AbstractManifold{1}
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0))) == 1.0
    @test @inferred(get_steer_vec(manifold, SVector(1,0,0), RotXYZ(0.0,0.0,0.0))) == 1.0

    num_ant_x = 2
    dist_x = 0.5
    manifold = IdealManifold(1, num_ant_x, dist_x; c0 = 1)
    positions = manifold.scaled_antenna_positions
    @test get_num_ants(manifold) == num_ant_x^2
    @test get_steer_vec(manifold, [0, 0, 1]) ≈ ones(num_ant_x^2)
    @test get_steer_vec(manifold, [1, 0, 0]) ≈ sign.(positions[1,:]).*im
    @test get_steer_vec(manifold, [0, 1, 0]) ≈ sign.(positions[2,:]).*im
    @test all(abs.(rem.(positions[1,:] .- π/2, 1.0π, RoundNearest)) .≈ 0)
    @test all(abs.(rem.(positions[2,:] .- π/2, 1.0π, RoundNearest)) .≈ 0)

    num_ant_y = 3
    dist_y = 1
    manifold = IdealManifold(1, num_ant_x, num_ant_y, dist_x, dist_y; c0 = 1)
    positions = manifold.scaled_antenna_positions
    @test get_num_ants(manifold) == num_ant_x * num_ant_y
    @test get_steer_vec(manifold, [0, 0, 1]) ≈ ones(num_ant_x*num_ant_y)
    @test get_steer_vec(manifold, [1, 0, 0]) ≈ sign.(positions[1,:]).*im
    @test get_steer_vec(manifold, [0, 1, 0]) ≈ ones(num_ant_x*num_ant_y)
    @test all(rem.(positions[1,:] .- π/2, 1.0π, RoundNearest) .≈ 0)
    @test all(rem.(positions[2,:], 2.0π, RoundNearest) .≈ 0)
end


module StochasticIntegrals

using DataFrames
using Dates
using Distributions: Normal, quantile
using LinearAlgebra: Symmetric, cholesky, inv, det, LowerTriangular, diag
using UnivariateFunctions
using Random
using Sobol: SobolSeq, next!
using FixedPointAcceleration

include("4_number_generators.jl")
export NumberGenerator, Mersenne, SobolGen, next!

include("1_main_functions.jl")
export ItoIntegral, get_volatility, get_variance, get_covariance, get_correlation, ItoSet, CovarianceAtDate
export get_draws, get_zero_draws, pdf, make_covariance, log_likelihood, brownians_in_use
include("2_data_conversions.jl")
export to_draws, to_dataframe, to_array
include("3_getConfidenceHypercube.jl")
export get_confidence_hypercube
include("5_conditional_distributions.jl")
export generate_conditioned_distribution

end


using ApproxFun, LinearAlgebra, Test
    import ApproxFun: testbandedblockbandedoperator, testblockbandedoperator, testraggedbelowoperator, Block

@testset "PDE" begin
    @testset "Zero Dirichlet" begin
        S = JacobiWeight(1.,1.,Jacobi(1.,1.))^2
        Δ = Laplacian(S)

        testbandedblockbandedoperator(Δ)

        u = Fun((x,y)->sin(π*x)*sin(π*y),S)
        f = -2π^2*u

        F = qr(Δ)
        ApproxFun.resizedata!(F,:,1000)
        @time v=F\f
        @test norm((u-v).coefficients)<100eps()

        F=qr(Δ)
        ApproxFun.resizedata!(F.R_cache,:,100)
        ApproxFun.resizedata!(F.R_cache,:,1000)
        @time v=F \ f
        @test norm((u-v).coefficients)<100eps()

        F=qr(Δ)
        @time v=F\f
        @test norm((u-v).coefficients)<100eps()
    end

    @testset "Rectangle Laplace/Poisson" begin
        dx = dy = ChebyshevInterval()
        d = dx × dy
        g = Fun((x,y)->exp(x)*cos(y),∂(d))

        B = Dirichlet(d)

        testblockbandedoperator(B)
        testbandedblockbandedoperator(Laplacian(d))
        testbandedblockbandedoperator(Laplacian(d)+0.0I)

        A=[Dirichlet(d);Laplacian(d)]

        @time u=A\[g,0.]
        @test u(.1,.2) ≈ real(exp(0.1+0.2im))

        A=[Dirichlet(d);Laplacian(d)+0.0I]
        @time u=A\[g,0.]

        @test u(.1,.2) ≈ real(exp(0.1+0.2im))

        ## Poisson
        f=Fun((x,y)->exp(-10(x+.2)^2-20(y-.1)^2),ChebyshevInterval()^2,500)  #default is [-1,1]^2
        d=domain(f)
        A=[Dirichlet(d);Laplacian(d)]
        @time  u=\(A,[zeros(∂(d));f];tolerance=1E-7)
        @test ≈(u(.1,.2),-0.04251891975068446;atol=1E-5)
    end

    @testset "Bilaplacian" begin
        dx = dy = ChebyshevInterval()
        d = dx × dy
        Dx = Derivative(dx); Dy = Derivative(dy)
        L = Dx^4⊗I + 2*Dx^2⊗Dy^2 + I⊗Dy^4

        testbandedblockbandedoperator(L)

        B = Dirichlet(dx) ⊗ Operator(I,dy)
        testraggedbelowoperator(B)

        A=[Dirichlet(dx) ⊗ Operator(I,dy);
                Operator(I,dx)  ⊗ Dirichlet(dy);
                Neumann(dx) ⊗ Operator(I,dy);
                Operator(I,dx) ⊗ Neumann(dy);
                 L]

        testraggedbelowoperator(A)

        @time u=\(A,[[1,1],[1,1],[0,0],[0,0],0];tolerance=1E-5)
        @test u(0.1,0.2) ≈ 1.0
    end

    @testset "Periodic x Interval" begin
        d=PeriodicSegment() × ChebyshevInterval()

        u_ex=Fun((x,y)->real(cos(x+im*y)),d)
        @test u_ex(1.0,0.1) ≈ real(cos(1.0+im*0.1)) atol=10eps()

        B=Dirichlet(Space(d))

        @test B.order == 0  # tests stupid bug
        g=Fun((x,y)->real(cos(x+im*y)),rangespace(B))  # boundary data

        @test norm((B*u_ex-g).coefficients) < 100eps()

        testbandedblockbandedoperator(Laplacian(d))

        @time u=[B;Laplacian(d)]\[g;0.]

        @test u(.1,.2) ≈ real(cos(.1+.2im))
    end

    @testset "Schrodinger" begin
        dx=0..1; dt=0.0..0.001
        C=Conversion(Chebyshev(dx)*Ultraspherical(1,dt),Ultraspherical(2,dx)*Ultraspherical(1,dt))
        testbandedblockbandedoperator(C)
        testbandedblockbandedoperator(Operator{ComplexF64}(C))

        d = dx × dt

        x,y = Fun(d)
        @test x(0.1,0.0001) ≈ 0.1
        @test y(0.1,0.0001) ≈ 0.0001

        V = x^2

        Dt=Derivative(d,[0,1]);Dx=Derivative(d,[1,0])

        ϵ = 1.
        u0 = Fun(x->exp(-100*(x-.5)^2)*exp(-1/(5*ϵ)*log(2cosh(5*(x-.5)))),dx)
        L = ϵ*Dt+(.5im*ϵ^2*Dx^2)
        testbandedblockbandedoperator(L)

        @time u = \([timedirichlet(d);L],[u0,[0.,0.],0.];tolerance=1E-5)
        @test u(0.5,0.001) ≈ 0.857215539785593+0.08694948835021317im  # empircal from ≈ schurfact
    end

    @testset "check we dispatch correctly to get fast build" begin
        S = JacobiWeight(1.,1.,Jacobi(1.,1.))^2
        Δ = Laplacian(S)
        @time S = view(Δ.op.ops[1].ops[1].op,Block.(1:40), Block.(1:40))
        @test typeof(S.parent.domaintensorizer) == ApproxFun.Trivial2DTensorizer
    end
end


using OMEinsum
A1, A2, A3 = rand(ComplexF64, 2, 3, 2), rand(ComplexF64, 3, 3, 2), rand(ComplexF64, 3, 2, 2)

#  ----------------------
# |                      |
# | i     j      k      i|
#  --[A1]---[A2]---[A3]--
#     |      |      |
#     a      b      c
st1 = vec(ein"ija,jkb,kic->abc"(A1, A2, A3))

B1,B2,B3 = conj(permutedims(A3, (2, 1, 3))), conj(permutedims(A2, (2, 1, 3))), conj(permutedims(A1, (2, 1, 3)))
st2 = vec(ein"ija,jkb,kic->cba"(B1, B2, B3))
size(B3)
size(A1)

C1,C2,C3 = conj(A1), conj(A2), conj(A3)

st3 = vec(ein"ija,jkb,kic->abc"(C1, C2, C3))

T1 = reshape(ein"ija,pqa->ipjq"(A1,C1), 4, 9)
T2 = reshape(ein"ija,pqa->ipjq"(A2,C2), 9, 9)
T3 = reshape(ein"ija,pqa->ipjq"(A3,C3), 9, 4)

ein"abab->"(reshape(T1 * T2 * T3, 2, 2, 2, 2))

vec(st1')

sum(abs2.(st1))

transpose(st2) * st1

reshape(A1, :, 2) * transpose(reshape(B1, :, 2))

T1 = reshape(A1, :, 2) * transpose(reshape(conj(A1), :, 2))
T2 = reshape(A2, :, 2) * transpose(reshape(conj(A2), :, 2))
T3 = reshape(A3, :, 2) * transpose(reshape(conj(A3), :, 2))

M1 = permutedims(reshape(T1, 2, 3, 3, 2), (1, 4, 2, 3))
M2 = permutedims(reshape(T2, 3, 3, 3, 3), (1, 4, 2, 3))
M3 = permutedims(reshape(T2, 3, 2, 2, 3), (1, 4, 2, 3))


using IHT
using SnpArrays
using DataFrames
using Distributions
using BenchmarkTools
using Random
using LinearAlgebra
using StatsFuns: logistic
using DelimitedFiles

#run n repeats of L0_reg using the same X and b, but different y
function normal_repeat(
    repeats  :: Int,
    x        :: SnpArray,
    xbm      :: SnpBitMatrix,
    true_b   :: Vector{Float64},
    noise    :: Float64,
    position :: Vector{Int} #correct position of the true model
)
    n = size(xbm, 1)
    k = size(position, 1)
    estimated_β = zeros(k, repeats)

    for i in 1:repeats
        # simulate random noise
        ϵ = rand(Normal(0, noise), n)

        #simulate phenotypes (e.g. vector y) via: y = Xb + noise
        y = xbm * true_b + ϵ

        #compute IHT result for less noisy data
        z = ones(n, 1) # intercept
        result = L0_reg(x, z, y, 1, k, debias=false)

        #store the correct position in estimated model
        estimated_β[:, i] .= result.beta[position]
    end

    return estimated_β
end

#run n repeats of L0_reg using the same X and b, but different y
function logistic_repeat(
    repeats  :: Int,
    x        :: SnpArray,
    xbm      :: SnpBitMatrix,
    true_b   :: Vector{Float64},
    noise    :: Float64,
    position :: Vector{Int} #correct position of the true model
)
    n = size(xbm, 1)
    k = size(position, 1)
    estimated_β = zeros(k, repeats)

    for i in 1:repeats
        #simulate phenotypes
        y_temp = xbm * true_b

        # Apply inverse logit link and sample from the vector of distributions
        prob = logistic.(y_temp) #inverse logit link
        y = [rand(Bernoulli(x)) for x in prob]
        y = Float64.(y)

        #compute IHT result for less noisy data
        z = ones(n, 1) # intercept
        result = L0_logistic_reg(x, z, y, 1, k, glm = "logistic", debias=true, show_info=false)

        #store the correct position in estimated model
        estimated_β[:, i] .= result.beta[position]
    end

    return estimated_β
end

#run n repeats of L0_reg using the same X and b, but different y
function poisson_repeat(
    repeats  :: Int,
    x        :: SnpArray,
    xbm      :: SnpBitMatrix,
    true_b   :: Vector{Float64},
    noise    :: Float64,
    position :: Vector{Int} #correct position of the true model
)
    n = size(xbm, 1)
    k = size(position, 1)
    estimated_β = zeros(k, repeats)

    for i in 1:repeats
        #simulate phenotypes
        y_temp = xbm * true_b

        # Simulate poisson data
        λ = exp.(y_temp) #inverse log link
        y = [rand(Poisson(x)) for x in λ]
        y = Float64.(y)

        #compute IHT result for less noisy data
        z = ones(n, 1) # intercept
        result = L0_poisson_reg(x, z, y, 1, k, glm = "poisson", debias=false, convg=false, show_info=false)

        #store the correct position in estimated model
        estimated_β[:, i] .= result.beta[position]
    end

    return estimated_β
end

function run()
    repeats = 10

    #set random seed for reproducibility
    Random.seed!(123)

    #simulat data
    n = 2000
    p = 10000
    bernoulli_rates = 0.5rand(p) #minor allele frequencies are drawn from uniform (0, 0.5)
    x = simulate_random_snparray(n, p, bernoulli_rates)
    xbm = SnpBitMatrix{Float64}(x, model=ADDITIVE_MODEL, center=true, scale=true);

    #specify true model size and noise of data
    k = 10   # number of true predictors
    s = 0.1  # noise

    #construct true model b
    true_b = zeros(p)       # model vector
    true_b[1:k] = randn(k)  # Initialize k non-zero entries in the true model
    shuffle!(true_b)        # Shuffle the entries
    correct_position = findall(x -> x != 0, true_b) # keep track of what the true entries are
    true_b[correct_position]

    #run repeats and save to file
    normal_result = normal_repeat(repeats, x, xbm, true_b, s, correct_position)
    logistic_result = logistic_repeat(repeats, x, xbm, true_b, s, correct_position)
    poisson_result = poisson_repeat(repeats, x, xbm, true_b, s, correct_position)

    writedlm("./repeats/normal_$repeats", normal_result)
    writedlm("./repeats/logistic_$repeats", logistic_result)
    writedlm("./repeats/poisson_$repeats", poisson_result)

    println("completed $repeats repeats.")
end

run()


abstract type Day7 <: AocDay end
day7 = Day7

using LightGraphs
using MetaGraphs
using GraphPlot
using StatsBase
using SimpleWeightedGraphs

function readinput(day::Type{Day7})
    rules = readlines("input/day7.txt")
    parsed_rules = parse_rule.(rules)
    parsed_rules
end

function parse_rule(rule)
    # extract container
    re = r"^(?<container>.*) bags contain"
    m = match(re, rule)

    # extract contents
    container = m["container"]
    contents = replace(rule, container * " bags contain " => "")
    contents = contents[1:end-1] # strip dot at the end
    contents = split(contents, ", ")
    contents = [replace(c, r" bags?" => "") for c in contents]
    labels = [string(container)]
    if contents != ["no other"]
        for bagtype in contents
            m = match(r"^(?<num>\d+) (?<bt>.*)$", bagtype)
            for i in 1:parse(Int, m["num"])
                push!(labels, string(m["bt"]))
            end
        end
    end

    labels
end

get_nodelabels_from_idv(idv) = [string(i) * " " * idv[2][i] for i in sort(collect(keys(idv[2])))]

function get_graph(r)
    vertices = first.(r);
    idv = [
        Dict(j => i for (i, j) in enumerate(vertices)),
        Dict(i => j for (i, j) in enumerate(vertices))
    ]
    bag_contents = Dict(i[1] => countmap(i[2:end]) for i in r)
    edges = Dict(i[1] => countmap(i[2:end]) for i in r)
    g = SimpleWeightedDiGraph(length(vertices))
    for (k, v) in bag_contents
        src_id = idv[1][k]
        for (kk, vv) in v
            dst_id = idv[1][kk]
            weight = vv
            for w in 1:weight
                add_edge!(g, src_id, dst_id, w)
            end
        end
    end
    (idv, g)
end

function solve(day::Type{Day7})
    r = readinput(day)
    idv, g = get_graph(r)
    nodelabels = get_nodelabels_from_idv(idv)
    starting_vertex = idv[1]["shiny gold"]
    ps = []
    function traverse_until_outer(g, vertex, path)
        parents = inneighbors(g, vertex)
        for p in parents
            push!(ps, p)
        end
        length(parents) == 0 && return path
        [traverse_until_outer(g, parent, [path; parent]) for parent in parents]
    end
    traverse_until_outer(g, starting_vertex, [])
    ps |> unique |> length
end

function solve_part2(day::Type{Day7})
    r = readinput(day)
    idv, g = get_graph(r)
    nodelabels = get_nodelabels_from_idv(idv)

    starting_vertex = idv[1]["shiny gold"]
    cs = []
    function traverse_in(g, vertex, path)
        @show path
        children = outneighbors(g, vertex)
        for c in children
            w = g.weights[c, vertex]
            #@show [c, w]
            push!(cs, [length(path), c, w])
        end
        length(children) == 0 && return path
        [traverse_in(g, child, [path; child]) for child in children]
    end
    traverse_in(g, starting_vertex, [starting_vertex])
    s = 0
    for (idx, (level, v, w)) in enumerate(cs)
        #@show idx, level, v, w
        s += w^level
    end
    return r, idv, g, s

end