"""
    niw_hyperparams(κ::Float32, m::AbstractArray{Float32}, ν::Float32, ψ::AbstractArray{Float32})

[Normal Inverse Wishart](https://en.wikipedia.org/wiki/Normal-inverse-Wishart_distribution)
"""
struct niw_hyperparams <: distribution_hyper_params
    κ::Float32
    m::AbstractArray{Float64}
    ν::Float32
    ψ::AbstractArray{Float64}
end

mutable struct niw_sufficient_statistics <: sufficient_statistics
    N::Float32
    points_sum::AbstractArray{Float64,1}
    S::AbstractArray{Float64,2}
end

function calc_posterior(prior::niw_hyperparams, suff_statistics::niw_sufficient_statistics)
    if suff_statistics.N == 0
        return prior
    end
    κ = prior.κ + suff_statistics.N
    ν = prior.ν + suff_statistics.N
    m = (prior.m.*prior.κ + suff_statistics.points_sum) / κ
    ψ = (prior.ν * prior.ψ + prior.κ*prior.m*prior.m' -κ*m*m'+ suff_statistics.S) / ν
    ψ = Matrix(Symmetric(ψ))
    ψ = (ψ+ψ')/2
    return niw_hyperparams(κ,m,ν,ψ)
end

function sample_distribution(hyperparams::niw_hyperparams)
    Σ = rand(Distributions.InverseWishart(hyperparams.ν, hyperparams.ν * hyperparams.ψ))
    μ = rand(Distributions.MvNormal(hyperparams.m, Σ / hyperparams.κ))
    invΣ = inv(Σ)
    chol = cholesky(Hermitian(invΣ))
    return mv_gaussian(μ, Σ, invΣ, logdet(Σ), chol.U)
end

function create_sufficient_statistics(
    hyper::niw_hyperparams,
    posterior::niw_hyperparams,
    points::AbstractArray{Float32,2},
    pts_to_group = 0,
)
    if size(points, 2) == 0
        return niw_sufficient_statistics(
            size(points, 2),
            zeros(Float32, length(hyper.m)),
            zeros(Float32, length(hyper.m), length(hyper.m)),
        )
    end
    pts = Array{Float64}(points)
    points_sum = sum(pts, dims = 2)[:]
    S = pts * pts'
    S = 0.5*(S+S')
    return niw_sufficient_statistics(size(points,2),points_sum,S)
end

function log_marginal_likelihood(
    hyper::niw_hyperparams,
    posterior_hyper::niw_hyperparams,
    suff_stats::niw_sufficient_statistics,
)
    D = length(suff_stats.points_sum)
    logpi = log(pi)
    return -suff_stats.N * D * 0.5 * logpi +
           log_multivariate_gamma(posterior_hyper.ν / 2, D) -
           log_multivariate_gamma(hyper.ν / 2, D) +
           (hyper.ν / 2) * (D * log(hyper.ν) + logdet(hyper.ψ)) -
           (posterior_hyper.ν / 2) *
           (D * log(posterior_hyper.ν) + logdet(posterior_hyper.ψ)) +
           (D / 2) * (log(hyper.κ / posterior_hyper.κ))
end

function aggregate_suff_stats(
    suff_l::niw_sufficient_statistics,
    suff_r::niw_sufficient_statistics,
)
    return niw_sufficient_statistics(
        suff_l.N + suff_r.N,
        suff_l.points_sum + suff_r.points_sum,
        suff_l.S + suff_r.S,
    )
end


# -*- coding: utf-8 -*-
using Catlab
using Catlab.Doctrines
import TikzPictures
import Catlab.Graphics: to_tikz
import SemanticModels.ModelTools.RelOlogModels: RelOlogModel, model, ⊚, ⊗
using SemanticModels.ModelTools.CategoryTheory
import SemanticModels.ModelTools.CategoryTheory: ⊔, FinSetMorph

# ### Defining a Relational Olog For SEIR

# +
@present seir(FreeBicategoryRelations) begin
    S::Ob
    E::Ob
    I::Ob
    R::Ob

    exposes::Hom(I, S)
    becomes_exposed::Hom(S, E)
    falls_ill::Hom(E, I)
    recovers_to::Hom(I, R)

    illness := compose(becomes_exposed, falls_ill)
    exposure := compose(exposes, becomes_exposed)
end

to_tikz(reduce(⊗, generators(seir, FreeBicategoryRelations.Hom)); arrowtip="Stealth", labels=true)
# -

for i in equations(seir)
    display(i[1])
    display(to_tikz(i[2]; arrowtip="Stealth", labels=true))
end


struct LinearSystem <: AbstractEnv
    A
    B
end

function State(env::LinearSystem)
    @unpack B = env
    n = size(B)[1]
    return function (x)
        @assert length(x) == n
        x
    end
end

function Params(env::LinearSystem)
    () -> nothing
end

function Dynamics!(env::LinearSystem)
    @unpack A, B = env
    @Loggable function dynamics!(dx, x, p, t; u)
        @log state = x
        @log input = u
        dx .= A*x + B*u
    end
end


function DiscreteND1D(μ::Float64, δ::Float64, x::Vector{Float64}, xstep::Float64) # x should be sorted array with constant step
    if μ < x[1] || μ > x[end]
        error("The mean value is out of input array")
    end
    #xstep = x[2] - x[1]
    CDF = Vector{Float64}()
    D = Normal(μ, δ)
    #for i=1:length(x)
    for xi in x
        #cdfi = cdf(D, x[i]+xstep/2) - cdf(D, x[i]-xstep/2)
        cdfi = cdf(D, xi+xstep/2) - cdf(D, xi-xstep/2)
        if cdfi < 5.0e-3
            cdfi = 0.0
        end
        push!(CDF, cdfi)
    end
    return normalize!(CDF, 1)
    #return map(x-> round(x/Sum; digits=4), CDF)
end

function ContinuousND2D(Means::NTuple{2, Float64}, StdDeviations::NTuple{2, Float64})
    CovarianceMat = [StdDeviations[1]^2 0.0; 0.0 StdDeviations[2]^2]
    D = MvNormal([Means...], CovarianceMat)
    return D
end

function Sample2D(rng::AbstractRNG, Means::NTuple{2, Float64}, StdDeviations::NTuple{2, Float64})
    D = ContinuousND2D(Means, StdDeviations)
    pair = [0.0, 0.0]
    rand!(rng, D, pair)
    return pair
end


using EconPDEs, Distributions

Base.@kwdef struct WachterModel{T<: Distribution}
    # consumption process parameters
    μ::Float64 = 0.025
    σ::Float64 = 0.02

    # idiosyncratic
    λbar::Float64 = 0.0355
    κλ::Float64 = 0.08
    νλ::Float64 = 0.067
    ZDistribution::T = Normal(-0.4, 0.25) #from Ian Martin higher order cumulants paper

    # utility parameters
    ρ::Float64 = 0.012
    γ::Float64 = 3.0
    ψ::Float64 = 1.1
    ϕ::Float64 = 2.6
end

function (m::WachterModel)(state::NamedTuple, y::NamedTuple)
    (; μ, σ, λbar, κλ, νλ, ZDistribution, ρ, γ, ψ, ϕ) = m
    (; λ) = state
    (; p, pλ_up, pλ_down, pλλ) = y

    # Drift and volatility of λ, p
    μλ = κλ * (λbar - λ)
    σλ = νλ * sqrt(λ)
    pλ = (μλ >= 0) ? pλ_up : pλ_down
    μp = pλ / p * μλ + 0.5 * pλλ / p * σλ^2
    σp_Zλ = pλ / p * σλ

    # Market price of risk
    κ_Zc = γ * σ
    κ_Zλ = (γ * ψ - 1) / (ψ - 1) * σp_Zλ
    η =  λ * (mgf(ZDistribution, 1) - 1 + mgf(ZDistribution, -γ) - 1 - (mgf(ZDistribution, 1 - γ) - 1))

    # Interest rate r
    r = ρ + μ / ψ - (1 + 1 / ψ) / 2 * γ * σ^2 - λ * (mgf(ZDistribution, -γ) - 1) + (1 / ψ - γ) / (1 - γ) * λ * (mgf(ZDistribution, 1 - γ) - 1) - (γ * ψ - 1) / (2 * (ψ - 1)) * σp_Zλ^2

    # Market Pricing
    pt = - p * (1 / p  + μ + μp + λ * (mgf(ZDistribution, 1) - 1) - r - κ_Zc * σ - κ_Zλ * σp_Zλ - η)

    return (; pt)
end

m = WachterModel()
λn = 30
stategrid = OrderedDict(:λ => range(0.0, 0.1, length = λn))
yend =  OrderedDict(:p => ones(λn))
result = pdesolve(m, stategrid, yend)
@assert result.residual_norm <= 1e-5

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

Solve for levered equity claim

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

function pde_levered(m, state, y, r, κ_Zc, κ_Zλ)
    μ = m.μ ; σ = m.σ ; λbar = m.λbar ; κλ = m.κλ ; νλ = m.νλ ; ZDistribution = m.ZDistribution ; ρ = m.ρ ; γ = m.γ ; ψ = m.ψ ; ϕ = m.ϕ
    λ = state.λ
    pe, peλ_up, peλ_down, peλλ = y.pe, y.peλ, y.peλλ
    μλ = κλ * (λbar - λ)
    σλ = νλ * sqrt(λ)
    peλ = (μλ >= 0) ? peλ_up : peλ_down
    μpe = peλ / pe * μλ + 0.5 * peλλ / pe * σλ^2
    σpe_Zλ = peλ / pe * σλ
    η =  λ * (mgf(ZDistribution, ϕ) - 1 + mgf(ZDistribution, -γ) - 1 - (mgf(ZDistribution, ϕ - γ) - 1))
    pet = pe * (1 / pe  + μ + μpe + λ * (mgf(ZDistribution, ϕ) - 1) - r(λ) - κ_Zc(λ) * ϕ * σ - κ_Zλ(λ) * σpe_Zλ - η)
    return (; pet)
end
#using Interpolations
#r = interpolate((state[:λ],), result[:r], Gridded(Linear()))
#κ_Zc = interpolate((state[:λ],), result[:κ_Zc], Gridded(Linear()))
#κ_Zλ = interpolate((state[:λ],), result[:κ_Zλ], Gridded(Linear()))
#y2, _, distance =  pdesolve((state, y) -> pde_levered(m, state, y, r, κ_Zc, κ_Zλ), stategrid, OrderedDict(:pe => y[:p]))


function exchange_action!(w, potential, u, v, x, ϕ, R)
    z = exp(im*ϕ)
    ρ = Density(u, applied(*, R, z*x))
    copyto!(potential, ρ)
    mul!(w, potential, v.args[2], 1/z)
end

function exchange_action(potential, u, v, ϕ, R)
    n = size(R,2)
    eye = complex(Matrix(1.0I, n, n))
    out = similar(eye)
    for j = 1:size(eye,2)
        exchange_action!(view(out, :, j),
                         potential, u, v,
                         view(eye, :, j), ϕ, R)
    end
    out
end

@testset "Poisson problem Hermiticity" begin
    N = 100
    ρ = 0.2

    R = StaggeredFiniteDifferences(N, ρ)
    r = axes(R,1)
    D = Derivative(r)
    ∇² = apply(*, R', D', D, R)

    T = ∇²/-2
    V = apply(*, R', QuasiDiagonal(-1 ./ r), R)
    H = T+V

    ee = eigen(H)

    @testset "n = $(n)" for n = 1:5
        potential = CoulombRepulsionPotential(R, 0, ComplexF64)
        u = applied(*, R, complex(ee.vectors[:,n]))
        v = applied(*, R, complex(ee.vectors[:,n]))

        @testset "ϕ = $(ϕ)" for ϕ ∈ π*(0:1/8:2)
            A = exchange_action(potential, u, v, ϕ, R)
            @test norm(A-A') < 1e-16
        end
    end
end


"""
Original publication:
A. Ghasemi, H. R. Rabiee, M. Fadaee, M. T. Manzuri,
and M. H. Rohban. Active learning from positive and
unlabeled data. In 2011 IEEE 11th International
Conference on Data Mining Workshops, pages
244–250, Dec 2011.
"""
struct ExpectedMaximumEntropyPQs <: DataBasedPQs
    p_x::Array{Float64, 1}
    bw_method::Union{String, U} where U <: Real
    ExpectedMaximumEntropyPQs(x::Array{T, 2}, bw_method="scott") where T <: Real = new(multi_kde(x, bw_method)(x), bw_method)
end

function qs_score(qs::ExpectedMaximumEntropyPQs, x::Array{T, 2}, labels::Dict{Symbol, Array{Int, 1}})::Array{Float64, 1} where T <: Real
    haskey(labels, :Lin) || throw(MissingLabelTypeException(:Lin))
    p_x_inlier = multi_kde(x[:, labels[:Lin]], qs.bw_method)(x)
    a = p_x_inlier ./ qs.p_x
    valid_score(a) = (-a^2 * log(a) + a + (a - 1)^2 * log(1 - a)) / (2 * a)
    # fallback for densities with values > 1
    return [(1 - s) <= 0 ? -Inf : valid_score(s) for s in a]
end


using Base.Test
using Klara

println("    Testing score functions for acceptance rate:")

println("      Testing logistic_rate_score...")

@test logistic_rate_score(0.25) == 1.7039056039366212
@test logistic_rate_score(0.5, 11) == 1.991859724568208

println("      Testing erf_rate_score...")

@test erf_rate_score(-0.1) == 0.6713732405408726
@test erf_rate_score(0.93, 2) == 1.9914724883356396

println("    Testing AcceptanceRateMCTuner constructors and methods...")

trate = 0.23
tuner = AcceptanceRateMCTuner(trate)

@test tuner.targetrate == trate
@test tuner.period == 100
@test tuner.verbose == false

tune = AcceptanceRateMCTune(1., 30, 100)
rate!(tune)
orate = tune.rate
tune!(tune, tuner)

@test tune.rate == logistic_rate_score(orate-tuner.targetrate)*orate

trate = 0.45
tuner = AcceptanceRateMCTuner(trate, score=erf_rate_score, period=1000, verbose=true)

@test tuner.targetrate == trate
@test tuner.period == 1000
@test tuner.verbose == true

tune = AcceptanceRateMCTune(1., 621, 1000)
rate!(tune)
orate = tune.rate
tune!(tune, tuner)

@test tune.rate == erf_rate_score(orate-tuner.targetrate)*orate


# Triangle in 3D defined by its 3 vertices.

struct Triangle_3D{T <: AbstractFloat}
    points::NTuple{3, Point_3D{T}}
end

# Constructors
# -------------------------------------------------------------------------------------------------
Triangle_3D(p₁::Point_3D{T},
            p₂::Point_3D{T},
            p₃::Point_3D{T}) where {T <: AbstractFloat} = Triangle_3D((p₁, p₂, p₃))

# Methods
# -------------------------------------------------------------------------------------------------
# Interpolation
function (tri::Triangle_3D{T})(r::R, s::S) where {T <: AbstractFloat,
                                                  R <: Real,
                                                  S <: Real}
    return (1 - T(r) - T(s))*tri.points[1] + T(r)*tri.points[2] + T(s)*tri.points[3]
end

function area(tri::Triangle_3D{T}) where {T <: AbstractFloat}
    # A = bh/2
    # Let u⃗ = |v₂ - v₁|, v⃗ = |v₃ - v₁|
    # b = |u⃗|
    # h = |sin(θ) v⃗|, where θ is the angle between u⃗ and v⃗
    # u⃗ × v⃗ = |u⃗||v⃗| sin(θ), hence
    # A = |u⃗ × v⃗|/2 = bh/2
    u⃗ = tri.points[2] - tri.points[1]
    v⃗ = tri.points[3] - tri.points[1]
    return norm(u⃗ × v⃗)/2
end

function intersect(l::LineSegment_3D{type}, tri::Triangle_3D{type}) where {type <: AbstractFloat}
    # Algorithm is from
    # Möller, T., & Trumbore, B. (1997). Fast, minimum storage ray-triangle intersection.
    p = Point_3D(type, 0)
    E₁ = tri.points[2] - tri.points[1]
    E₂ = tri.points[3] - tri.points[1]
    T = l.points[1] - tri.points[1]
    D = l.points[2] - l.points[1]
    P = D × E₂
    Q = T × E₁
    det = P ⋅ E₁
    if abs(det) < 5e-6
        return false, p
    else
        u = (P ⋅ T)/det
        v = (Q ⋅ D)/det
        t = (Q ⋅ E₂)/det
        return (u < 0) || (v < 0) || (u + v > 1) || (t < 0) || (1 < t) ? (false, p) : (true, l(t))
    end
end


function gen_rho( Ns, dr, sigma, Sf )
    Npoints = size(dr)[1]
    g1 = Array{Float64}(Npoints)
    c1 = 2*sigma^2
    cc1 = sqrt(2*pi*sigma^2)^3
    for ip=1:Npoints
        g1[ip] = exp(-dr[ip]^2/c1)/cc1
    end
    ctmp = R_to_G(Ns,g1)
    for ip=1:Npoints
        ctmp[ip] = ctmp[ip]*Sf[ip]
    end
    return real( G_to_R(Ns,ctmp) )
end


module TestForward
using CompressedSensing
using CompressedSensing: sparse_data, fr, foba, perturb, FR

using Test
using LinearAlgebra
n, m, k = 32, 48, 3
A, x, b = sparse_data(n = n, m = m, k = k) # needs to be determined
δ = 1e-2
y = perturb(b, δ)

@testset "forward regression" begin
    # noiseless
    xfr = fr(A, b, sparsity = k)
    @test xfr.nzind == x.nzind
    @test xfr.nzval ≈ x.nzval

    # with small perturbation
    xfr = fr(A, y, sparsity = k)
    @test xfr.nzind == x.nzind # support recovery
    @test isapprox(xfr.nzval, x.nzval, atol = 2δ)

    # test initialization with support set
    nzind = [1, 2, 3]
    P = FR(A, y, nzind)
    xi = P.AiQR \ y
    @test length(xi) == 3
    @test xi ≈ P.A[:, nzind] \ y
end

@testset "FoBa" begin
    xfr = foba(A, y, δ)
    @test xfr.nzind == x.nzind # support recovery
    @test isapprox(xfr.nzval, x.nzval, atol = 2δ)
end

end # TestForward


include("Graph.jl")

using LinearAlgebra
using SparseArrays

function Degree(G)
    C = zeros(G.n)
    for (ID, u, v, w) in G.E
        C[u] += w
        C[v] += w
    end
    return C
end

function spA(G)
    Is = zeros(Int, G.m*2)
    Js = zeros(Int, G.m*2)
    Vs = zeros(G.m*2)
    for (ID, u, v, w) in G.E
        Is[ID] = u
        Js[ID] = v
        Vs[ID] = w
        Is[G.m + ID] = v
        Js[G.m + ID] = u
        Vs[G.m + ID] = w
    end
    return sparse(Is, Js, Vs, G.n, G.n)
end

function alpP(G, alp)
    d = Degree(G)
    Is = zeros(Int, G.m*2)
    Js = zeros(Int, G.m*2)
    Vs = zeros(G.m*2)
    for (ID, u, v, w) in G.E
        Is[ID] = u
        Js[ID] = v
        Vs[ID] = alp * w / d[v]
        Is[G.m + ID] = v
        Js[G.m + ID] = u
        Vs[G.m + ID] = alp * w / d[u]
    end
    return sparse(Is, Js, Vs, G.n, G.n)
end

function BetweennessCentrality(G)
    g = Array{Array{Int32, 1}, 1}(undef, G.n)
    foreach(i -> g[i] = [], 1 : G.n)
    for (ID, u, v, w) in G.E
        push!(g[u], v)
        push!(g[v], u)
    end
    C = zeros(G.n)
    p = Array{Array{Int32, 1}, 1}(undef, G.n)
    d = zeros(Int32, G.n)
    S = zeros(Int32, G.n+10)
    sigma = zeros(G.n)
    Q = zeros(Int32, G.n+10)
    delta = zeros(G.n)
    for s = 1 : G.n
        foreach(i -> p[i] = [], 1 : G.n)
        top = 0
        sigma .= 0
        sigma[s] = 1.0
        d .= -1
        d[s] = 0
        front = 1
        rear = 1
        Q[1] = s

        while front <= rear
            v = Q[front]
            front += 1
            top += 1
            S[top] = v
            for w in g[v]
                if d[w] < 0
                    rear += 1
                    Q[rear] = w
                    d[w] = d[v] + 1
                end
                if d[w] == (d[v] + 1)
                    sigma[w] += sigma[v]
                    push!(p[w], v)
                end
            end
        end

        delta .= 0

        while top > 0
            w = S[top]
            top -= 1
            for v in p[w]
                delta[v] += ((sigma[v] / sigma[w]) * (1 + delta[w]))
                if w != s
                    C[w] += delta[w]
                end
            end
        end

    end

    return C
end

function ClosenessCentrality(G)
    g = Array{Array{Int32, 1}, 1}(undef, G.n)
    foreach(i -> g[i] = [], 1 : G.n)
    for (ID, u, v, w) in G.E
        push!(g[u], v)
        push!(g[v], u)
    end
    C = zeros(G.n)
    d = zeros(Int32, G.n)
    Q = zeros(Int32, G.n+10)
    for s = 1 : G.n
        d .= -1
        d[s] = 0
        front = 1
        rear = 1
        Q[1] = s

        while front <= rear
            v = Q[front]
            front += 1
            for w in g[v]
                if d[w] < 0
                    rear += 1
                    Q[rear] = w
                    d[w] = d[v] + 1
                end
            end
        end

        C[s] = sum(d)
    end

    # foreach(i -> C[i] = 1.0 / C[i], 1 : G.n)

    return C
end

function ForestDistanceClosenessCentrality(G)
    # get Forest Matrix
    IpL = zeros(G.n, G.n)
    for (ID, u, v, w) in G.E
        IpL[u, u] += w
        IpL[v, v] += w
        IpL[u, v] -= w
        IpL[v, u] -= w
    end
    foreach(i -> IpL[i, i] += 1, 1 : G.n)
    W = inv(IpL)

    # calculate FDC
    trace = tr(W)
    C = zeros(G.n)
    # foreach(i -> C[i] = 1.0 / (G.n * W[i, i] + trace - 2), 1 : G.n)
    foreach(i -> C[i] = W[i, i], 1 : G.n)

    return C
end

function InformationCentrality(G)
    # get pseudo inverse of L
    L = zeros(G.n, G.n)
    for (ID, u, v, w) in G.E
        L[u, u] += w
        L[v, v] += w
        L[u, v] -= w
        L[v, u] -= w
    end
    L .+= (1 / G.n)
    Lp = inv(L)
    Lp .-= (1 / G.n)

    # calculate Information Centrality
    trace = tr(Lp)
    C = zeros(G.n)
    foreach(i -> C[i] = G.n / (G.n * Lp[i, i] + trace), 1 : G.n)

    return C
end

function PageRank(G; alpha = 0.85)
    aP = alpP(G, alpha)
    C = zeros(G.n)
    C[1] = 1.0
    adC = ((1.0 - alpha) / G.n) * ones(G.n)

    while true
        pC = copy(C)
        C = aP * C + adC
        if norm(C - pC) < 1e-12
            break
        end
    end

    return C
end

function EigenvectorCentrality(G)
    C = zeros(G.n)
    C[1] = 1.0
    A = spA(G)

    while true
        pC = copy(C)
        C = A * C
        C ./= C[argmax(C)]
        if norm(C - pC) < 1e-9
            break
        end
    end

    return C
end


### Fetch required packages and reaction networks ###
using DiffEqBase, Catalyst, Random, Test

using StableRNGs
rng = StableRNG(12345)

### Tests various cutom made functions ###
new_hill(x, v, k, n) = v*x^n/(k^n+x^n)
new_poly(x,p1,p2) = .5*p1*x^2
new_exp(x,p) = exp(-p*x)

custom_function_network_1 = @reaction_network begin
    hill(X,v1,K1,2), X + Y --> Z1
    mm(X,v2,K2), X + Y --> Z2
    p1*X^2+p2, X + Y --> Z3
    exp(-p3*Y), X + Y --> Z4
    hillR(X,v3,K3,2), X + Y --> Z5
    mmR(X,v4,K4), X + Y --> Z6
end v1 K1 v2 K2 p1 p2 p3 v3 K3 v4 K4

custom_function_network_2 = @reaction_network begin
    new_hill(X,v1,K1,2), X + Y --> Z1
    v2*X/(X+K2), X + Y --> Z2
    2*new_poly(X,p1,p2)+p2, X + Y --> Z3
    new_exp(Y,p3), X + Y --> Z4
    v3*(K3^2)/(K3^2+X^2), X + Y --> Z5
    v4*K4/(X+K4), X + Y --> Z6
end v1 K1 v2 K2 p1 p2 p3 v3 K3 v4 K4

f1 = ODEFunction(convert(ODESystem,custom_function_network_1),jac=true)
f2 = ODEFunction(convert(ODESystem,custom_function_network_2),jac=true)
g1 = SDEFunction(convert(SDESystem,custom_function_network_1))
g2 = SDEFunction(convert(SDESystem,custom_function_network_2))
for factor in [1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3]
    u0 = factor*rand(rng,length(custom_function_network_1.states))
    p = factor*rand(rng,length(custom_function_network_2.ps))
    t = rand(rng)
    @test all(abs.(f1(u0,p,t) .- f2(u0,p,t)) .< 100*eps())
    @test all(abs.(f1.jac(u0,p,t) .- f2.jac(u0,p,t)) .< 100*eps())
    @test all(abs.(g1(u0,p,t) .- g2(u0,p,t)) .< 100*eps())
end


function Base.:+(A::T,B::T) where T <: CoordinateVector
    A.coord == B.coord || throw(ArgumentError("Vectors must have the same coordinates"))
    return T(A.data .+ B.data, A.coord, A.J, A.g, A.h)
end

function Base.:+(A::T,B::S) where {T<:CoordinateVector, S<:CoordinateVector}
    return A + convert(B)
end

function Base.:+(a::T,x::S) where {T<:Real, S<:CoordinateVector}
    return S(x.data+a,x.coord,x.J,x.g,x.h)
end

function Base.:+(x::T,a::S) where {T<:CoordinateVector, S<:Real}
    return T(x.data + a,x.coord,x.J,x.g,x.h)
end

function Base.:-(A::T,B::T) where {T<:CoordinateVector}
    A.coord == B.coord || throw(ArgumentError("Vectors must have the same coordinates"))
    return T(A.data .- B.data, A.coord, A.J, A.g, A.h)
end

function Base.:-(A::T,B::S) where {T<:CoordinateVector,S<:CoordinateVector}
    return A - convert(B)
end

function Base.:-(a::T,x::S) where {T<:Real,S<:CoordinateVector}
    return S(a - x.data,x.coord,x.J,x.g,x.h)
end

function Base.:-(x::T,a::S) where {T<:CoordinateVector,S<:Real}
    return T(x.data - a,x.coord,x.J,x.g,x.h)
end

function Base.:*(a::T,x::S) where {T<:Real,S<:CoordinateVector}
    return S(a*x.data,x.coord,x.J,x.g,x.h)
end

function Base.:*(x::T,a::S) where {T<:CoordinateVector,S<:Real}
    return T(a*x.data,x.coord,x.J,x.g,x.h)
end

function Base.:/(a::T,x::S) where {T<:Real,S<:CoordinateVector}
    return S(a./ x.data,x.coord,x.J,x.g,x.h)
end

function Base.:/(x::T,a::S) where {T<:CoordinateVector,S<:Real}
    return T(x.data/a,x.coord,x.J,x.g,x.h)
end

function dot(A::T,B::T) where {T<:CoordinateVector}
    A.coord === B.coord || throw(ArgumentError("Vectors must have the same coordinates"))
    dp = sum(A.data .* (A.g*B.data))
    return dp
end

function dot(A::T,B::S) where {T<:CoordinateVector,S<:CoordinateVector}
    A.coord === B.coord || throw(ArgumentError("Vectors must have the same coordinates"))
    dp = sum(A.data .* B.data)
    return dp
end

norm(x::T) where {T<:CoordinateVector} = sqrt(dot(x,x))

function cross(A::T,B::T) where {T<:CoordinateVector}
    A.coord === B.coord || throw(ArgumentError("Vectors must have the same coordinates"))
    length(A) == 3 || throw(DomainError("Cross product only defined for 3-vectors"))
    AxB = zeros(eltype(A),3)
    detJ = det(A.J)

    for i=1:3
        for j=1:3
            v = detJ*A[i]*B[j]
            for k=1:3
                AxB[k] = AxB[k] + levicivita([i,j,k])*v
            end
        end
    end

    AxB = inv(A.g)*AxB
    return T(AxB,A.coord,A.J,A.g,A.h)
end

function cross(A::T,B::S) where {T<:CoordinateVector,S<:CoordinateVector}
    return cross(A,convert(B))
end


# Script to create leontief inverse matrix, exports and value added

using DataFrames, XLSX, LinearAlgebra

# ---------- Notes -------------------------------------------------------------------------------------------------------

# Problems with value added. See comment for v_add_share.

# ---------- Load data -------------------------------------------------------------------------------------------------------

# Subsample of EU27+GB (code also works for original dataset too only need to adjust the row bounds from 6/5 to 7/6 and N = 43+1)
path = "clean/raw_wide_EU.xlsx"

raw = DataFrame(XLSX.readxlsx(path)["Sheet1"][:], :auto)

# ---------- Data wrangling -------------------------------------------------------------------------------------------------------

N = 27 + 1 # number of countries, EU27+GB
S = 56 # number of sectors

# cut out the additional variables in beginning/end (to have square matrix) (N*S×N*S rows/columns)
raw_D = raw[6:N*S+5, 5:N*S+4] # intermediary demand data
# cut sectoral input/output so only final demand variables remain (S*N rows and 5*N columns - 5 different measures of final demand:
# households/government/NGOs/inventory/capital_formation)
raw_FD = raw[6:N*S+5, 5+N*S:4+N*S+5*N] # final demand data

# convert into matrix (cannot make matrix calculations with DataFrames)
D = Matrix(convert.(Float64, raw_D)) # N*S×N*S
FD = Matrix(convert.(Float64, raw_FD)) # N*S×N*5

# ---------- Variable creation -------------------------------------------------------------------------------------------------------

# gross total inputs used by sector k and country i: sum rows of column k,i
inputs = [sum(D[1:end, i]) for i in 1:size(D, 2)] # N*S×1

# gross total output from sector k and country i: sum columns of row k,i
outputs = [sum(D[i, 1:end]) for i in 1:size(D, 1)] # N*S×1, without final demand

# gross final demand from sector k and country i per country: sum five final demand columns of row k, i
f_demand = [sum(FD[i, j:j+4]) for i in 1:size(FD, 1), j in 1:5:size(FD, 2)] # N*S×N, final demand

# gross output from sector k and country i per country = exports per country (without final demand => intermediate demand): sum all country columns of row k, i
i_demand = [sum(D[i, j:j+S-1]) for i in 1:size(D, 1), j in 1:S:size(D, 2)] # N*S×N, intermediate demand per country

# gross exports from sector k and country i per country: sum intermediate and final demand
exports = i_demand + f_demand #N*S×N, exports per country

# gross final demand from sector k and country i
final = [sum(f_demand[i, :]) for i in 1:size(f_demand, 1)] # N*S×1

# gross total demand from sector k and country i
outputs_total = outputs .+ final

# value added of sector k and country i (total): (output-input)/output
v_add_share = (outputs_total .- inputs) ./ outputs_total # N*S×1, with final demand

all_value_added = hcat(raw[6:end,[:x4, :x1]], v_add_share, makeunique=true)
rename!(all_value_added, [:iso3, :WIOD, :value_added])
transform!(all_value_added, [:iso3, :WIOD] .=> ByRow(string) .=> [:iso3, :WIOD], renamecols=false) # change type to string to use XLSX.writetable()

XLSX.writetable("clean/all_value_added.xlsx", all_value_added, overwrite=true)

# ---------- Leontief inverse matrix -------------------------------------------------------------------------------------------------------

# technical coefficients: "a dollar's value of inputs from country i into sector k per a dollar's worth of output of country j in sector s"
A = similar(D)

# divide by entire output of country-sector pair
for i in 1:size(A, 1)
    for j in 1:size(A, 2)
        A[i, j] = D[i, j] / outputs_total[j]
    end
end

replace!(A, NaN => 0.0) # remove all NaN, mostly sectors U, T

# Leontief inverse matrix
# Leontief coeffiecients measure the total of dollars worth of country j, sector k goods required to meet 1 dollar woth of country i, sector z 's final demand
L = inv(I - A)


function solve()
    ns = [parse(Int, x) for x in readline()]
    l = length(ns)
    m = -1
    for i=1:2^l-1
        s = 0
        for j=0:l-1
            if i & (1<<j) != 0
                s += ns[j+1]
            end
        end
        if s % 3 == 0
            m = max(m, count_ones(i))
        end
    end
    m == -1 ? -1 : l-m
end

println(solve())


@testset "utils.jl" begin
    rng = MersenneTwister(42)
    m = randn(rng)
    σ = rand(rng)
    d = Normal(m, σ)
    @test AGPL.second_moment(d) ≈ abs2(m) + abs2(σ)
    c = AGPL.second_moment(d)
    @test AGPL.approx_expected_logistic(m, c) ≈ exp(m / 2) / cosh(c / 2) / 2 atol = 1e-5
    for T in [Float64, Float32]
        bigm = T(1000)
        c = bigm + abs(randn(rng, T))
        @test AGPL.approx_expected_logistic(bigm, c) ≈ logistic(bigm)
    end
end


@testset "Exp Atoms: $solver" for solver in solvers
    if can_solve_exp(solver)
        @testset "exp atom" begin
            y = Variable()
            p = Problem{BigFloat}(:minimize, exp(y), y>=0)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ 1 atol=TOL
            @test evaluate(exp(y)) ≈ 1 atol=TOL

            y = Variable()
            p = Problem{BigFloat}(:minimize, exp(y), y>=1)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ exp(1) atol=TOL
            @test evaluate(exp(y)) ≈ exp(1) atol=TOL

            y = Variable(5)
            p = Problem{BigFloat}(:minimize, sum(exp(y)), y>=0)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ 5 atol=TOL
            @test evaluate(sum(exp(y))) ≈ 5 atol=TOL

            y = Variable(5)
            p = Problem{BigFloat}(:minimize, sum(exp(y)), y>=0)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ 5 atol=TOL
        end

        @testset "log atom" begin
            y = Variable()
            p = Problem{BigFloat}(:maximize, log(y), y<=1)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ 0 atol=TOL

            y = Variable()
            p = Problem{BigFloat}(:maximize, log(y), y<=2)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ log(2) atol=TOL

            y = Variable()
            p = Problem{BigFloat}(:maximize, log(y), [y<=2, exp(y)<=10])
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ log(2) atol=TOL
        end

        @testset "log sum exp atom" begin
            y = Variable(5)
            p = Problem{BigFloat}(:minimize, logsumexp(y), y>=1)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ log(exp(1) * 5) atol=TOL
        end

        @testset "logistic loss atom" begin
            y = Variable(5)
            p = Problem{BigFloat}(:minimize, logisticloss(y), y>=1)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ log(exp(1) + 1) * 5 atol=TOL
        end

        @testset "entropy atom" begin
            y = Variable(5, Positive())
            p = Problem{BigFloat}(:maximize, entropy(y), sum(y)<=1)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ -(log(1 / 5)) atol=TOL
        end

        @testset "relative entropy atom" begin
            x = Variable(1)
            y = Variable(1)
            # x log (x/y)
            p = Problem{BigFloat}(:minimize, relative_entropy(x,y), y==1, x >= 2)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ 2 * log(2) atol=TOL
        end

        @testset "log perspective atom" begin
            x = Variable(1)
            y = Variable(1)
            # y log (x/y)
            p = Problem{BigFloat}(:maximize, log_perspective(x,y), y==5, x <= 10)
            @test vexity(p) == ConvexVexity()
            solve!(p, solver)
            @test p.optval ≈ 5 * log(2) atol=TOL
        end
    end
end


function single_stance!(du, u, p, t)
    z = u
    walker = p
    θ₁ = z[1]
    θ₂ = z[3]
    ω₁ = z[2]
    ω₂ = z[4]
    M = walker.M; m = walker.m; I=walker.I; l = walker.l; c = walker.c;
    g = walker.g; γ = walker.γ

    A11 = 2*I + M*l^2 + 2*c^2*m + 2*l^2*m - 2*c*l*m - 2*c*l*m*cos(θ₂);
    A12 = I + c^2*m - c*l*m*cos(θ₂);
    A21 = I + c^2*m - c*l*m*cos(θ₂);
    A22 = I + c^2*m;
    A_ss = [A11 A12; A21 A22];

    b1 = c*g*m*sin(γ - θ₁) - M*g*l*sin(γ - θ₁) - c*g*m*sin(θ₁ - γ + θ₂) - 2*g*l*m*sin(γ - θ₁) - c*l*m*ω₂^2*sin(θ₂) - 2*c*l*m*ω₁*ω₂*sin(θ₂);
    b2 = -c*m*(g*sin(θ₁ - γ + θ₂) - l*ω₁^2*sin(θ₂));
    b_ss = [b1; b2];

    if walker.use_controller
        (U, θ₂ᵣ, θ₂̇ᵣ, θ₂̈ᵣ) = controller(t, z, walker)
    else
        U = 0.0
    end

    B = [0; 1]
    α = A_ss\(b_ss+B*U);
    du[1] = ω₁
    du[2] = α[1]
    du[3] = ω₂
    du[4] = α[2]

end

function footstrike(t, z, walker)
    z=z[1]
    θ₁_n = z[1]
    θ₂_n = z[3]
    ω₁_n = z[2]
    ω₂_n = z[4]

    θ₁ = θ₁_n + θ₂_n
    θ₂ = -θ₂_n

    M = walker.M; m = walker.m; I = walker.I
    l = walker.l; c=walker.c

    J11 = 1;
    J12 = 0;
    J13 = l*(cos(θ₁_n + θ₂_n) - cos(θ₁_n));
    J14 = l*cos(θ₁_n + θ₂_n);
    J21 = 0;
    J22 = 1;
    J23 = l*(sin(θ₁_n + θ₂_n) - sin(θ₁_n));
    J24 = l*sin(θ₁_n + θ₂_n);
    J = [J11 J12 J13 J14; J21 J22 J23 J24];

    A11 = M + 2*m;
    A12 = 0;
    A13 = (m*(2*c*cos(θ₁_n + θ₂_n) - 2*l*cos(θ₁_n)))/2 + m*cos(θ₁_n)*(c - l) - M*l*cos(θ₁_n);
    A14 = c*m*cos(θ₁_n + θ₂_n);
    A21 = 0;
    A22 = M + 2*m;
    A23 = (m*(2*c*sin(θ₁_n + θ₂_n) - 2*l*sin(θ₁_n)))/2 - M*l*sin(θ₁_n) + m*sin(θ₁_n)*(c - l);
    A24 = c*m*sin(θ₁_n + θ₂_n);
    A31 = (m*(2*c*cos(θ₁_n + θ₂_n) - 2*l*cos(θ₁_n)))/2 + m*cos(θ₁_n)*(c - l) - M*l*cos(θ₁_n);
    A32 = (m*(2*c*sin(θ₁_n + θ₂_n) - 2*l*sin(θ₁_n)))/2 - M*l*sin(θ₁_n) + m*sin(θ₁_n)*(c - l);
    A33 = 2*I + M*l^2 + 2*c^2*m + 2*l^2*m - 2*c*l*m - 2*c*l*m*cos(θ₂_n);
    A34 = I + c^2*m - c*l*m*cos(θ₂_n);
    A41 = c*m*cos(θ₁_n + θ₂_n);
    A42 = c*m*sin(θ₁_n + θ₂_n);
    A43 = I + c^2*m - c*l*m*cos(θ₂_n);
    A44 = I + c^2*m;
    A_n_hs = [A11 A12 A13 A14; A21 A22 A23 A24; A31 A32 A33 A34; A41 A42 A43 A44];

    X_n_hs = [0 0 ω₁_n ω₂_n]';
    b_hs = [A_n_hs*X_n_hs; 0; 0];
    A_hs = [A_n_hs -J' ; J zeros((2,2))];
    X_hs = A_hs\b_hs;
    ω = zeros(2)
    ω[1] = X_hs[3]+X_hs[4]; ω[2] = -X_hs[4];

    zplus = [θ₁ ω[1] θ₂ ω[2]]

    return zplus
end


using FinancialMonteCarlo, ReverseDiff;
@show "Black Scholes Model"
S0 = 100.0;
K = 100.0;
r = 0.02;
T = 1.0;
d = 0.01;
D = 90.0;

Nsim = 10000;
Nstep = 30;
sigma = 0.2
mc = MonteCarloConfiguration(Nsim, Nstep);
mc1 = MonteCarloConfiguration(Nsim, Nstep, FinancialMonteCarlo.AntitheticMC());
toll = 0.8

rfCurve = ZeroRate(r);

Model = BlackScholesProcess(sigma, Underlying(S0, d));

x = Float64[sigma, S0, r, d, T]

@show ReverseDiff.gradient(x -> pricer(BlackScholesProcess(x[1]), ZeroRate(x[2], x[3], x[4]), mc, Forward(x[5])), x)
@show ReverseDiff.gradient(x -> pricer(BlackScholesProcess(x[1]), ZeroRate(x[2], x[3], x[4]), mc, EuropeanOption(x[5], K)), x)
@show ReverseDiff.gradient(x -> pricer(BlackScholesProcess(x[1]), ZeroRate(x[2], x[3], x[4]), mc, AmericanOption(x[5], K)), x)
@show ReverseDiff.gradient(x -> pricer(BlackScholesProcess(x[1]), ZeroRate(x[2], x[3], x[4]), mc, BarrierOptionDownOut(x[5], K, D)), x)
@show ReverseDiff.gradient(x -> pricer(BlackScholesProcess(x[1]), ZeroRate(x[2], x[3], x[4]), mc, AsianFloatingStrikeOption(x[5])), x)
@show ReverseDiff.gradient(x -> pricer(BlackScholesProcess(x[1]), ZeroRate(x[2], x[3], x[4]), mc, AsianFixedStrikeOption(x[5], K)), x)


function network_model(
    inputs::SimulationInputs,
    I_balance::AbstractArray{T},
    voltages::AbstractArray{T},
) where {T <: ACCEPTED_REAL_TYPES}
    # This operation might need improvement, when the total shunts aren't added the
    # function is not allocating
    ybus = get_ybus(inputs) .+ get_total_shunts(inputs)
    bus_count = get_bus_count(inputs)
    rows_vals = SparseArrays.rowvals(ybus)
    ybus_vals = SparseArrays.nonzeros(ybus)
    for n in UnitRange{Int}(1, bus_count)
        # The indexing works this way because of the shape of ybus_rectangular and because of the
        # ordering of
        for i in SparseArrays.nzrange(ybus, n)
            I_balance[n] -= ybus_vals[i] * voltages[rows_vals[i]]
        end
        for i in SparseArrays.nzrange(ybus, n + bus_count)
            I_balance[n + bus_count] -= ybus_vals[i] * voltages[rows_vals[i]]
        end
    end
    return I_balance
end

#=
A = sparse(I,J,V)
rows = rowvals(A)
vals = nonzeros(A)
m, n = size(A)
for j = 1:n
   for i in nzrange(A, j)
      row = rows[i]
      val = vals[i]
      # perform sparse wizardry...
   end
end
=#


using ReachabilityBenchmarks, MathematicalSystems, Reachability, Plots

include(@relpath "projectile_model.jl")
include(@relpath "projectile_specifications.jl")

S = projectile_model()
X0, options = projectile_specification()
options = Options(options)

# initial value problem
problem = InitialValueProblem(S, X0)

# partition with 2D blocks
partition = [2*i-1:2*i for i in 1:2]

# reachability algorithms
algorithm_reach = BFFPSV18(:δ => 0.5, :vars => [1, 3], :partition => partition)

# compute flowpipe
options[:mode] = "reach"
solution = solve(problem, options; op=algorithm_reach)

# project flowpipe to x₁ and x₃
solution.options[:plot_vars] = [1, 3]
solution_proj = project(solution)

# plot projection
plot(solution_proj)
savefig("projectile")


@doc raw"""
Sellmeier formula:
```math
n^2 - 1 = c_1 + \sum \limits_{i=1}^{N} \frac{c_{2i} \lambda^2}{\lambda^2 - c_{2i+1}^2}
```
"""
struct Sellmeier{N} <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{N,Float64}
end

function (f::Sellmeier{N})(λ) where {N}
    c = f.coeffs
    rhs = c[1]
    for i = 2:2:N
        rhs += c[i] * λ^2 / (λ^2 - c[i + 1]^2)
    end
    return sqrt(rhs + 1)
end

@doc raw"""
Sellmeier-2 formula:
```math
n^2 - 1 = c_1 + \sum \limits_{i=1}^{N} \frac{c_{2i} \lambda^2}{\lambda^2 - c_{2i+1}}
```
"""
struct Sellmeier2{N} <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{N,Float64}
end

function (f::Sellmeier2{N})(λ) where {N}
    c = f.coeffs
    rhs = c[1]
    for i = 2:2:N
        rhs += c[i] * λ^2 / (λ^2 - c[i + 1])
    end
    return sqrt(rhs + 1)
end

@doc raw"""
Polynomial formula:
```math
n^2 = c_1 + \sum \limits_{i=1}^{N}  c_{2i} \lambda^{c_{2i+1}}
```
"""
struct Polynomial{N} <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{N,Float64}
end

function (f::Polynomial{N})(λ) where {N}
    c = f.coeffs
    rhs = c[1]
    for i = 2:2:N
        rhs += c[i] * λ^c[i + 1]
    end
    return sqrt(rhs)
end

@doc raw"""
RefractiveIndex.INFO formula:
```math
n^2 = c_1
    + \frac{c_{2} \lambda^{c_{3}}}{\lambda^2 - c_{4}^{c_{5}}}
    + \frac{c_{6} \lambda^{c_{7}}}{\lambda^2 - c_{8}^{c_{9}}}
    + \sum \limits_{i=5}^{N} c_{2i} \lambda^{c_{2i+1}}
```
"""
struct RIInfo{N} <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{N,Float64}
end

function (f::RIInfo{N})(λ) where {N}
    c = f.coeffs
    rhs = c[1]
    for i = 2:4:min(N, 9)
        rhs += (c[i] * λ^c[i + 1]) / (λ^2 - c[i + 2]^c[i + 3])
    end
    for i = 10:2:N
        rhs += c[i] * λ^c[i + 1]
    end
    return sqrt(rhs)
end

@doc raw"""
Cauchy formula:
```math
n = c_1 + \sum \limits_{i=5}^{N} c_{2i} \lambda^{c_{2i+1}}
```
"""
struct Cauchy{N} <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{N,Float64}
end

function (f::Cauchy{N})(λ) where {N}
    c = f.coeffs
    rhs = c[1]
    for i = 2:2:N
        rhs += c[i] * λ^c[i + 1]
    end
    return rhs
end

@doc raw"""
Gases formula:
```math
n - 1 = c_1
    + \sum \limits_{i=1}^{N} \frac{c_{2i}}{c_{2i+1} - \lambda^{-2} }
```
"""
struct Gases{N} <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{N,Float64}
end

function (f::Gases{N})(λ) where {N}
    c = f.coeffs
    rhs = c[1]
    for i = 2:2:N
        rhs += c[i] / (c[i + 1] - 1 / λ^2)
    end
    return rhs + 1
end

@doc raw"""
Herzberger formula:
```math
n = c_1
    + \frac{c_{2}}{\lambda^2 - 0.028}
    + c_{3} \left( \frac{1}{\lambda^2 - 0.028} \right)^2
    + \sum \limits_{i=1}^{N} c_{i+3} \lambda^{2i}
```
"""
struct Herzberger{N} <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{N,Float64}
end

function (f::Herzberger{N})(λ) where {N}
    c = f.coeffs
    rhs = c[1]
    rhs += c[2] / (λ^2 - 0.028)
    rhs += c[3] * (1 / (λ^2 - 0.028))^2
    for i = 4:N
        pow = 2 * (i - 3)
        rhs += c[i] * λ^pow
    end
    return rhs
end

@doc raw"""
Retro formula:
```math
\frac{n^2 - 1}{n^2 +2} = c_1
    + \frac{c_{2} \lambda^2}{\lambda^2 - c_{3}}
    +  c_{4} \lambda^2
```
"""
struct Retro <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{4,Float64}
end

function (f::Retro)(λ)
    c = f.coeffs
    rhs = c[1] + c[2] * λ^2 / (λ^2 - c[3]) + c[4] * λ^2
    return sqrt((-2rhs - 1) / (rhs - 1))
end

@doc raw"""
Exotic formula:
```math
n^2 = c_1
    + \frac{c_{2}}{\lambda^2 - c_{3}}
    + \frac{c_{4}(\lambda - c_{5})}{(\lambda - c_{5})^2 + c_{6}}
```
"""
struct Exotic <: Formula
    λrange::NTuple{2,Float64}
    coeffs::NTuple{6,Float64}
end

function (f::Exotic)(λ)
    c = f.coeffs
    rhs = c[1] + c[2] / (λ^2 - c[3]) + c[4] * (λ - c[5]) / ((λ - c[5])^2 + c[6])
    return sqrt(rhs)
end


using StructuralAnalysis
sa = StructuralAnalysis

### Nodes
begin
	node = Dict{Integer,Array{Float64}}()
	node[1] = [4000/tan(30*π/180),4000]
	node[2] = [10928,0]
	node[3] = [0,0]
	node
end

### Members
begin
	member = Dict{Integer,Array{Integer}}()
	member[1] = [1,2]
	member[2] = [1,3]
	member[3] = [2,3]
	member
end

node[member[1][2]][1]

### Modulus of Elasticity
begin
	E = Dict{Integer, Float64}()
	E[1] = 200
	E[2] = 200
	E[3] = 200
	E
end

### Cross-Sectional Area
begin
	A = Dict{Integer, Float64}()
	A[1] = 20000
	A[2] = 15000
	A[3] = 18000
	A
end

### Poisson's Ratio
begin
    ν = Dict{Integer,Float64}()
    ν[1] = 0.3
    ν[2] = 0.3
    ν[3] = 0.3
    ν
end

### Moment of Inertia
begin
    I = Dict{Integer,Array}()
    I[1] = [100*10^6,200*10^6]
    I[2] = [100*10^6,200*10^6]
    I[3] = [100*10^6,200*10^6]
end

### Forces
begin
	f = Dict{Integer, Array}()
	f[1] = [500*cos(40*π/180),500*sin(40*π/180)]
	f[2] = [0,missing]
	f[3] = [missing,missing]
	f
end

### Displacements
begin
	u = Dict{Integer, Array}()
	u[1] = [missing,missing]
	u[2] = [missing,0]
	u[3] = [0,0]
	u
end

kg = sa.LocalK(E, A, I, ν, member, node)

k = sa.AxialLocalK(E,A,member,node)
KG = sa.AxialGlobalK(k,member,node)
Free = sa.FreeNodalDir(u)
KGAug = sa.AugmentGlobal(Free,KG)
Force = sa.GlobalForce(Free,f)
freedisp = inv(KGAug) * Force


Pkg.clone("https://github.com/dfdx/NaiveBayes.jl.git")

using NaiveBayes
using RDatasets

iris = dataset("datasets", "iris")

# observations in columns and variables in rows
X = array(iris[:, 1:4])'
p, n = size(X)
# by default species is a PooledDataArray,
y = [species for species in iris[:, 5]]

# how much data use for training
train_frac = 0.9
k = int(floor(train_frac * n))
idxs = randperm(n)
train = idxs[1:k]
test = idxs[k+1:end]

model = GaussianNB(unique(y), p)
fit(model, X[:, train], y[train])

accuracy = countnz(predict(model, X[:,test]).==
            y[test]) / countnz(test)

println("Accuracy: $accuracy")

X = [1 1 0 2 1;
     0 0 3 1 0;
     1 0 1 0 2]

y = [:a, :b, :b, :a, :a]

m = MultinomialNB(unique(y), 3)
fit(m, X, y)

Xtest = [0 4 1;
         2 2 0;
         1 1 1]

predict(m, Xtest)


cd(@__DIR__)
using AdvancedPS, Random, KernelDensity, Distributions
include("KDE.jl")

function loglike(μ, σ, data)
    simdata = rand(Normal(μ, σ), 10_000)
    kd = kernel(simdata)
    dist = InterpKDE(kd)
    #println("proposal: ",μ," ",σ)
    like = max.(1e-10, pdf(dist, data))
    return sum(log.(like))
end

priors = (
    μ=(Normal(0, 10),),
    σ=(Truncated(Cauchy(0, 1), 0.0, Inf),)
)

bounds = ((-Inf,Inf),(0.0,Inf))

data = rand(Normal(0.0, 1.0), 50)

model = DEModel(priors=priors, model=x->loglike(x..., data))

de = DE(bounds=bounds, visualize=false, burnin=1000, priors=priors, progress=true)
n_iter = 2000
chains = sample(model, de, n_iter)
println(chains)


# Structs
struct ChebyshevGrid{T <: AbstractFloat, P} <: ApproximationGrid
    a::T
    b::T
    n::Int
    p::P
end

# Constructors
function ChebyshevGrid(a::A, b::B, n) where {A, B}
    T = promote_type(float(A), float(B))
    p = FFTW.plan_r2r!(fill(zero(T), n), FFTW.REDFT10)
    return ChebyshevGrid{T, typeof(p)}(a, b, n, p)
end

# Nodes
function nodes(g::ChebyshevGrid{T}) where {T}
    n = g.n
    a = T(0.5) * (g.b - g.a)
    b = T(0.5) * (g.b + g.a)
    return [a * sinpi((2k + one(T) - n) / 2n) + b for k = 0:(n - 1)]
end

# Weights
function weights(g::ChebyshevGrid)
    n = g.n
    a = zeros(n)
    a[1:2:n] = (2 / n) ./ (1 .- ((1:2:n) .- 1).^2)
    return FFTW.r2r(a, FFTW.REDFT01)
end

# Jacobian
jacobian(g::ChebyshevGrid{T}) where {T} = T(0.5) * (g.b - g.a)

# Interpolation
function interpolate(g::ChebyshevGrid, v::AbstractVector, x′)
    @assert (g.a ≤ x′ ≤ g.b)
    x = 2 * (x′ - (g.b + g.a) / 2) / (g.b - g.a)

    n = length(v)

    c = reverse_plan(g.p, v, n)
    c[1] /= 2
    c   ./= n

    if n == 0
        return zero(eltype(c))
    elseif n == 1 # avoid issues with NaN x
        return first(c) * one(x)
    end

    x = 2x
    b₁ = b₂ = zero(eltype(c))
    @inbounds for k = n:-1:2
        b₂, b₁ = b₁, muladd(x, b₁, c[k] - b₂)
    end

    return muladd(x / 2, b₁, c[1] - b₂)
end

reverse_plan(p, v::AbstractVector{Float64}, n) = p * reverse(v)
function reverse_plan(p, v::AbstractVector{S}, n) where {S}
    r = reinterpret(eltype(S), reverse(v))
    m = length(r)
    d = div(m, n)
    w = permutedims(reshape(r, d, n))

    for i in 1:d
        p * view(w, :, i) # `p` is an in-place `FFTW` plan which mutates `w`
        r[i:d:m] = view(w, :, i)
    end

    return reinterpret(S, r)
end


# this script does a Monte Carlo that applies IV to solve inconsistency of OLS estimator
# when there is measurement error of the regressors
using Plots

function GIVmoments(theta, data)
	data = [data lags(data,2)]
    data = data[3:end,:] # get rid of missings
	n = size(data,1)
	y = data[:,1]
	ylag = data[:,2]
	x = data[:,3]
	xlag = data[:,6]
	xlag2 = data[:,9]
	X = [ones(n,1) ylag x]
	e = y - X*theta
	Z = [ones(n,1) x xlag xlag2]
	m = e.*Z
end

function main()
# do the Monte Carlo
n = 100
sig = 1
reps = 1000
results = zeros(reps,3)
for rep = 1:reps
	x = randn(n) # an exogenous regressor
	e = randn(n) # the error term
	ystar = zeros(n)
	# generate the dep var
	for t = 2:n
	  ystar[t] = 0.0 + 0.9*ystar[t-1] + 1.0*x[t] + e[t]
	end
    # add measurement error
	y = ystar + sig*randn(n)
	# now do GMM, using the data with meas. error in both dep. var. and regressor
	ylag = lag(y,1)
	data = [y ylag x];
    data = data[2:end,:] # drop first obs, missing due to lag
	theta = [0, 0.9, 1]
	weight = 1
    # note to self: this is very slow, because it uses the general GMM method, instead of analytic GIV
    # also, the weight matrix is not optimal
    moments = theta-> GIVmoments(theta, data)
	b, junk, junk, junk, junk = gmm(moments, theta, weight)
	b = b - [0.0, 0.9, 1.0] # subtract true values, so mean should be approx. zero if consistent
    results[rep,:] = b
end

histogram(results[:,1],nbins = 30)
#savefig("givconstant.png")
histogram(results[:,2],nbins = 30)
#savefig("givrho.png")
dstats(results)
return
end
main()


## Influence coefficients for velocities
#============================================#

# Helper
function transform(x, z, x1, x2)
    s1, s2 = x - x1, x - x2
    r1, r2 = √(s1^2 + z^2), √(s2^2 + z^2)
    θ1, θ2 = atan(z, s1), atan(z, s2)

    r1, r2, θ1, θ2
end

term_1(xp, x, z, x1, x2, r1, r2, θ1, θ2) = (x2 - x1) - z * (θ2 - θ1) + (x - xp) * log(r2 / r1)
term_2(xp, x, z, r1, r2, θ1, θ2) = (x - xp) * (θ2 - θ1) + z * log(r2 / r1)

boundary_term(xp, x, x1, x2) = (1/2π, 1/2 * (x - xp) / (x2 - x1))

# Source velocities
#============================================#

function constant_source_velocity(σ, x, z, x1, x2)
    r1, r2, θ1, θ2 = transform(x, z, x1, x2)
    @. σ / 2π * SVector(-log(r2 / r1), θ2 - θ1)
end

function linear_source_velocity_a(σ1, x, z, x1, x2)
    r1, r2, θ1, θ2 = transform(x, z, x1, x2)
    if abs(z) <= 1e-12
        ua, wa = boundary_term(x2, x, x1, x2)
        σ1 .* SVector(ua, -wa)
    else
        ua = term_1(x2, x, z, x1, x2, r1, r2, θ1, θ2)
        wa = term_2(x2, x, z, r1, r2, θ1, θ2)

        @. σ1 / (2π * (x2 - x1)) * SVector(ua, -wa)
    end
end

function linear_source_velocity_b(σ2, x, z, x1, x2)
    r1, r2, θ1, θ2 = transform(x, z, x1, x2)
    if abs(z) <= 1e-12
        ub, wb = boundary_term(x1, x, x1, x2)
        σ2 .* SVector(-ub, wb)
    else
        ub = term_1(x1, x, z, x1, x2, r1, r2, θ1, θ2)
        wb = term_2(x1, x, z, r1, r2, θ1, θ2)

        @. σ2 / (2π * (x2 - x1)) * SVector(-ub, wb)
    end
end

# Vortex velocities
#============================================#

function linear_vortex_velocity_a(γ1, x, z, x1, x2)
    r1, r2, θ1, θ2 = transform(x, z, x1, x2)
    # if z == 0.
    #   ua, wa = (reverse ∘ boundary_term)(x2, x, x1, x2)
    #   γ1 .* SVector(-ua, -wa)
    # else
        ua = term_2(x2, x, z, r1, r2, θ1, θ2)
        wa = term_1(x2, x, z, x1, x2, r1, r2, θ1, θ2)

        @. γ1 / (2π * (x2 - x1)) * SVector(-ua, -wa)
    # end
end

function linear_vortex_velocity_b(γ2, x, z, x1, x2)
    r1, r2, θ1, θ2 = transform(x, z, x1, x2)
    # if z == 0.
    #   ub, wb = (reverse ∘ boundary_term)(x1, x, x1, x2)
    #   γ2 .* SVector(ub, wb)
    # else
        ub = term_2(x1, x, z, r1, r2, θ1, θ2)
        wb = term_1(x1, x, z, x1, x2, r1, r2, θ1, θ2)

        @. γ2 / (2π * (x2 - x1)) * SVector(ub, wb)
    # end
end


using Revise
using AdFem
using LinearAlgebra
using PyPlot
using DelimitedFiles
using MAT

function f(x, y)
    10/(1+x^2) + x * y + 10*y^2
end

function calc_residual_and_jacobian(θ, u)
    E = θ[1:get_ngauss(mmesh)]
    nu = θ[get_ngauss(mmesh)+1:end]
    μ = E / (2*(1 + nu))
    λ = E*nu/((1 + nu)*(1 - 2*nu))

    ψ, J = neo_hookean(u, μ, λ, mmesh)
    B = compute_fem_source_term(zeros(get_ngauss(mmesh)), -0.5*ones(get_ngauss(mmesh)), mmesh)
    ψ = ψ - B
    J, ψ = impose_Dirichlet_boundary_conditions(J, ψ, bdnode, zeros(length(bdnode)))
    ψ, J
end

mmesh = Mesh(joinpath(PDATA, "twoholes_large.stl"))
mmesh = Mesh(mmesh.nodes * 10, mmesh.elems)

E = eval_f_on_gauss_pts(f, mmesh)
nu = 0.3 * ones(get_ngauss(mmesh))
θ = placeholder([E; nu])
bdnode = bcnode((x,y)->(x<1e-5) || (x>0.49-1e-5), mmesh)
bdnode = [bdnode; bdnode .+ mmesh.ndof]
nr = newton_raphson_with_grad(calc_residual_and_jacobian, zeros(2mmesh.ndof), θ)

ADCME.options.newton_raphson.verbose = true
sess = Session(); init(sess)
x = run(sess, nr)

matwrite("fenics/fwd2.mat", Dict("x"=>x, "E"=>E))

close("all")
figure(figsize = (15, 5))
subplot(121)
u = x[1:mmesh.nnode]
v = x[mmesh.ndof+1:mmesh.ndof + mmesh.nnode]
visualize_scalar_on_fem_points(sqrt.(u.^2+v.^2), mmesh)
subplot(122)
visualize_displacement(x, mmesh)
savefig("fenics/fwd2.png")

close("all")
visualize_scalar_on_gauss_points(E, mmesh)
savefig("fenics/E.png")


# module Petkovsek

using SymPy

# export algpoly

macro log(var)
    println("$var: ", eval(var))
    return eval(var)
end

function fallingfactorial(x, j)
    result = 1
    for i in 0:j - 1
        result *= (x - i)
    end
    return result
end

function algpoly(polylist, f, n)
    # Construct polynomials qj that arise from treating the shift operator
	# as the difference operator plus the identity operator.
    qlist = []
    for j in 0:length(polylist)-1
        qj = 0 * n
        for i in j:length(polylist)-1
            qj += binomial(i, j) * polylist[i+1]
        end
        push!(qlist, qj)
    end

    # Find all candidates for the degree bound on output polynomial.
    b = maximum([(degree(poly, n) - (j - 1)) for (j, poly) in enumerate(qlist)])
    first = degree(f, n) - b
    second = -1 * b - 1
    lc = [LC(Poly(p,n)) for p in qlist]

    alpha = 0 * n
    for j in 0:length(qlist)-1
        if (degree(qlist[j+1], n) - j) == b
            alpha += lc[j+1] * fallingfactorial(n, j)
        end
    end

    deg = polyroots(alpha)
    third = maximum(keys(deg))
    d = max(first, second, third, 0) |> Int64
    # Use method of undetermined coefficients to find the output polynomial.
    varlist = symset("a", d+1)
    pol = 0 * n
    for i in 0:d
        pol += n^i * varlist[i+1]
    end
    solution = -1 * f
    for (i, poly) in enumerate(polylist)
        solution += subs(pol, (n, n + (i-1))) * poly
    end
    coef = coeffs(Poly(solution, n))
    filter!(e->e!=n*0, coef)
    if isempty(coef)
        return Dict([v => v for v in varlist])
    end
    return solve(coef, varlist)
end

function factors(expr)
    c, list = factor_list(expr)
    result = [x^y for (x,y) in list]
    if c != 1
        push!(result, c)
    end
    return result
end

function alghyper(polylist, n)
    p = polylist[1]
    alist = factors(p)
    for (i,p) in enumerate(alist)
        alist[i] /= LC(Poly(p, n))
    end
    if !(1*n^0 in alist)
        push!(alist, 1*n^0)
    end

    d = length(polylist)
    p = subs(polylist[end], (n,n-d+2))
    blist = factors(p)
    for (i,p) in enumerate(blist)
        blist[i] /= LC(Poly(p,n))
    end
    if !(1*n^0 in blist)
        push!(blist, 1*n^0)
    end

    solutions = []
    for aelem in alist
        for belem in blist
            plist = []
            for i in 0:d-1
                pi = polylist[i+1]
                for j in 0:i-1
                    pi *= subs(aelem, (n, n+j))
                end
                for j in i:d-1
                    pi *= subs(belem, (n, n+j))
                end
                push!(plist, pi)
            end

            m = maximum([degree(Poly(p, n)) for p in plist])
            alpha = [coeff(expand(p), n^m) for p in plist]
            @syms z
            zpol = 0*z
            for i in 0:length(alpha) - 1
                zpol += alpha[i+1]*z^i
            end

            vals = [key for (key,val) in polyroots(zpol) if key != 0]

            for x in vals
                polylist2 = [x^(i-1)*p for (i,p) in enumerate(plist)]
                polysols = algpoly(polylist2, 0*n, n)
                if isempty(polysols)
                    continue
                end
                polysols = collect(values(polysols))
                filter!(e->e!=n*0, polysols)
                if length(polysols) > 0
                    c = 0*n
                    for (i,p) in enumerate(polysols)
                        c += n^(i-1) * p
                    end
                    s = x * aelem/belem * subs(c, (n, n+1))/c
                    push!(solutions, simplify(s))
                end
            end
        end
    end
    return solutions
end

function tohg(sol, n)
    facts = factors(sol)
    result = []
    for f in facts
        if has(f, n)
            f = f |> subs(n, n-1)
            c = coeff(f, n)
            f = f / c
            push!(result, factorial(f))
            if c != 1
                push!(result, c^n)
            end
        else
            push!(result, f^n)
        end
    end
    return prod(result)
end

function hyper()

# end # module

# using SymPy
# using Petkovsek

@syms n y

alghyper([2*n*(n+1), -(n^2 +3*n-2), n-1], n)

# algpoly([3, -n, n-1], 0*n, n)
# algpoly([n-1, -n, 3], 0*n, n)
println("alghyper: ", [tohg(sol, n) for sol in alghyper([2*n*(n+1), -(n^2 +3*n-2), n-1], n)])
println("algpoly: ", algpoly([n*(n + 1), -1*n^2 - 3*n + 2, 2*n - 2], 0*n,n))


using Test, MLToolkit
using Distributions: Poisson

@testset "Russian Roulette" begin
    function X(i)
        if i < 0
            return 0
        end
        if i > 10
            return 0
        end
        return i <= 5 ? i : 10 - i
    end

    S_true = sum(X.(1:10))

    dist = Poisson(5.0)

    n_mc = 100_000
    S_rr = mean([roll(X, dist) for _ = 1:n_mc])

    @test S_rr ≈ S_true atol=0.1
    @test roll(X, dist, n_mc) ≈ S_true rtol=0.01

    dist = Poisson(5.0)
    T(i) = pdf(dist, i) * X(i)

    S_true = sum(X.(1:10) .* pdf.(Ref(dist), 1:10))

    n_mc = 100_000
    S_rr_1 = mean([roll(T, dist) for _ = 1:n_mc])
    S_rr_2 = mean([roll_expectation(X, dist, Poisson(5.1)) for _ = 1:n_mc])
    S_rr_3 = mean([roll_expectation(X, dist, LogitNPD()) for _ = 1:n_mc])

    @test S_rr_1 ≈ S_true atol=0.1
    @test S_rr_2 ≈ S_true atol=0.1
    @test S_rr_3 ≈ S_true atol=0.1

    dist = LogitNPD()
    T(i) = pdf(dist, i) * X(i)

    S_true = sum(X.(1:10) .* pdf.(Ref(dist), 1:10))

    n_mc = 100_000
    S_rr_1 = mean([roll(T, dist) for _ = 1:n_mc])
    S_rr_2 = mean([roll_expectation(X, dist, Poisson(5.1)) for _ = 1:n_mc])
    S_rr_3 = mean([roll_expectation(X, dist, LogitNPD()) for _ = 1:n_mc])
    S_rr_4 = mean([roll_expectation(X, dist, dist) for _ = 1:n_mc])

    @test S_rr_1 ≈ S_true atol=0.1
    @test S_rr_2 ≈ S_true atol=0.1
    @test S_rr_3 ≈ S_true atol=0.1
    @test S_rr_4 ≈ S_true atol=0.1
end


export interpolate,solution

"""
    interpolate(mesh,vector;n=2^3)

Interpolate a vector `vector` defined on a uniform collocation grid defined by `mesh`.

Returns `(new_mesh,new_vector)` a new uniform mesh with `n` times as many values, and the vector of values on collocation points.

"""
function interpolate(mesh::Mesh,vector;n=2^3::Int)

    fourier=fft(vector)
    m=Int(mesh.N/2)
    new_fourier = [fourier[1:m] ;zeros((n-1)*2*m) ;fourier[m+1:end]]

    new_mesh = Mesh(mesh.xmin,mesh.xmax,n*mesh.N)
    new_vector=ifft(new_fourier)*n
    if all((vector[i] isa Real) for i in length(vector))
        new_vector=real.(new_vector)
    end
    return new_mesh,new_vector
end

"""
    interpolate(mesh,vector,x;fast)

Interpolate a vector `vector` defined on a uniform collocation grid defined by `mesh`, on collocation points given by `x`.

If the collocation points `x` are regularly spaced and the optional keyword argument `fast` is set to `true` (default is `false`),
then the algorithm is faster and uses less allocations, but is less precise.

Returns the vector of values on collocation points.

"""
function interpolate(mesh::Mesh,vector,x;fast=false)

    fourier=fft(vector)
    k = mesh.k
	x₀ = mesh.xmin
	if fast == false
		#same as new_vector=exp.(1im*(x.-x₀)*k')*fourier/length(k)
		new_vector = similar(x,Complex); z = complex.(zero(k));
		for i in 1:length(x)
			z .= exp.(-1im*(x[i].-x₀)*k)
			new_vector[i] = real(dot(z,fourier))
		end
		new_vector ./= length(k)
	else
		y=x[2:end]-x[1:end-1];y.-=y[1];
        if maximum(abs.(y))>8*eps(maximum(x))
            @error("Collocation points must be equally spaced.")
        end
		new_vector = similar(x,Complex); z = exp.(-1im*(x[1].-x₀)*k);eidk=exp.(-1im*(x[2]-x[1])*k);
		for i in 1:length(x)
			new_vector[i] = real(dot(z,fourier))
			z .*= eidk
		end
		new_vector ./= length(k)
	end

    if all((vector[i] isa Real) for i in length(vector))
        new_vector=real.(new_vector)
    end
    return new_vector
end

"""
    solution(p::Problem;t,x,interpolation)

Gives the solution of a solved initial-value at a given time `t`.

# Arguments
- Argument `p` is of type `Problem`.
- Keyword argument `t` is optional, the last computed time is returned by default.
- Keyword argument `x` is optional, if provided the solution is interpolated to the collocation vector `x`.
- Keyword argument `interpolation` is optional, if an integer is provided the solution is interpolated on as many collocation points (if `true`, then the default value `2^3` is chosen).

# Return values
Provides `(η,v,x,t)` where
- `η` is the surface deformation at collocation points;
- `v` is the tangential velocity (derivative of the trace of the velocity potential) at collocation points;
- `x` is the vector of collocation points;
- `t` the time (first computed time greater or equal to provided `t`).

"""
function solution(p::Problem; t=nothing, x=nothing, interpolation = false)
	if t == nothing t = p.times.tfin end
	t=min(max(t,0),p.times.tfin)
	index = indexin(false,p.times.ts.<t)[1]
	t=p.times.ts[index]
	if Symbol(typeof(p.model)) == :WaterWaves
		if x != nothing || interpolation != false
			@warn "cannot interpolate non-regularly spaced mesh."
		end
		(x,η,v) = (p.model.mapfro)(p.data.U[index])

	else
    	(η,v) = (p.model.mapfro)(p.data.U[index])
		mesh=p.mesh
		if x != nothing
			η = interpolate(mesh,η,x)
			v = interpolate(mesh,v,x)
		else
			x=mesh.x
		end
		if interpolation == true
			new_mesh,η = interpolate(mesh,η)
			new_mesh,v = interpolate(mesh,v)
			x = new_mesh.x
		elseif isa(interpolation,Int)
			new_mesh,η = interpolate(mesh,η;n=interpolation)
			new_mesh,v = interpolate(mesh,v;n=interpolation)
			x = new_mesh.x
		end
	end
	return η,v,x,t
end


"""
    log_inverse_gamma(x; α=1., β=1., d=0)

Log of the InverseGamma PDF.
Equivalent to `using Distributions; logpdf(InverseGamma(α, β), x)`
https://en.wikipedia.org/wiki/Inverse-gamma_distribution

# Keyword Arguments
- `x::Real`: Input
- `α::Real=1.`: Shape parameter
- `β::Real=1.`: Scale parameter
- `d::Integer=0`: How many derivatives to take
"""
function log_inverse_gamma(x::Real; α::Real=1., β::Real=1., d::Integer=0)
    @assert 0 <= d <= 2 "Can only differentiate up to two times"
    @assert α > 0 "α needs to be > 0"
    @assert β > 0 "β needs to be > 0"
    if d == 0
        x > 0 ? val = -(β / x) - (1 + α) * log(x) + α * log(β) - loggamma(α) : val = -Inf
    elseif d == 1
        x > 0 ? val = (β / x - (1 + α)) / x : val = 0
    else
        x > 0 ? val = (-2 * β / x + (1 + α)) / (x * x) : val = 0
    end
    return val
end

"""
    log_gamma(x, parameters; d=0, passed_mode_std=false)

Log of the Gamma PDF.
Equivalent to `using Distributions; logpdf(Gamma(α, θ), x)`
https://en.wikipedia.org/wiki/Gamma_distribution

# Arguments
- `x::Real`: Input
- `parameters::Vector`: Either the shape and scale parameters (i.e. [α, θ]) or a mode and standard deviation
- `d::Integer=0`: How many derivatives to take
- `passed_mode_std::Bool=false`: Whether to the `parameters` need to be converted to shape and scale parameters
"""
function log_gamma(x::Real, parameters::Vector{<:Real}; d::Integer=0, passed_mode_std::Bool=false)
    @assert 0 <= d <= 2 "Can only differentiate up to two times"
    @assert length(parameters) == 2
    assert_positive(parameters)
    if passed_mode_std
        parameters = gamma_mode_std_to_α_θ(parameters[1], parameters[2])
    end
    α = parameters[1]
    θ = parameters[2]
    if d == 0
        x > 0 ? val = -(x / θ) + (α - 1) * log(x) - α * log(θ) - loggamma(α) : val = -Inf
    elseif d == 1
        x > 0 ? val = (α - 1) / x - 1 / θ : val = 0
    else
        x > 0 ? val = -(α - 1) / (x * x) : val = 0
    end
    return val
end

"""
    gamma_mode_std_to_α_θ(m, σ)

Convert a gamma distribution's mode and standard deviation into it's shape and
scale parameters

# Arguments
- `m::Real`: Desired mode of gamma distribution
- `σ::Real`: Desired standard deviation of gamma distribution
"""
function gamma_mode_std_to_α_θ(m::Real, σ::Real)
    θ = (sqrt(m ^ 2 + 4 * σ ^ 2) - m) / 2
    α = m / θ + 1
    return [α, θ]
end

"""
    gauss_cdf(x)

The CDF of a Gaussian (i.e. (1 + erf(x))/2)
"""
gauss_cdf(x::Real) = (1 + erf(x))/2

"""
    log_gaussian(x, parameters; d=0, min=-Inf, max=Inf)

Log of the Gaussian PDF.
Equivalent to `using Distributions; logpdf(Gaussian(μ, σ), x)`

# Arguments
- `x::Real`: Input
- `parameters::Vector`: The mean and standard deviation parameters (i.e. [μ, σ])
- `d::Integer=0`: How many derivatives to take
- `min::Real=-Inf`: Where to minimally truncate the Gaussian
- `max::Real=Inf`: Where to maximally truncate the Gaussian
"""
function log_gaussian(x::Real, parameters::Vector{<:Real}; d::Integer=0, min::Real=-Inf, max::Real=Inf)
    @assert 0 <= d <= 2 "Can only differentiate up to two times"
    @assert length(parameters) == 2
    μ = parameters[1]
    σ = parameters[2]
    assert_positive(σ)
    @assert min < max
    if d == 0
        (min < x < max) ? val = (-((x - μ) / σ) ^ 2 - log(2 * π * σ * σ)) / 2 - log(1 - gauss_cdf(min - μ) - gauss_cdf(μ - max)) : val = -Inf
    elseif d == 1
        (min < x < max) ? val = -(x - μ)/(σ * σ) : val = 0
    else
        (min < x < max) ? val = -1 / (σ * σ) : val = 0
    end
    return val
end

"""
    log_uniform(x; d=0, min=0, max=1)

Log of the Uniform PDF.

# Arguments
- `x::Real`: Input
- `min_max::Vector=[0,1]`: Where to truncate the Uniform
- `d::Integer=0`: How many derivatives to take
"""
function log_uniform(x::Real; min_max::Vector{<:Real}=[0,1], d::Integer=0)
    @assert 0 <= d <= 2 "Can only differentiate up to two times"
    @assert length(min_max) == 2
    min, max = min_max
    @assert min < max
    if d == 0
        min <= x <= max ? -log(max - min) : -Inf
    else
        return 0
    end
end

"""
    log_loguniform(x::Real, min_max::Vector{<:Real}; d::Integer=0, shift::Real=0)

Log of the log-Uniform PDF. Flattens out in log space starting at shift
Also known as a (modified in shifted case) Jeffrey's prior

# Arguments
- `x::Real`: Input
- `min_max::Vector`: Where to truncate the log-Uniform
- `d::Integer=0`: How many derivatives to take
- `shift::Real=0`: Where to shift the peak of the distribution
"""
function log_loguniform(x::Real, min_max::Vector{<:Real}; d::Integer=0, shift::Real=0)
    @assert 0 <= d <= 2 "Can only differentiate up to two times"
    @assert length(min_max) == 2
    min, max = min_max
    @assert 0 < min + shift < max + shift
    xpshift = x + shift
    if d == 0
        min <= x <= max ? val = -log(xpshift) - log(log((max + shift)/(min + shift))) : val = -Inf
    elseif d == 1
        min <= x <= max ? val = -1 / xpshift : val = 0
    elseif d == 2
        min <= x <= max ? val = 1 / (xpshift * xpshift) : val = 0
    end
    return val
end

"""
    log_Rayleigh(x, σ; d=0, cutoff=Inf)

Log of the Rayleigh PDF.
https://en.wikipedia.org/wiki/Rayleigh_distribution

# Arguments
- `x::Real`: Input
- `σ::Real`: The mode
- `d::Integer=0`: How many derivatives to take
- `cutoff::Real=Inf`: Where to cutoff the tail of the distribution
"""
function log_Rayleigh(x::Real, σ::Real; d::Integer=0, cutoff::Real=Inf)
    @assert 0 <= d <= 2
    @assert cutoff > 0
    cutoff == Inf ? normalization = 0 : normalization = -log(1 - exp(-cutoff * cutoff / 2 / σ / σ))
    if d == 0
        0 <= x <= cutoff ? val = normalization - (x * x / 2 / σ / σ) + log(x / σ / σ) : val = -Inf
    elseif d == 1
        0 <= x <= cutoff ? val = 1 / x - x / σ / σ : val = 0
    elseif d == 2
        0 <= x <= cutoff ? val = -1 / x / x - 1 / σ / σ : val = 0
    end
    return val
end

"""
    log_circle(xs, min_max_r; d=[0,0])

Log of the 2D circle PDF.

# Arguments
- `xs::Real`: Inputs
- `min_max_r::Vector{<:Real}`: How far the inner and outer edge of the circle extends from the origin
- `d::Vector{<:Integer}=[0,0]`: How many derivatives to take
"""
function log_circle(xs::Vector{<:Real}, min_max_r::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) == 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(xs) == length(min_max_r) == length(d) == 2
    min_r, max_r = min_max_r

    # @assert min_r < sqrt(dot(x, x)) < max_r
    # @assert min_θ < atan(x[1], x[2]) < max_θ
    if all(d .== 0)
        min_r < sqrt(dot(xs, xs)) < max_r ? val = -log(2 * π * (max_r ^ 2 - min_r ^ 2)) : val = -Inf
    else
        return 0
    end
    return val
end

"""
    log_cone(xs; d=[0,0])

Log of the 2D unit cone PDF.

# Arguments
- `xs::Real`: Inputs
- `d::Vector{<:Integer}=[0,0]`: How many derivatives to take
"""
function log_cone(xs::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(xs) == length(d) == 2

    r_sq = dot(xs, xs)  # x^2 + y^2
    r = sqrt(r_sq)
    if d == [0,0]
        0 <= r < 1 ? val = log(3 / π * (1 - r)) : val = -Inf
    elseif d == [0,1]
        0 <= r < 1 ? val = xs[2] / (r_sq - r) : val = 0
    elseif d == [0,2]
        0 <= r < 1 ? val = (-xs[2] ^ 2 * r + xs[1] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [1,0]
        0 <= r < 1 ? val = xs[1] / (r_sq - r) : val = 0
    elseif d == [1,1]
        0 <= r < 1 ? val = (xs[1] * xs[2] * (1 - 2 * r)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [2,0]
        0 <= r < 1 ? val = (-xs[1] ^ 2 * r + xs[2] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    end
    return val
end

"""
    log_quad_cone(xs; d=[0,0])

Log of the 2D unit quadratic cone PDF.

# Arguments
- `xs::Real`: Inputs
- `d::Vector{<:Integer}=[0,0]`: How many derivatives to take
"""
function log_quad_cone(xs::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(xs) == length(d) == 2

    r_sq = dot(xs, xs)  # x^2 + y^2
    r = sqrt(r_sq)
    if d == [0,0]
        0 <= r < 1 ? val = log(6 / π * (1 - 2 * r + r_sq)) : val = -Inf
    elseif d == [0,1]
        0 <= r < 1 ? val = 2 * xs[2] / (r_sq - r) : val = 0
    elseif d == [0,2]
        0 <= r < 1 ? val = 2 * (-xs[2] ^ 2 * r + xs[1] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [1,0]
        0 <= r < 1 ? val = 2 * xs[1] / (r_sq - r) : val = 0
    elseif d == [1,1]
        0 <= r < 1 ? val = (2 * xs[1] * xs[2] * (1 - 2 * r)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [2,0]
        0 <= r < 1 ? val = 2 * (-xs[1] ^ 2 * r + xs[2] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    end
    return val
end

"""
    log_cubic_cone(xs; d=[0,0])

Log of the 2D unit cubic cone PDF.

# Arguments
- `xs::Real`: Inputs
- `d::Vector{<:Integer}=[0,0]`: How many derivatives to take
"""
function log_cubic_cone(xs::Vector{<:Real}; d::Vector{<:Integer}=[0,0])
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(xs) == length(d) == 2

    r_sq = dot(xs, xs)  # x^2 + y^2
    r = sqrt(r_sq)
    if d == [0,0]
        0 <= r < 1 ? val = log(10 / π * (1 - r)^3) : val = -Inf
    elseif d == [0,1]
        0 <= r < 1 ? val = 3 * xs[2] / (r_sq - r) : val = 0
    elseif d == [0,2]
        0 <= r < 1 ? val = 3 * (-xs[2] ^ 2 * r + xs[1] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [1,0]
        0 <= r < 1 ? val = 3 * xs[1] / (r_sq - r) : val = 0
    elseif d == [1,1]
        0 <= r < 1 ? val = (3 * xs[1] * xs[2] * (1 - 2 * r)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    elseif d == [2,0]
        0 <= r < 1 ? val = 3 * (-xs[1] ^ 2 * r + xs[2] ^ 2 * (r - 1)) /
            (r ^ 3 * (1 - 2 * r + r_sq)) : val = 0
    end
    return val
end

"""
    log_rot_Rayleigh(xs; d=[0,0], σ=1/5, cutoff=Inf)

Log of the 2D rotated Rayleigh PDF

# Arguments
- `xs::Real`: Inputs
- `d::Vector{<:Integer}=[0,0]`: How many derivatives to take
- `σ::Real=1/5`: the radial mode of the distribution
- `cutoff::Real=Inf`: Where to cutoff the tail of the distribution
"""
function log_rot_Rayleigh(xs::Vector{<:Real}, σ::Real; d::Vector{<:Integer}=[0,0], cutoff::Real=Inf)
    @assert all(0 .<= d .<= 2)
    @assert sum(d) <= 2
    @assert cutoff > 0

    @assert length(xs) == length(d) == 2
    r_sq = dot(xs, xs)  # x^2 + y^2
    r = sqrt(r_sq)
    σ_sq = σ ^ 2
    cutoff == Inf ? normalization = -log(2 * σ * σ * π * π * π) / 2 : normalization = -log(2 * π) - log(sqrt(π / 2) * σ * erf(cutoff / sqrt(2) / σ) - cutoff * exp(- cutoff * cutoff / 2 / σ / σ))
    if d == [0,0]
        0 <= r < cutoff ? val = normalization - r_sq / (2 * σ_sq) + log(r) - (2 * log(σ)) : val = -Inf
    elseif d == [0,1]
        0 <= r < cutoff ? val = -xs[2] * (r_sq - σ_sq) / (r_sq * σ_sq) : val = 0
    elseif d == [0,2]
        0 <= r < cutoff ? val = -(xs[1] ^ 4 + xs[1] ^ 2 * (2 * xs[2] ^ 2 - σ_sq) + xs[2] ^ 2 * (xs[2] ^ 2 + σ_sq)) / (r_sq ^ 2 * σ_sq) : val = 0
    elseif d == [1,0]
        0 <= r < cutoff ? val = -xs[1] * (r_sq - σ_sq) / (r_sq * σ_sq) : val = 0
    elseif d == [1,1]
        0 <= r < cutoff ? val = -2 * xs[1] * xs[2] / r_sq ^ 2 : val = 0
    elseif d == [2,0]
        0 <= r < cutoff ? val = -(xs[2] ^ 4 + xs[2] ^ 2 * (2 * xs[1] ^ 2 - σ_sq) + xs[1] ^ 2 * (xs[1] ^ 2 + σ_sq)) / (r_sq ^ 2 * σ_sq) : val = 0
    end
    return val
end

"""
    log_bvnormal(xs, Σ; μ=zeros(T, 2), d=[0,0], lows=zeros(T, 2) .- Inf)

Log of the bivariate normal PDF
NOTE THAT THAT WHEN USING lows!=[-∞,...], THIS IS NOT PROPERLY NORMALIZED

# Arguments
- `xs::Real`: Inputs
- `Σ::Colesky`: The covariance matrix of the distribution
- `μ::Vector=zeros(T, 2)`: The mean of the distribution
- `d::Vector{<:Integer}=[0,0]`: How many derivatives to take
- `lows::Vector=zeros(T, 2) .- Inf`: The lower cutoffs of the distribution
"""
function log_bvnormal(xs::Vector{T}, Σ::Cholesky; μ::Vector{T}=zeros(T, 2), d::Vector{<:Integer}=[0,0], lows::Vector{T}=zeros(T, 2) .- Inf) where {T<:Real}
    @assert minimum(d) >= 0
    @assert maximum(d) <= 2
    @assert sum(d) <= 2

    @assert length(xs) == length(d) == length(μ) == 2
    y = xs - μ

    if sum(d) == 0
        all(xs .>= lows) ? val = -nlogL(Σ, y) : val = -Inf
    elseif sum(d) == 1
        y1 = Float64.(d)
        all(xs .>= lows) ? val = -dnlogLdθ(y1, Σ \ y) : val = 0
    elseif d == [0,2]
        y1 = y2 = [0, 1.]
        all(xs .>= lows) ? val = -d2nlogLdθ(y2, [0,0.], Σ \ y, Σ \ y1) : val = 0
    elseif d == [1,1]
        y1 = [1, 0.]
        y2 = [0, 1.]
        all(xs .>= lows) ? val = -d2nlogLdθ(y2, [0,0.], Σ \ y, Σ \ y1) : val = 0
    elseif d == [2,0]
        y1 = y2 = [1, 0.]
        all(xs .>= lows) ? val = -d2nlogLdθ(y2, [0,0.], Σ \ y, Σ \ y1) : val = 0
    end
    return val
end

"""
    bvnormal_covariance(σ11, σ22, ρ)

Converting 2 standard deviations and their correlation into a bivariate
covariance matrix and Cholesky factorizing the result.
"""
function bvnormal_covariance(σ11::Real, σ22::Real, ρ::Real)
    assert_positive(σ11, σ22)
    @assert 0 <= ρ <= 1
    v12 = σ11*σ22*ρ
    return ridge_chol([σ11^2 v12;v12 σ22^2])
end


using DelayEmbeddings, DynamicalSystemsBase
using Test
using Random
import Peaks

@testset "Pecora" begin
# %% Generate data
lor = Systems.lorenz([0.0;1.0;0.0];ρ=60)
data = trajectory(lor, 200; Δt=0.02, Ttr = 10)
metric = Chebyshev()

UNDERSAMPLING = false

@testset "Pecora univariate" begin
# %% Timeseries case

s = data[:, 2] # input timeseries = first entry of lorenz
optimal_τ = estimate_delay(s, "mi_min")
Tmax = 100
K = 14
samplesize = 1

# using PyPlot
# pygui(true)
# figure()
# ax1 = subplot(211)
# ylabel("⟨ε★⟩")
# ax2 = subplot(212)
# xlabel("τ (index units)")
# ylabel("⟨Γ⟩")

τs = (0,)
Random.seed!(123)
es_ref, Γs = pecora(s, τs; delays = 0:Tmax, w = optimal_τ, samplesize = samplesize, K = K, metric = metric, undersampling = UNDERSAMPLING)
(max1,_) = Peaks.findmaxima(vec(es_ref))
@test optimal_τ - 2 ≤ max1[1]-1 ≤ optimal_τ + 2
maxi = maximum(es_ref)
@test maxi ≤ 1.3
# ax1.plot(es_ref, label = "τs = $(τs)")
# ax2.plot(Γs)

τs = (0, max1[1]-1,)
Random.seed!(123)
es, Γs = pecora(s, τs; delays = 0:Tmax, w = optimal_τ, samplesize = samplesize, K = K, metric = metric, undersampling = UNDERSAMPLING)
(max2,_) = Peaks.findmaxima(vec(es))
(min1,_) = Peaks.findminima(vec(es))
@test 1 ≤ max2[1]-1 ≤ 4
@test optimal_τ - 2 ≤ min1[1]-1 ≤ optimal_τ + 2
# ax1.plot(es, label = "τs = $(τs)")
# ax2.plot(Γs)

τs = (0, max1[1]-1, max2[1]-1)
Random.seed!(123)
es, Γs = pecora(s, τs; delays = 0:Tmax, w = optimal_τ, samplesize = samplesize, K = K, metric = metric, undersampling = UNDERSAMPLING)
(min2,_) = Peaks.findminima(vec(es))
@test max2[1]-2 ≤ min2[1]-1 ≤ max2[1]
# ax1.plot(es, label = "τs = $(τs)")
# ax2.plot(Γs)
# ax1.legend()
# ax1.set_title("lorenz data")

end

@testset "Pecora multivariate" begin
## %% Trajectory case
s = Dataset(data)
optimal_τ = estimate_delay(s[:,2], "mi_min")
Tmax = 100
K = 14
samplesize = 1

js = (2,)
τs = (0,)
Random.seed!(123)
es_ref, Γs = pecora(s[:,2], τs; delays = 0:Tmax, w = optimal_τ, samplesize = samplesize, K = K, metric = metric, undersampling = UNDERSAMPLING)
Random.seed!(123)
es, Γs = pecora(s, τs, js; delays = 0:Tmax, samplesize = samplesize, w = optimal_τ, K = K, metric = metric, undersampling = UNDERSAMPLING)

@test round.(es[:,2], digits = 4) == round.(vec(es_ref),digits = 4)
(x_maxi,_) = Peaks.findmaxima(es[:,1])
@test 8 ≤ x_maxi[1]-1 ≤ 10
(z_maxi,_) = Peaks.findmaxima(es[:,3])
@test 13 ≤ z_maxi[1]-1 ≤ 15

# using PyPlot
# pygui(true)
# figure()
# subplot(211)
# for i in 1:3
#     plot(es[:, i], label = "τs = $(τs), js = $(js), J = $(i)")
# end
# ylabel("⟨ε★⟩")
# title("lorenz system, multiple timeseries")
# legend()
# grid()
# subplot(212)
# plot(es_ref, label = "τs = $(τs), js = $(js), J = 2")
# ylabel("⟨ε★⟩-ref")
# xlabel("τ (index units)")
# grid()

end
end


"""
    powermean

# Calculates the weighted powermean of a series of numbers

Calculates *order*th power mean of *values*, weighted by
*weights*. By default, *weights* are equal and *order*
is 1, so this is just the arithmetic mean.

# Arguments:

- `values`: values for which to calculate mean
- `order[s]`: order[s] of power mean
- `weights`: weights of elements, normalised to 1 inside function

# Returns:

- weighted power mean(s)
"""
function powermean end
function powermean(values::V1, order::R = 1,
                   weights::V2 = fill!(similar(values), 1)) where
    {R <: Real, FP <: AbstractFloat,
     V1 <: AbstractVector{FP}, V2 <: AbstractVector{FP}}
    length(values) == length(weights) ||
    throw(DimensionMismatch("powermean: Weight and value vectors must be the same length"))

    # Check whether all weights are zero in group.
    # In that case we want to return a NaN
    if iszero(weights)
        return convert(FP, NaN)
    end

    if isinf(order)
        if order > 0 # +Inf -> Maximum
            s = zero(FP)
            for i in eachindex(values, weights)
                @inbounds if (weights[i] > eps(FP)) & (values[i] > s)
                    s = values[i]
                end
            end
            return s
        else # -Inf -> Minimum
            s = convert(FP, Inf)
            for i in eachindex(values, weights)
                @inbounds if (weights[i] > eps(FP)) & (values[i] < s)
                    s = values[i]
                end
            end
            return s
        end
    else
        if order ≈ zero(order)
            s = zero(FP)
            for i in eachindex(values, weights)
                @inbounds if weights[i] > eps(FP)
                    s += weights[i] * log(values[i])
                end
            end
            return exp(s / sum(weights))
        else
            s = zero(FP)
            for i in eachindex(values, weights)
                @inbounds if weights[i] > eps(FP)
                    s += weights[i] * values[i] ^ order
                end
            end
            return (s / sum(weights)) ^ (one(FP) / order)
        end
    end
end

# This is the next most common case - a vector of orders
function powermean(values::V1,
                   orders::VR,
                   weights::V2 = fill!(similar(values), 1.0)) where
    {R <: Real, VR <: AbstractVector{R},
     FP <: AbstractFloat, V1 <: AbstractVector{FP}, V2 <: AbstractVector{FP}}
    return map(order -> powermean(values, order, weights), orders)
end

# This is the next most simple case - matrices with subcommunities, and an order or orders
function powermean(values::M1, orders, weights::M2 = fill!(similar(values), 1)) where
    {FP <: AbstractFloat, M1 <: AbstractMatrix{FP}, M2 <: AbstractMatrix{FP}}
    size(values) == size(weights) ||
        throw(DimensionMismatch("powermean: Weight and value matrixes " *
                                "must be the same size"))
    @views map(col -> powermean(values[:, col], orders,
                                weights[:, col]), 1:size(values, 2))
end

"""
    qD

Calculates Hill / naive-similarity diversity of order(s) *qs* of a
population with given relative proportions.

# Arguments:

- `proportions`: relative proportions of different types in population

- `qs`: single number or vector of orders of diversity measurement

# Returns:

- Diversity of order qs (single number or vector of diversities)

"""
function qD end
function qD(asm::AbstractAssemblage, qs)
    hassimilarity(asm) && error("function cannot run with $(typeof(gettypes(asm))) types as contains similarity")
    countsubcommunities(asm) == 1 ||
    throw(DimensionMismatch("Can only calculate diversity of a single community"))

    powermean(getabundance(asm), qs .- 1, getabundance(asm))[1] .^ -1
end

function qD(proportions::AbstractVector{<: Real}, qs)
    qD(Metacommunity(proportions), qs)
end

"""
    qDZ

Calculates Leinster-Cobbold / similarity-sensitive diversity of >= 1
order(s) *qs* of a population with given relative *proportions*, and
similarity matrix *Z*.

# Arguments:

- `proportions`: relative proportions of different types in a population

- `qs`: single number or vector of orders of diversity measurement

- `Z`: similarity matrix

# Returns:

- Diversity of order qs (single number or vector of diversities)

    """
function qDZ end

function qDZ(asm::AbstractAssemblage, qs)
    countsubcommunities(asm) == 1 ||
    throw(DimensionMismatch("Can only calculate diversity of a single community"))

    powermean(getordinariness!(asm), qs .- 1, getabundance(asm))[1] .^ -1
end

function qDZ(proportions::AbstractVector{<: Real}, qs,
             sim::AbstractTypes = UniqueTypes(size(proportions, 1)))
    qDZ(Metacommunity(proportions, sim), qs)
end

function qDZ(proportions::AbstractVector{<: Real}, qs, Z::AbstractMatrix{<: AbstractFloat})
    qDZ(Metacommunity(proportions, GeneralTypes(Z)), qs)
end


function check_in_interval(num,interval)
    (num >= interval[1]) & (num <= interval[2])
end

function compute_posterior(vec_num,alpha)
    quantile(vec_num,[alpha/2,1-alpha/2])
end

function distanceFunction(ytilde, y)
    return mean((ytilde[3,:]-y[3,:]) .^ 2)
end

function identity_mapping(y)
    y
end

function compute_norm(y,yhat;p=2)
    norm(y[2:3,2:end] - yhat[2:3,2:end], p)/size(y)[1]
end

function compute_l2_norm(y,yhat)
    sol1=hcat(y[:]...)'
    sol2=hcat(yhat[:]...)'
    l2_error = sqrt(sum(sum((sol1.-sol2).^2,dims=2)*0.01,dims=1))[1]
end

function compute_l2_norm_decay(y,yhat)
    sol1=hcat(y[:]...)'
    sol2=hcat(yhat[:]...)'
    T=size(sol1)[1]
    l2_error = sqrt(sum(sum((sol1.-sol2).^2,dims=2).*exp.(-0.001*(1:1:T))*0.01,dims=1))[1]
end

function compute_full_norm(y,yhat;p=Inf)
    norm(y - yhat, p)/size(y)[1]
end

function compute_at_steps(y,yhat;index=3:3:18,p=Inf)
    norm((y .- yhat)[2:3,index], p)/length(index)
end

function random_walk(p_prev, sd)
    # return proposed p, log(q(p_n+1|p_n))

    p_propose = map( (x,y) -> x + rand((Normal(0,y))) ,p_prev,sd)

    log_pdf = map( (x,y,z) -> logpdf((Normal(x,z)), y ), p_prev, p_propose, sd )
    return p_propose, log_pdf
end

function proposal_Normal_density(p1, p2, sd)
    return map( (x,y,z) -> pdf(Normal(x,z),y), p1, p2, sd )
end

function proposal_LN(log_p_prev,sd)
    log_p_prev .+ rand.(Normal.(0,sd))
end

function proposalRatio_LN(p_prev,p_cand,sd)
    0.5
end

function proposal_Gamma(log_p_prev,k)
    #TODO
end

function proposalRatio_Gamma(p_prev,p_cand,k)
    # TODO
end

function proposal_Normal(p_prev,sd)
    p_prev .+ rand.(Normal.(0,sd))
end

function proposalRatio_Normal(p_prev,p_cand,sd)
    1
end

function systematic_resample(weights)
    N = length(weights)
    partitions = rand()/N .+ (0:1:N-1)./N
    idx = zeros(Int,N)
    Q = cumsum(weights)
    i=1;j=1;
    while i<=N
        if partitions[i] < Q[j]
            idx[i]=j
            i +=1
        else
            j+=1
        end
    end
    return idx
end

function CPUtoc_modified(verbose)
    t = CPUTime.CPUtoq()
    if verbose
        println("elpased CPU time: ",t," seconds")
    end
    return t
end

function show_calibration(simulation)
    @printf("Calibration: ABC %f, ABC MCMC %f, ABC SMC %f\n", mean(simulation[2],dims=1)[1], mean(simulation[2],dims=1)[2],mean(simulation[2],dims=1)[3])
    @printf("CPU_time: ABC %f, ABC MCMC %f, ABC SMC %f\n", mean(simulation[4],dims=1)[1], mean(simulation[4],dims=1)[2],mean(simulation[4],dims=1)[3])
    @printf("CPU_time: ABC %f, ABC MCMC %f, ABC SMC %f\n", median(simulation[4],dims=1)[1], median(simulation[4],dims=1)[2],median(simulation[4],dims=1)[3])
    @printf("ESS: ABC %f, ABC MCMC %f, ABC SMC %f\n", median(simulation[5],dims=1)[1], mean(simulation[5],dims=1)[2],mean(simulation[5],dims=1)[3])
    @printf("ESS: ABC %f, ABC MCMC %f, ABC SMC %f\n", mean(simulation[5],dims=1)[1], median(simulation[5],dims=1)[2],median(simulation[5],dims=1)[3])
    @printf("ESS Time: ABC %f, ABC MCMC %f, ABC SMC %f\n", mean(simulation[6],dims=1)[1], mean(simulation[6],dims=1)[2],mean(simulation[6],dims=1)[3])
    @printf("ESS Time: ABC %f, ABC MCMC %f, ABC SMC %f\n", median(simulation[6],dims=1)[1], median(simulation[6],dims=1)[2],median(simulation[6],dims=1)[3])
end