# full size example
# there are 6 cases
# please write the data output (e.g. to csv) by yourself
using JuMP
using Gurobi

include("solveDEA.jl")
include("variable.jl")

# CRS input-oriented
function case1(data)
    # make up the data set
    X = data[:,1:6] # 6
    Z = data[:,7:10] # 4
    Y = data[:,11:15] # 5
    scale = size(data)[1]
    xNo = size(X)[2]
    zNo = size(Z)[2]
    yNo = size(Y)[2]

    for k = 1:scale
        # crs = Model(solver = GurobiSolver(Presolve=0, OutputFlag=0))
        model = Model(solver = GurobiSolver(OutputFlag=0)) # Gurobi is used as the LP solver here. Users can choose their favorite solver.

        @solveDEA_varibleInit(model)
        @solveDEA_varible(model, Lambda[1:25000] >= 0, "nc") # non-critical group 1
        @solveDEA_varible(model, Gamma[1:25000] >= 0, "nc") # non-critical group 2
        @solveDEA_varible(model, middle[1:4] >= 0, "c") # critial
        @solveDEA_varible(model, Theta >= 0, "c") # critial

        @objective(model, Min, Theta)

        @constraint(model, inputConA[i=1:xNo], sum{Lambda[r]*X[r,i], r = 1:scale} <= Theta*X[k,i])
        @constraint(model, outputConA[j=1:zNo], sum{Lambda[r]*Z[r,j], r = 1:scale} >= middle[j])
        @constraint(model, inputConB[i=1:zNo], sum{Gamma[r]*Z[r,i], r = 1:scale} <= middle[i])
        @constraint(model, outputConB[j=1:yNo], sum{Gamma[r]*Y[r,j], r = 1:scale} >= Y[k,j])

        #solve(model)
        solveDEA(model)

        #println("lambdas: $(getvalue(Lambda)))")

        # retrieve results
        println("Objective value: ", getobjectivevalue(model))

    end
end

println("begins")
data = readcsv("C:/Users/mb516/Desktop/solveDEA_local_0307/15-7-25k_01.csv")
case1(data)
# case2(data)
# case3(data)
# case4(data)
# case5(data)
# case6(data)

println("done!")


module Garch

using Random, Statistics, Optim, Parameters

export Std, GJR, fit!, simulate!, predict!

@with_kw mutable struct Std
    @deftype Float64
    σ = 0.01
    @assert σ > 0.0
    μ₁ = rand()
    @assert μ₁ > 0.0 && μ₁ < 1.0
    μ₂ = rand()
    @assert μ₂ > 0.0 && μ₂ < 1.0
end

@with_kw mutable struct GJR
    @deftype Float64
    σ = 0.01
    @assert σ > 0.0
    μ₁ = rand()
    @assert μ₁ > 0.0 && μ₁ < 1.0
    μ₂ = rand()
    @assert μ₂ > 0.0 && μ₂ < 1.0
    μ₃ = rand()
    @assert μ₃ > 0.0 && μ₃ < 1.0
    isdown::Bool = true
end

function nll(z, σ, rt)
    μ₁, μ₂ = exp.(-exp.(-z))
    ll = 0.0
    n = length(rt)
    σ² = σ * σ
    h = σ²
    μ = 1.0 - μ₂
    for t ∈ 2:n
        h = σ² + μ₁ * (μ₂ * h + μ * rt[t-1] * rt[t-1] - σ²)
        if h > 0.0
            ll += 0.5 * (log(h) + rt[t] * rt[t] / h)
        end
    end
    return ll
end

function nll(z, σ, rt, J)
    μ₁, μ₂, μ₃ = exp.(-exp.(-z))
    ll = 0.0
    n = length(rt)
    σ² = σ * σ
    h = σ²
    for t ∈ 2:n
        h = σ² + μ₁ * ((1.0 - μ₂ + 2.0 * (μ₂ - μ₃) * J[t-1]) * rt[t-1] * rt[t-1] + μ₃ * h - σ²)
        if h > 0.0
            ll += 0.5 * (log(h) + rt[t] * rt[t] / h)
        end
    end
    return ll
end

function fit!(model::GJR, x::Vector{Float64}; time_limit = 60.0, solver = NelderMead(), g_tol = 1.0e-6)
    @unpack σ, μ₁, μ₂, μ₃, isdown = model
    σ = std(x)
    J = 1.0 .* (isdown ? (x .< 0.0) : (x .> 0.0))
    p = [-log(-log(μ₁)), -log(-log(μ₂)), -log(-log(μ₃))]
    p0 = 4.0 .* rand(3)
    res = optimize(p -> nll(p, σ, x, J), p0, solver, Optim.Options(time_limit = time_limit, g_tol = g_tol))
    μ₁, μ₂ = exp.(-exp.(-res.minimizer))
    @pack! model = σ, μ₁, μ₂, μ₃, isdown
    return res
end

function fit!(model::Std, x::Vector{Float64}; time_limit = 60.0, solver = NelderMead(), g_tol = 1.0e-6)
    @unpack σ, μ₁, μ₂ = model
    σ = std(x)
    p = [-log(-log(μ₁)), -log(-log(μ₂))]
    p0 = 4.0 .* rand(2)
    res = optimize(p -> nll(p, σ, x), p0, solver, Optim.Options(time_limit = time_limit, g_tol = g_tol))
    μ₁, μ₂ = exp.(-exp.(-res.minimizer))
    @pack! model = σ, μ₁, μ₂
    return res
end

function simulate(model::Std, pathlen::Integer; r0 = 0.0)
    r = fill(0.0, pathlen + 1)
    r[1] = r0
    @unpack σ, μ₁, μ₂ = model
    σ² = σ * σ
    h = σ²
    μ = 1.0 - μ₂
    for t ∈ 1:pathlen
        h = max(0.0, σ² + μ₁ * (μ₂ * h + μ * r[t] * r[t] - σ²))
        r[t+1] = √h * randn()
    end
    return r[2:end]
end

function simulate(model::GJR, pathlen::Integer; r0 = 0.0)
    r = fill(0.0, pathlen + 1)
    r[1] = r0
    @unpack σ, μ₁, μ₂, μ₃, isdown = model
    σ² = σ * σ
    h = σ²
    for t ∈ 1:pathlen
        J = isdown ? (r[t] < 0.0) : (r[t] > 0.0)
        h = max(σ² + μ₁ * ((1.0 - μ₂ + 2.0 * (μ₂ - μ₃) * J) * r[t] * r[t] + μ₃ * h - σ²), 0.0)
        r[t+1] = √h * randn()
    end
    return r[2:end]
end

function predict(model::Union{GJR,Std}, paths::Integer, pathlen::Integer)
    vol = fill(0.0, paths)
    vol .= [std(simulate(model, pathlen, r0 = 0.0)) for i ∈ 1:paths]
    return vol
end

end


#This script will set up and solve the equations, in fourier space, for the gravitational wave
#perturbation h as a function of time. We solve (Dtt+k^2)h(t,k)=T(t,k).

#TODO: The integrator does not evolve in time, always spits dh = 0, fix it.

using FFTW
using LinearAlgebra
using Interpolations
using OrdinaryDiffEq
#Transform of only real functions. Gives back a matrix of 2 component vector for
# the momenta k and the matrix for the mode coefficients fk.
abstract type Parameters end

struct param{T<:Interpolations.GriddedInterpolation, TP<:Real} <: Parameters# where {T<:Interpolations.GriddedInterpolation, TP<:Real}
    f  :: T
    x  :: Array{TP,1}
    y  :: Array{TP,1}
    kx :: Array{TP,2}
    ky :: Array{TP,2}
    dt :: TP
end

function get_evol_variable(u::Array{T,2}, u_t::Array{T,2}) where {T<:Complex}
    N      = length(u)
    u_evol = im.*zeros(N)
    u_evol = reshape(u, N)
    append!(u_evol, reshape(u_t, N))

    u_evol
end

function get_tensors(u_evol::Array{T,1}, Nkx::N, Nky::N) where {T<:Complex, N<:Int}
    Ntot     = length(u_evol)
    idxsplit = Int(Ntot/2)
    h        = reshape(u_evol[1:idxsplit],Nkx,Nky)
    h_t      = reshape(u_evol[idxsplit+1:end],Nkx,Nky)

    h, h_t
end

function Fourier_Transform_2D(f::Array{T,2}) where {T<:Real}
    Nx   = length(f[:,1])
    Ny   = length(f[1,:])
    plan = plan_rfft(f)

    fk   = 1/(Nx*Ny).*(plan * f)
end

function initial_conditions(ff::VEVTimeSeries, kx::Array{T,2}, ky::Array{T,2}) where {T<:Real}
    Tk  = Fourier_Transform_2D(ff[1,:,:])
    Nkx = length(kx[:,1])
    Nky = length(ky[1,:])

    tol = 10^-10
    indeces = findall(abs.(Tk).<tol)
    for i in indeces
        Tk[i] = 0.0
    end

    h_t    = im.*zeros(Nkx,Nky)
    h      = Tk./(kx.^2 + ky.^2)
    h[1,1] = Tk[1,1]

    get_evol_variable(h,h_t)
end

function rhs(h_evol::Array{T,1}, param::Parameters, t::TP) where {T<:Complex, TP<:Real}
    f_inter = param.f(t,param.x,param.y)
    kx      = param.kx
    ky      = param.ky
    Nkx     = length(kx[:,1])
    Nky     = length(ky[1,:])
    Tk      = Fourier_Transform_2D(f_inter)
    tol     = 10^-10
    indeces = findall(abs.(Tk).<tol)
    for i in indeces
        Tk[i] = 0.0
    end
    h, h_t    = get_tensors(h_evol,Nkx,Nky)
    dh        = h_t
    dh_t      = Tk - (kx.^2 + ky.^2) .*h
    dh_t[1,1] = 0.0

    get_evol_variable(dh,dh_t)
end

#Computes the component associated to field in the bounbdary stress tensor.
#E.g. field=energy gives us h00 as solution.
function solve_GW(dirname::String, field::Symbol; dt::T, alg, option::String="static") where {T<:Real}
    ff       = VEVTimeSeries(dirname, field)
    tt, x, y = get_coords(ff,:,:,:)
    tspan    = (tt[1],tt[end])
    dx       = x[2]-x[1]
    dy       = y[2]-y[1]
    kxx      = 2π.*rfftfreq(length(x), 1/dx)
    kyy      = 2π.*fftfreq(length(y), 1/dy)
    Nkx      = length(kxx)
    Nky      = length(kyy)
    kx       = zeros(Nkx,Nky)
    ky       = zeros(Nkx,Nky)
    for j in eachindex(kyy)
        for i in eachindex(kxx)
            kx[i,j] = kxx[i]
            ky[i,j] = kyy[j]
        end
    end
    h0_evol  = initial_conditions(ff,kx,ky)
    ff_inter = interpolate((tt,x,y,), ff[:,:,:], Gridded(Linear()))
    prm      = param{typeof(ff_inter),typeof(x[1])}(ff_inter,x,y,kx,ky,dt)

    problem    = ODEProblem(rhs, h0_evol, tspan, prm)
    integrator = init(problem, alg, save_everystep=false, dt=dt, adaptive=false)

    h   = im.*zeros(Int(floor((tspan[2]-tspan[1])/dt+1)),Nkx,Nky)
    h_t = similar(h)
    Tk  = similar(h)
    n = 0
    for (u, t) in tuples(integrator)
        n += 1
        h[n,:,:], h_t[n,:,:] = get_tensors(u,Nkx,Nky)
        Tk[n,:,:]            = Fourier_Transform_2D(ff_inter(t,x,y))
        umax  = maximum(abs.(u[2:Nkx*Nky]))
        dumax = maximum(abs.(u[Nkx*Nky+2:end]))

        println("t = $t")
        println("\n")
        println("max |h| = $umax")
        println("max |h_t| = $dumax")
        println("------------------------------------------------------------")
        println("\n")
    end
    h, h_t, Tk, kxx, kyy
end


function compute_kinetics_vector(x,m,t)::Array{Float64,1}

    # initialize -
    κ = m["κ"]
    S = m["S"]
    number_of_rates = m["number_of_rates"]
    rate_vector = zeros(number_of_rates)

    # main loop -
    tmp_set = Set{Float64}()
    for j ∈ 1:number_of_rates

        # TODO: we should cache the index vectors for each rate in the model dictionary -
        idx_reactants = findall(x->x<0.0, S[:,j])
        for index ∈ idx_reactants

            σ = S[index,j]
            term = x[index]^(-σ)
            push!(tmp_set,term)
        end

        # compute the rate -
        rate_vector[j] = κ[j]*prod(tmp_set)

        # clean-up
        empty!(tmp_set)
    end

    # return -
    return rate_vector
end


#in module Units

meter = Physical(1, m=1)
kilogram = Physical(1, kg=1)
second = Physical(1, s=1)
ampere = Physical(1, A=1)
kelvin = Physical(1, K=1)
mole = Physical(1, mol=1)
candela = Physical(1, cd=1)
hertz = 1/second
newton = kilogram*meter/second^2
pascal = newton/meter^2
joule = newton*meter
watt = joule/second
coulomb = ampere * second
volt = watt/ampere
farad = coulomb/volt
ohm = volt/ampere
weber = volt*second
tesla = weber/meter^2
henry = weber/ampere
lumen = candela
lux = lumen/meter^2

NA = 6.02e23/mole
planck = 6.62607004081e-34joule/second


using LinearAlgebra: sqrt
using Printf: Threads
using Plots: Threads
using ProgressMeter
using Printf
include("PolygonBase.jl")
include("ESBase.jl")
include("silhouette&pieces.jl")

# X[1つめのピースのx座標, 1つめのピースのy座標, １つめの回転角Θ, 2つめのx座標, ...]
function loss_poly(X::Array)
    global pices, silhouette

    # silhouetteとpieceの共通部分の面積を求める
    tmp_p = move(pieces[1], X[1], X[2])
    rotate!(tmp_p, X[3])
    unioned = MYPolygon2LibGEOS(tmp_p)
    sum_pieces = LibGEOS.area(MYPolygon2LibGEOS(tmp_p))

    for i in 2:length(pieces)
        tmp_p = move(pieces[i], X[3i-2], X[3i-1])
        rotate!(tmp_p, X[3i])
        unioned = LibGEOS.union(unioned, MYPolygon2LibGEOS(tmp_p))
        sum_pieces += MYPolygon2LibGEOS(tmp_p) |> LibGEOS.area
    end
    tmp = LibGEOS.intersection(unioned, MYPolygon2LibGEOS(silhouette))
    A = LibGEOS.area(tmp) / LibGEOS.area(MYPolygon2LibGEOS(silhouette))

    return -A * 100
end

silhouette = tri_l
pieces = [tri_s, tri_s, square_s]

# 初期化
max_gen = 128
sample_num = 512

# 最終結果を保存するか否か
save_results = true
if save_results
    rm("outputs", force=true, recursive=true)
    mkdir("outputs")

    best_fitnesses = Array{Float64, 1}()
    correct_time = 0
    p = Progress(sample_num)
    for t in 1:sample_num
        global best_fitnesses, correct_time
        local cmaes, rng
        cmaes = init_CMAES(zeros(3 * length(pieces)), 0.8, 18)
        fitnesses = zeros(cmaes.dim)
        X = zeros((cmaes.dim, cmaes.λ))
        rng = MersenneTwister(t)

        for gen in 1:max_gen
            # 個体生成
            X = samplePopulation(cmaes, rng=rng)

            # 回転角のパラメータを -π ~ π までに正規化
            for j in 1:size(X)[2]
                for i in 3:3:size(X)[1]
                    X[i, j] = mod(X[i, j], 2π)
                end
            end

            # 評価値
            loss(x) = loss_poly(x)
            fitnesses = get_fitness(X, loss)

            # 更新
            update!(cmaes, X, fitnesses, gen)
        end
        push!(best_fitnesses, maximum(fitnesses))

        if -best_fitnesses[end] > 90
            correct_time += 1
        end

        # 表示用
        best_arg = argmin(fitnesses)
        best_pieces = Array{MYPolygon, 1}()
        for i in 1:length(pieces)
            todisplay = move(pieces[i], X[3i-2, best_arg], X[3i-1, best_arg])
            rotate!(todisplay, X[3i, best_arg] * 2π)
            push!(best_pieces, todisplay)
        end
        display(silhouette, best_pieces...)
        savefig(@sprintf "outputs\\%02.0f_%d_results" -best_fitnesses[end] t)

        next!(p)
    end
else
    best_fitnesses = Array{Float64, 1}()
    correct_time = Threads.Atomic{Int}(0)
    p = Progress(sample_num÷Threads.nthreads())
    Threads.@threads for t in 1:sample_num
        global best_fitnesses, correct_time
        local cmaes
        cmaes = init_CMAES(zeros(3 * length(pieces)), 1.0, 0)
        fitnesses = zeros(cmaes.dim)
        X = zeros((cmaes.dim, cmaes.λ))
        rng = MersenneTwister(t)

        for gen in 1:max_gen
            # 個体生成
            X = samplePopulation(cmaes, rng=rng)

            # 回転角のパラメータを -π ~ π までに正規化
            for j in 1:size(X)[2]
                for i in 3:3:size(X)[1]
                    X[i, j] %= mod(X[i, j], 1.0)
                end
            end

            # 評価値
            # loss(x) = loss_poly(x, loss_args)
            loss(x) = loss_poly_light(x)
            fitnesses = get_fitness(X, loss)

            # 更新
            update!(cmaes, X, fitnesses, gen)
        end
        push!(best_fitnesses, maximum(fitnesses))

        if -best_fitnesses[end] > 94
            Threads.atomic_add!(correct_time, 1)
        end

        if Threads.threadid() == 1
            next!(p)
        end
    end
end

pl = histogram(-best_fitnesses, bins = 40:5:100, xlabel="Fitness", ylabel="The number of answers", lab=false)

println("correct prob: $(correct_time[])/$(sample_num) = $(100correct_time[]/sample_num)%")

savefig(pl, "fitness_distribusion_$(sample_num)samples_$(correct_time[])-corrects.png")
Base.display(pl)


logistic(x) = 1 / (1 + exp(-x))
antilogistic(x) = 1 / (1 + log(-x))

function exp_to_log_tform(tape::Tape)
    new_tape = similar(tape)
    changed = false
    for op in tape
        if op isa Call && op.fn == exp
            changed = true
            push!(new_tape, copy_with(op, fn=log))
        else
            push!(new_tape, op)
        end
    end
    return new_tape, changed
end

@testset "transform" begin
    @test transform(t -> (t, false), logistic, 4.0) == logistic
    f1 = transform(t -> (t, true), logistic, 4.0)
    # not the same, but recompiled function
    @test f1 != logistic
    # but should still do the same thing
    @test f1(20.0) == logistic(20.0)
    # actual transformation
    f2 = transform(exp_to_log_tform, logistic, -4.0)
    @test f2(-1.0) == antilogistic(-1.0)
end


@testset "Models logfunctions" begin
    rng = Random.MersenneTwister(42)
    D = 5
    p0 = MvNormal(0.5 * ones(D))
    likelihood = MvNormal(7.0 * ones(D))
    logf = x -> logpdf(likelihood, x)
    m = BayesModel(p0, logf)

    x1 = randn(rng, Float64, D)
    @test integrand(m)(x1) ≈ pdf(likelihood, x1)
    @test logprior(m)(x1) ≈ logpdf(p0, x1)
    @test logjoint(m)(x1) ≈ logpdf(p0, x1) + logpdf(likelihood, x1)
    @test BQ.p_0(m) == p0
    @test logintegrand(m) == logf
end


"""
   ρSTE <: AbstractLoss

_U_nbiased Stochastic Triplet Embedding loss.

This loss implements the paper "Learning with Noisy Labels" for STE. The paper
may be found in https://proceedings.neurips.cc/paper/2013/file/3871bd64012152bfb53fdf04b401193f-Paper.pdf
"""
struct ρSTE <: AbstractLoss
    σ::Real
    constant::Float64
    ρ::Dict{Int,Float64}

    function ρSTE(;σ::S = 1/sqrt(2), ρ::Dict{Int,Float64} = Dict(-1 => 0.4, +1 => 0.4)) where {S <: Real, T <: AbstractFloat}
        σ > 0 || throw(ArgumentError("σ in STE loss must be > 0"))
        (0 ≤ ρ[-1] < 1 && 0 ≤ ρ[+1] < 1) || throw(ArgumentError("ρ₊ in ρSTE loss must be in the (0, 1) interval."))
        new(σ, 1/σ^2, ρ)
    end
end

function Base.show(io::IO, loss::ρSTE)
    println("ρSTE(σ = $(round(loss.σ, digits=3)), constant = $(round(loss.constant, digits=3)), ρ₊ = $(round(loss.ρ[1], digits=3)), ρ₋ = $(round(loss.ρ[-1], digits=3)))")
end

@doc raw"""
    function kernel(loss::ρSTE, X::AbstractMatrix)

Computes:

```math
K = \exp(\|X_i - X_j\|^2/(2\sigma^2)) \forall (i,j) \in {1,\ldots,n} \times {1,\ldots,n}.
```
"""
function kernel(loss::ρSTE, X::Embedding)
    K = pairwise(SqEuclidean(), X.X, dims=2)
    c = -loss.constant / 2

    for ij in eachindex(K)
        @inbounds K[ij] = exp(c * K[ij])
    end
    return K
end

function gradient(loss::ρSTE, triplets::LabeledTriplets, X::AbstractMatrix)

    K = kernel(loss, X) # Triplet kernel values (in the STE loss)

    # We need to create an array to prevent race conditions
    # This is the best average solution for small and big Embeddings
    nthreads = Threads.nthreads()
    triplets_range = partition(length(triplets), nthreads)

    C⁺ = zeros(Float64, nthreads)
    ∇C⁺ = [zeros(Float64, size(X)) for _ in 1:nthreads]

    Threads.@threads for tid in 1:nthreads
        C⁺[tid] = tgradient!(∇C⁺[tid], loss, triplets, X, K, triplets_range[tid]; flip_triplets=false)
    end

    C⁻ = zeros(Float64, nthreads)
    ∇C⁻ = [zeros(Float64, size(X)) for _ in 1:nthreads]

    Threads.@threads for tid in 1:nthreads
        C⁻[tid] = tgradient!(∇C⁻[tid], loss, triplets, X, K, triplets_range[tid]; flip_triplets=true)
    end

    return (sum(C⁺) + sum(C⁻)) / (1 - sum(values(loss.ρ))), -(sum(∇C⁺) + sum(∇C⁻)) / (1 - sum(values(loss.ρ)))
end

function tgradient!(
    ∇C::Matrix{<:AbstractFloat},
    loss::ρSTE,
    triplets::LabeledTriplets,
    X::AbstractMatrix,
    K::Matrix{<:AbstractFloat},
    triplets_range::UnitRange{Int64};
    flip_triplets::Bool = false,
    )

    C = 0.0

    for t in triplets_range
        @inbounds i, j, k = if triplets[t][:y] == -1
            triplets[t][:i], triplets[t][:j], triplets[t][:k]
        else
            triplets[t][:i], triplets[t][:k], triplets[t][:j]
        end

        if flip_triplets
            i, j, k = i, k, j
        end

        @inbounds P = K[i,j] / (K[i,j] + K[i,k])
        C += if flip_triplets
            -loss.ρ[triplets[t][:y]] * log(P)
        else
            -(1 - loss.ρ[-triplets[t][:y]]) * log(P)
        end

        for d in 1:ndims(X)
            @inbounds ∂x_j = (1 - P) * (X[d,i] - X[d,j])
            @inbounds ∂x_k = (1 - P) * (X[d,i] - X[d,k])

            if flip_triplets
                @inbounds ∇C[d,i] += - loss.constant * (∂x_j - ∂x_k) * loss.ρ[triplets[t][:y]]
                @inbounds ∇C[d,j] +=   loss.constant *  ∂x_j         * loss.ρ[triplets[t][:y]]
                @inbounds ∇C[d,k] += - loss.constant *  ∂x_k         * loss.ρ[triplets[t][:y]]
            else
                @inbounds ∇C[d,i] += - loss.constant * (∂x_j - ∂x_k) * (1 - loss.ρ[-triplets[t][:y]])
                @inbounds ∇C[d,j] +=   loss.constant *  ∂x_j         * (1 - loss.ρ[-triplets[t][:y]])
                @inbounds ∇C[d,k] += - loss.constant *  ∂x_k         * (1 - loss.ρ[-triplets[t][:y]])
            end
        end
    end
    return C
end


"""
    pressure(mom::JuSwalbe.Macroquant; γ = 0.01, θ = 1/9)

Film pressure of the thin film equation.

This is just the summation of the laplacian of the height `Δh` and the disjoining potential `Π`.
Since this function uses Π one has to be careful of θ as it should be an array.
However it can be either a single element array or as large as the whole domain.
See also: [`Δh`](@ref), [`Π`](@ref)

# Math
The film pressure is simply

`` p_{film} = -\\gamma\\Delta h + \\Pi(h) ``

The sign is not 100% fixed and can change from paper to paper.
Here I choose to be in agreement with Thiele et al., which is known to be a good theoretician.

# Example
```jldoctest
julia> using JuSwalbe

julia> height = reshape([i for i in 1.0:1.0:16.0],4,4)
4×4 Array{Float64,2}:
 1.0  5.0   9.0  13.0
 2.0  6.0  10.0  14.0
 3.0  7.0  11.0  15.0
 4.0  8.0  12.0  16.0

julia> p = pressure(height)
4×4 Array{Float64,2}:
 -0.200016  -0.0400001   -0.04        0.12
 -0.160002  -7.44536e-8  -1.6082e-8   0.16
 -0.160001  -4.68862e-8  -1.20826e-8  0.16
 -0.12       0.04         0.04        0.2

julia> mom = JuSwalbe.Macroquant(height=height, velocity=JuSwalbe.Twovector(x=zeros(4,4), y=zeros(4,4)), pressure=zeros(4,4), energy=zeros(4,4))
JuSwalbe.Macroquant{Array{Float64,2},JuSwalbe.Twovector{Array{Float64,2}}}
  height: Array{Float64}((4, 4)) [1.0 5.0 9.0 13.0; 2.0 6.0 10.0 14.0; 3.0 7.0 11.0 15.0; 4.0 8.0 12.0 16.0]
  velocity: JuSwalbe.Twovector{Array{Float64,2}}
  pressure: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]
  energy: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]

julia> mom.pressure
4×4 Array{Float64,2}:
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0

julia> pressure(mom) # Pressure is stored in the moments struct!
4×4 Array{Float64,2}:
 -0.200016  -0.0400001   -0.04        0.12
 -0.160002  -7.44536e-8  -1.6082e-8   0.16
 -0.160001  -4.68862e-8  -1.20826e-8  0.16
 -0.12       0.04         0.04        0.2

julia> mom.pressure
4×4 Array{Float64,2}:
 -0.200016  -0.0400001   -0.04        0.12
 -0.160002  -7.44536e-8  -1.6082e-8   0.16
 -0.160001  -4.68862e-8  -1.20826e-8  0.16
 -0.12       0.04         0.04        0.2

```
So what does this mean?
A positive pressure is force that drives the film down in height.
While a negative pressure generates a flux towards that location.
In the example a linear increasing height field was used, an equilibrium though is reached when the film is flat.

# References
## Recent (short)
- [Signatures of slip in dewetting polymer films](https://www.pnas.org/content/116/19/9275)

## Review
- [Dynamics and stability of thin liquid films](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.81.1131)
"""
function pressure(mom::JuSwalbe.Macroquant{Matrix{T}, JuSwalbe.Twovector{Matrix{T}}}; γ::T=T(0.01), θ::Matrix{T}=ones(T,(1,1))*T(1/9)) where {T<:Number}
    # Dummy array to store the result
    p = zeros(T, size(mom.height))
    # All calculation needed here
    p = -γ * Δh(mom) .+ Π(mom, γ=γ, θ=θ)
    # Write the solution into the struct as well
    mom.pressure = p

    return p
end

function pressure(height::Array{T,2}; γ::T=0.01, θ::Matrix{T}=ones(T,(1,1))*T(1/9)) where {T<:Number}
    # Dummy array to store the result
    p = zeros(size(height))
    # All calculation needed here
    p = -γ * Δh(height) .+ Π(height, γ=γ, θ=θ)

    return p
end
# One dimensional implementation
function pressure(mom::JuSwalbe.Macroquant{Vector{T}, Vector{T}}; γ::T=T(0.01), θ::Vector{T}=ones(T,1)*T(1/9)) where {T<:Number}
    # Dummy array to store the result
    p = zeros(T, length(mom.height))
    # All calculation needed here
    p = -γ * Δh(mom) .+ Π(mom, γ=γ, θ=θ)
    # Write the solution into the struct as well
    mom.pressure = p

    return p
end

function pressure(height::Array{T,1}; γ::T=0.01, θ::Vector{T}=ones(T,1)*T(1/9)) where {T<:Number}
    # Dummy array to store the result
    p = zeros(size(height))
    # All calculation needed here
    p = -γ * Δh(height) .+ Π(height, γ=γ, θ=θ)

    return p
end

"""
    ∇p(mom::JuSwalbe.Macroquant)

Computation of the pressure gradient in one and two spatial dimensions.

Given a struct `Macroquant` with an `.pressure` entry performs a finite difference approximation of the spatial derivative.
A five-point stencil is used for one spatial dimension.
For two spatial dimensions a weighted nine-point stencil is applied.
An perodicity around the edges is assumed (ring, torus).

# Math
Since this is essentially discrete operators I will not go into details.
## Two spatial dimensions

`` \\nabla p = 3 \\sum_{i=0}^8 w_i \\mathbf{c}_i p(\\mathbf{r}+\\mathbf{c}_i) +O(\\nabla^3). ``

Where `` \\mathbf{c}_i, w_i `` are the standard D2Q9 lattice velocities and weights.
Of course this vector `` \\nabla p `` needs to be multiple with the height field to yield the correct force.

## One spatial dimension

`` \\partial_x p = \\frac{p(x-2\\Delta x) - 8p(x-1\\Delta x) + 0p(x) + 8p(x+1\\Delta x) - p(x+2\\Delta x)}{12\\Delta x} + O(\\partial_x^5). ``

Where the step size is limited by the resolution of the lattice Boltzmann grid `` \\Delta x ``.

# Example
## Two spatial dimensions
```jldoctest
julia> using JuSwalbe

julia> zerovelocity = JuSwalbe.Twovector(x=zeros(5,5), y=zeros(5,5)) # Thanks to @with_kw macro
JuSwalbe.Twovector{Array{Float64,2}}
  x: Array{Float64}((5, 5)) [0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0; … ; 0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0]
  y: Array{Float64}((5, 5)) [0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0; … ; 0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0]

julia> mom = JuSwalbe.Macroquant(height=fill(1.0, (5,5)), velocity=zerovelocity, pressure=zerovelocity.x, energy=zerovelocity.y)
JuSwalbe.Macroquant{Array{Float64,2},JuSwalbe.Twovector{Array{Float64,2}}}
  height: Array{Float64}((5, 5)) [1.0 1.0 … 1.0 1.0; 1.0 1.0 … 1.0 1.0; … ; 1.0 1.0 … 1.0 1.0; 1.0 1.0 … 1.0 1.0]
  velocity: JuSwalbe.Twovector{Array{Float64,2}}
  pressure: Array{Float64}((5, 5)) [0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0; … ; 0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0]
  energy: Array{Float64}((5, 5)) [0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0; … ; 0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0]

julia> mom.height[2,3] = 2.0; mom.height[3,2] = 2.0; mom.height[4,3] = 2.0; mom.height[3,4] = 2.0
2.0

julia> p = pressure(mom)
5×5 Array{Float64,2}:
 -1.6082e-5   -0.00168275  -0.00668275  -0.00168275  -1.6082e-5
 -0.00168275  -0.0133494    0.029998    -0.0133494   -0.00168275
 -0.00668275   0.029998    -0.0266827    0.029998    -0.00668275
 -0.00168275  -0.0133494    0.029998    -0.0133494   -0.00168275
 -1.6082e-5   -0.00168275  -0.00668275  -0.00168275  -1.6082e-5

julia> mom.pressure
5×5 Array{Float64,2}:
 -1.6082e-5   -0.00168275  -0.00668275  -0.00168275  -1.6082e-5
 -0.00168275  -0.0133494    0.029998    -0.0133494   -0.00168275
 -0.00668275   0.029998    -0.0266827    0.029998    -0.00668275
 -0.00168275  -0.0133494    0.029998    -0.0133494   -0.00168275
 -1.6082e-5   -0.00168275  -0.00668275  -0.00168275  -1.6082e-5

julia> f = JuSwalbe.Forces(slip=zerovelocity, h∇p=zerovelocity, thermal=zerovelocity, bathymetry=zerovelocity)
JuSwalbe.Forces{JuSwalbe.Twovector{Array{Float64,2}}}
  slip: JuSwalbe.Twovector{Array{Float64,2}}
  h∇p: JuSwalbe.Twovector{Array{Float64,2}}
  bathymetry: JuSwalbe.Twovector{Array{Float64,2}}
  thermal: JuSwalbe.Twovector{Array{Float64,2}}

julia> gradp = - ∇p(mom, f)
5×5×2 Array{Float64,3}:
[:, :, 1] =
  0.00166667    0.00097105  -0.0102825    0.00097105   0.00166667
  0.000137716  -0.00833802   0.00277309  -0.00833802   0.000137716
 -0.0          -0.0         -0.0         -0.0         -0.0
 -0.000137716   0.00833802  -0.00277309   0.00833802  -0.000137716
 -0.00166667   -0.00097105   0.0102825   -0.00097105  -0.00166667

[:, :, 2] =
  0.00166667   0.000137716  -0.0  -0.000137716  -0.00166667
  0.00097105  -0.00833802   -0.0   0.00833802   -0.00097105
 -0.0102825    0.00277309   -0.0  -0.00277309    0.0102825
  0.00097105  -0.00833802   -0.0   0.00833802   -0.00097105
  0.00166667   0.000137716  -0.0  -0.000137716  -0.00166667

julia> f.h∇p.x # The force comes with sign
5×5 Array{Float64,2}:
  0.00166667    0.00097105  -0.0102825    0.00097105   0.00166667
  0.000137716  -0.00833802   0.00277309  -0.00833802   0.000137716
 -0.0          -0.0         -0.0         -0.0         -0.0
 -0.000137716   0.00833802  -0.00277309   0.00833802  -0.000137716
 -0.00166667   -0.00097105   0.0102825   -0.00097105  -0.00166667

julia> f.h∇p.y # Same as for the x values
5×5 Array{Float64,2}:
  0.00166667   0.000137716  -0.0  -0.000137716  -0.00166667
  0.00097105  -0.00833802   -0.0   0.00833802   -0.00097105
 -0.0102825    0.00277309   -0.0  -0.00277309    0.0102825
  0.00097105  -0.00833802   -0.0   0.00833802   -0.00097105
  0.00166667   0.000137716  -0.0  -0.000137716  -0.00166667

```

## One spatial dimension
```jldoctest
julia> using JuSwalbe

julia> mom = JuSwalbe.Macroquant(height=ones(10), velocity=zeros(10), pressure=zeros(10), energy=zeros(10))
JuSwalbe.Macroquant{Array{Float64,1},Array{Float64,1}}
  height: Array{Float64}((10,)) [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
  velocity: Array{Float64}((10,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  pressure: Array{Float64}((10,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  energy: Array{Float64}((10,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

julia> f = JuSwalbe.Forces(slip=zeros(10), h∇p=zeros(10), thermal=zeros(10), bathymetry=zeros(10))
JuSwalbe.Forces{Array{Float64,1}}
  slip: Array{Float64}((10,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  h∇p: Array{Float64}((10,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  bathymetry: Array{Float64}((10,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  thermal: Array{Float64}((10,)) [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

julia> mom.height[4] = 0.5; mom.height[5] = 2.0; mom.height[6] = 0.5;

julia> p = pressure(mom, θ=zeros(1), γ=1.0)
10-element Array{Float64,1}:
 -0.0
 -0.0
  0.5
 -2.0
  3.0
 -2.0
  0.5
 -0.0
 -0.0
 -0.0

julia> gradp = ∇p(mom, f)
10-element Array{Float64,1}:
 -0.041666666666666664
  0.5
 -1.5833333333333333
  0.9166666666666666
  0.0
 -0.9166666666666666
  1.5833333333333333
 -0.5
  0.041666666666666664
  0.0

```

# References
## Two spatial dimensions
- [Isotropic discrete Laplacian operators from lattice hydrodynamics](https://www.sciencedirect.com/science/article/pii/S0021999112004226)
- [Discretizations for the Incompressible Navier--Stokes Equations Based on the Lattice Boltzmann Method](https://epubs.siam.org/doi/abs/10.1137/S1064827599357188)

The second reference is the older and more mathematically one, while the first reference is short and great for a fast look up.

## One spatial dimension
- [Numerical Differentiation](http://www2.math.umd.edu/~dlevy/classes/amsc466/lecture-notes/differentiation-chap.pdf)

Any lecture notes or book on discrete mathematics should do for the one dimensional case.
"""
function ∇p(mom::JuSwalbe.Macroquant{Vector{T},Vector{T}}, force::JuSwalbe.Forces{Vector{T}}) where {T<:Number}
    # A (deep) copy of the pressure field
    p = deepcopy(mom.pressure)
    len = length(p)
    gradp = zeros(T, len)
    # Periodic extension of pressure, for higher order differential
    pushfirst!(p, p[len]) # Add last element on first spot
    pushfirst!(p, p[len]) # Add second last element on first spot
    push!(p, p[3])
    push!(p, p[4])
    ∇pout = zeros(T, length(p))
    # Five point first derivative
    for i in 3:len+2
        ∇pout[i] = T(1.0/12.0) * (p[i-2] + 8*p[i+1] - 8*p[i-1] - p[i+2])
    end
    gradp = ∇pout[3:len+2]
    # Write force to force struct
    force.h∇p = - mom.height .* gradp
    # Return result
    return  mom.height .* gradp
end

function ∇p(mom::JuSwalbe.Macroquant{Matrix{T},JuSwalbe.Twovector{Matrix{T}}}, force::JuSwalbe.Forces{JuSwalbe.Twovector{Matrix{T}}}) where {T<:Number}
    # A (deep) copy of the pressure field
    p = deepcopy(mom.pressure)
    width, thick = size(p)
    ∇poutx = zeros(T, (width, thick))
    ∇pouty = zeros(T, (width, thick))
    output = zeros(T, (width, thick, 2))
    # Periodic extension of pressure, for higher order differential
    wrapper = 1
    pperiodic = padarray(p, Pad(:circular, wrapper, wrapper))
    # Five point first derivative
    @inbounds for j in 1:thick, i in 1:width
        ∇poutx[i,j] = T(3.0) * (T(1.0/9.0) * (pperiodic[i+1,j] - pperiodic[i-1,j])
                              + T(1.0/36.0) * (pperiodic[i+1,j+1] - pperiodic[i-1,j+1] - pperiodic[i-1,j-1] + pperiodic[i+1,j-1]))
        ∇pouty[i,j] = T(3.0) * (T(1.0/9.0) * (pperiodic[i,j+1] - pperiodic[i,j-1] )
                              + T(1.0/36.0) * (pperiodic[i+1,j+1] + pperiodic[i-1,j+1] - pperiodic[i-1,j-1] - pperiodic[i+1,j-1]))
    end

    # Write force to force struct
    force.h∇p = JuSwalbe.Twovector(x = .- mom.height .* ∇poutx, y = .- mom.height .* ∇pouty)
    output[:, :, 1] = mom.height .* ∇poutx
    output[:, :, 2] = mom.height .* ∇pouty
    # Return result
    return  output
end

"""
    Π(mom::JuSwalbe.Macroquant; h_star = 0.1, exponents = [9,3], γ = 0.01, θ = 1.0/9.0)

Calculates the disjoining pressure for a given surface tension and contact angle at every lattice point.

The disjoing pressure potential enables the simulation to study phenomena like dewetting of thin liquid films.
Only with this potential it is possible to "dewett" without using highly sophisticated boundary conditions at `` h\\rightarrow 0 ``.
The functional shape of this potential can vary, ranging from the powerlaw used here to exponentials to a mixture of both.
Using the powerlaw shape the exponents (9,3) mimic the Lenard-Jones potential for the liquid substrate interaction.

# Arguments
- `mom::JuSwalbe.Macroquant` : Macroscopic moments of the simulation, important here `.height` which contains the height field. Allows for array input as well.
- `h_star::T` : Minimum of the pontential. T needs to be a subtype of `Number`!
- `exponents::Array{Int64,1}` : Exponents for the powerlaw potential
- `γ::T` : Surface tension
- `θ::T` : Equilibrium contact angle in multiple of π (for accuarcy reasons). Needs to be an array when doing patterning. For simple substrate use a one entry array with correct dimension.

# Math
The potential can be derived from the assumption that a given surface energy demands an equilibrium contact angle for the fluid.
For the exact derivation take a look at the references, in principle it is the derivative of the interfacial potential with respect to `h`.

`` \\Phi'(h) = \\Pi(h) ``

`` \\Pi(h) = \\kappa f(h) = (1-\\cos\\theta)\\frac{(n-1)(m-1)}{(n-m)h_{\\ast}}\\Big[\\Big(\\frac{h_{\\ast}}{h}\\Big)^n - \\Big(\\frac{h_{\\ast}}{h}\\Big)^m\\Big] ``

`` \\Pi(h_{\\ast}) = 0 ``

# Example
```jldoctest
julia> using JuSwalbe

julia> n,m = (4,4)
(4, 4)

julia> moment = JuSwalbe.Macroquant(height=ones(n,m), velocity=JuSwalbe.Twovector(x=zeros(n,m), y=zeros(n,m)), pressure=zeros(n,m), energy=zeros(n,m))
JuSwalbe.Macroquant{Array{Float64,2},JuSwalbe.Twovector{Array{Float64,2}}}
  height: Array{Float64}((4, 4)) [1.0 1.0 1.0 1.0; 1.0 1.0 1.0 1.0; 1.0 1.0 1.0 1.0; 1.0 1.0 1.0 1.0]
  velocity: JuSwalbe.Twovector{Array{Float64,2}}
  pressure: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]
  energy: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]

julia> p = Π(moment)
4×4 Array{Float64,2}:
 -1.6082e-5  -1.6082e-5  -1.6082e-5  -1.6082e-5
 -1.6082e-5  -1.6082e-5  -1.6082e-5  -1.6082e-5
 -1.6082e-5  -1.6082e-5  -1.6082e-5  -1.6082e-5
 -1.6082e-5  -1.6082e-5  -1.6082e-5  -1.6082e-5

julia> p = Π(moment, θ=zeros(1,1)) # Fully wetting substrate
4×4 Array{Float64,2}:
 -0.0  -0.0  -0.0  -0.0
 -0.0  -0.0  -0.0  -0.0
 -0.0  -0.0  -0.0  -0.0
 -0.0  -0.0  -0.0  -0.0

julia> h = reshape([i for i in 1.0:(n*m)],n,m)
4×4 Array{Float64,2}:
 1.0  5.0   9.0  13.0
 2.0  6.0  10.0  14.0
 3.0  7.0  11.0  15.0
 4.0  8.0  12.0  16.0

julia> p = Π(h)
4×4 Array{Float64,2}:
 -1.6082e-5   -1.28656e-7  -2.20603e-8  -7.31997e-9
 -2.01025e-6  -7.44536e-8  -1.6082e-8   -5.86078e-9
 -5.95628e-7  -4.68862e-8  -1.20826e-8  -4.76503e-9
 -2.51281e-7  -3.14101e-8  -9.30669e-9  -3.92626e-9

```

# References
To get a good understanding:
- [Long-scale evolution of thin liquid films](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.69.931)
- [Wetting and spreading](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.81.739)
- [Dynamics and stability of thin liquid films](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.81.1131)

A rather recent new setup for the shape of `Π` can be found in
- [Signatures of slip in dewetting polymer films](https://www.pnas.org/content/116/19/9275)
"""
function Π(mom::JuSwalbe.Macroquant{Matrix{T}, JuSwalbe.Twovector{Matrix{T}}}; h_star::T=T(0.1), exponents=[9,3], γ::T=T(0.01), θ::Matrix{T}=ones(T,(1,1))*T(1/9)) where {T<:Number}
    # Theoretical minimum of the wetting pontential
    hbyhstar_n = ones(T, size(mom.height))
    hbyhstar_m = ones(T, size(mom.height))
    Π_h = zeros(T, size(mom.height))

    hbyhstar_n = power(h_star ./ mom.height, exponents[1])
    hbyhstar_m = power(h_star ./ mom.height, exponents[2])

    # Actual formular of the disjoining potential, long range attracion short range repulsion.
    Π_h = (γ .* (1 .- cospi.(θ))
          .* (exponents[1] - 1)*(exponents[2] - 1)/((exponents[1] - exponents[2])*h_star)
          .* (hbyhstar_n .- hbyhstar_m))

    return Π_h
end

function Π(height::Array{T,2}; h_star::T=T(0.1), exponents=[9,3], γ::T=T(0.01), θ::Matrix{T}=ones(T,(1,1))*T(1/9)) where {T<:Number}
    # Theoretical minimum of the wetting potential, two dimensional on array
    Π_h = zeros(T, size(height))

    hbyhstar_n = power(h_star ./ height, exponents[1])
    hbyhstar_m = power(h_star ./ height, exponents[2])
    # Actual formular of the disjoining potential, long range attracion short range repulsion.
    Π_h = (γ .* (1 .- cospi.(θ))
          .* (exponents[1] - 1)*(exponents[2] - 1) / ((exponents[1] - exponents[2])*h_star)
          .* (hbyhstar_n .- hbyhstar_m))

    return Π_h
end
# One dimensional implementation
function Π(mom::JuSwalbe.Macroquant{Vector{T}, Vector{T}}; h_star::T=T(0.1), exponents=[9,3], γ::T=T(0.01), θ::Vector{T}=ones(T,1)*T(1/9)) where {T<:Number}
    # Theoretical minimum of the wetting pontential
    Π_h = zeros(T, length(mom.height))

    hbyhstar_n = power(h_star ./ mom.height, exponents[1])
    hbyhstar_m = power(h_star ./ mom.height, exponents[2])
    # Actual formular of the disjoining potential, long range attracion short range repulsion.
    Π_h = (γ .* (1 .- cospi.(θ))
          .* (exponents[1] - 1)*(exponents[2] - 1) / ((exponents[1] - exponents[2])*h_star)
          .* (hbyhstar_n .- hbyhstar_m))

    return Π_h
end

function Π(height::Array{T,1}; h_star::T=T(0.1), exponents=[9,3], γ::T=T(0.01), θ::Vector{T}=ones(T,1)*T(1/9)) where {T<:Number}
    # Theoretical minimum of the wetting potential, one dimensional on array
    Π_h = zeros(T, length(height))

    hbyhstar_n = power(h_star ./ height, exponents[1])
    hbyhstar_m = power(h_star ./ height, exponents[2])
    # Actual formular of the disjoining potential, long range attracion short range repulsion.
    Π_h = (γ .* (1 .- cospi.(θ))
          .* (exponents[1] - 1)*(exponents[2] - 1) / ((exponents[1] - exponents[2])*h_star)
          .* (hbyhstar_n .- hbyhstar_m))

    return Π_h
end

"""
  power(array, n)

Computes the `n`th power elementwise of an `array`.

Turns out this is about **two** times faster than using `.^`, the build in power function.

# Example
```jldoctest
julia> using JuSwalbe

julia> h = fill(0.1, (5,5))
5×5 Array{Float64,2}:
 0.1  0.1  0.1  0.1  0.1
 0.1  0.1  0.1  0.1  0.1
 0.1  0.1  0.1  0.1  0.1
 0.1  0.1  0.1  0.1  0.1
 0.1  0.1  0.1  0.1  0.1

julia> JuSwalbe.power(h, 3)
5×5 Array{Float64,2}:
 0.001  0.001  0.001  0.001  0.001
 0.001  0.001  0.001  0.001  0.001
 0.001  0.001  0.001  0.001  0.001
 0.001  0.001  0.001  0.001  0.001
 0.001  0.001  0.001  0.001  0.001

```
"""
function power(array::Array{T, 2}, n::Int) where {T<:Number}
  temp = ones(T, size(array))
  for i = 1:n
    temp .*= array
  end
  return temp
end
function power(array::Array{T, 1}, n::Int) where {T<:Number}
  temp = ones(T, length(array))
  for i = 1:n
    temp .*= array
  end
  return temp
end

"""
    Δh(mom::JuSwalbe.Macroquant)

Calculates the laplacian of the height field with periodic boundaries.

The calculation of the laplacian is central for the thin film evolution.
Only the pressure gradient will induce a flow, at least for the non-fluctuating version.
Therefore it is fairly important to have an accurate computation of the laplacian.

# Math
The Laplace equation is given by

`` \\Delta \\rho = 0 ``

The laplace operator is simply Δ = ∇ . ∇.
Since the film pressure has a contribution Δh we need a discreticed laplace operator.

## Two spatial dimensions

In two dimensions I follow the paper from Santosh and Succi.
A nine-point stencil is used and the equation goes as follow

`` \\Delta h_{i,j} = \\frac{1}{6}[4\\sum_{nn}h_{i,j} + \\sum_{diag}h_{i,j} - 20 h_{i,j}] ``

Where `i` and `j` are the x and y coordinates.
The index `nn` relates to the nearest neighbors, all four elements which are exactly Δx away from (i,j).
On the other hand the diagonal elements are those four which are have a distance √2Δx to (i,j).
Periodicity is taken care of by a circular padding of the height array.

## One spatial dimension

For a single spatial dimension it is mostly sufficient to use the central difference approach.
This approach is given by

`` \\partial_x^2 h_{i} = h_{i-1} - 2h_{i} + h_{i+1}

with `i` being the index along the spatial dimension.

# Example
```jldoctest
julia> using JuSwalbe

julia> height = reshape(collect(1.0:16.0), (4,4))
4×4 Array{Float64,2}:
 1.0  5.0   9.0  13.0
 2.0  6.0  10.0  14.0
 3.0  7.0  11.0  15.0
 4.0  8.0  12.0  16.0

julia> velx = zeros(Float64, (4,4))
4×4 Array{Float64,2}:
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0

julia> vely = zeros(Float64, (4,4))
4×4 Array{Float64,2}:
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0

julia> vel = JuSwalbe.Twovector(x=velx, y=vely)
JuSwalbe.Twovector{Array{Float64,2}}
  x: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]
  y: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]

julia> moment = JuSwalbe.Macroquant(height=height, velocity=vel, pressure=zeros(4,4), energy=zeros(4,4))
JuSwalbe.Macroquant{Array{Float64,2},JuSwalbe.Twovector{Array{Float64,2}}}
  height: Array{Float64}((4, 4)) [1.0 5.0 9.0 13.0; 2.0 6.0 10.0 14.0; 3.0 7.0 11.0 15.0; 4.0 8.0 12.0 16.0]
  velocity: JuSwalbe.Twovector{Array{Float64,2}}
  pressure: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]
  energy: Array{Float64}((4, 4)) [0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0]

julia> moment.height
4×4 Array{Float64,2}:
 1.0  5.0   9.0  13.0
 2.0  6.0  10.0  14.0
 3.0  7.0  11.0  15.0
 4.0  8.0  12.0  16.0

julia> laplace = Δh(moment)
4×4 Array{Float64,2}:
 20.0   4.0   4.0  -12.0
 16.0   0.0   0.0  -16.0
 16.0   0.0   0.0  -16.0
 12.0  -4.0  -4.0  -20.0

julia> height = collect(1.0:16.0)
16-element Array{Float64,1}:
  1.0
  2.0
  3.0
  4.0
  5.0
  6.0
  7.0
  8.0
  9.0
 10.0
 11.0
 12.0
 13.0
 14.0
 15.0
 16.0

julia> Δh(height)
16-element Array{Float64,1}:
  16.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
   0.0
 -16.0

```

# References
## Two spatial dimensions
There are plenty of papers concerning discret differentail operators.
Many of them are good, although definitly not the best I go here with:
- [Isotropic discrete Laplacian operators from lattice hydrodynamics](https://www.sciencedirect.com/science/article/pii/S0021999112004226)

## One spatial dimension
Almost any reference on discrete differentiation is good enough.
- [Numerical Differentiation](http://www2.math.umd.edu/~dlevy/classes/amsc466/lecture-notes/differentiation-chap.pdf)

"""
function Δh(mom::JuSwalbe.Macroquant{Matrix{T}, Twovector{Matrix{T}}}) where {T<:Number}
    # Get the size of the problem
    width, thick = size(mom.height)
    # Build a wrap around to mimic periodic boundaries
    wrapper = 1
    toruswrapped = padarray(mom.height, Pad(:circular, wrapper, wrapper))
    # Generate a dummy array for the solution
    Δ = zeros(T, size(toruswrapped))
    # Exclude the wrapped boundaries
    for j in 1:width, i in 1:thick
        @inbounds Δ[i,j] = 1.0/6.0 * (4.0 * (toruswrapped[i+1, j] + toruswrapped[i-1, j] + toruswrapped[i, j+1] + toruswrapped[i, j-1])
                                     + 1.0 * (toruswrapped[i+1, j+1] + toruswrapped[i+1, j-1] + toruswrapped[i-1, j+1] + toruswrapped[i-1, j-1])
                                     - 20.0 * toruswrapped[i,j])
    end
    # Return the result without the wrapped boundaries
    return Δ[1:(end-2), 1:(end-2)]
end

function Δh(height::Array{T,2}) where {T<:Number}
    # Get the size of the problem
    width, thick = size(height)
    # Build a wrap around to mimic periodic boundaries
    wrapper = 1
    toruswrapped = padarray(height, Pad(:circular, wrapper, wrapper))
    # Generate a dummy array for the solution
    Δ = zeros(T, size(toruswrapped))
    # Exclude the wrapped boundaries
    for j in 1:width, i in 1:thick
        @inbounds Δ[i,j] = 1.0/6.0 * (4.0 * (toruswrapped[i+1, j] + toruswrapped[i-1, j] + toruswrapped[i, j+1] + toruswrapped[i, j-1])
                                     + 1.0 * (toruswrapped[i+1, j+1] + toruswrapped[i+1, j-1] + toruswrapped[i-1, j+1] + toruswrapped[i-1, j-1])
                                     - 20.0 * toruswrapped[i,j])
    end
    # Return the result without the wrapped boundaries
    return Δ[1:(end-2), 1:(end-2)]
end

# The one dimensonal cases
function Δh(mom::JuSwalbe.Macroquant{Vector{T}, Vector{T}}) where {T<:Number}
    # Get the size of the problem
    len = length(mom.height)
    # Build a wrap around to mimic periodic boundaries
    wrapper = 1
    # Ensures stability of height
    copyheight = deepcopy(mom.height)
    # Extend the one dimensional array periodic
    pushfirst!(copyheight, last(copyheight))
    push!(copyheight, copyheight[2])
    # Generate a dummy array for the solution
    Δ = zeros(T, size(copyheight))
    # Exclude the wrapped boundaries
    for i in 2:len+1
        @inbounds Δ[i] = copyheight[i-1] - 2*copyheight[i] + copyheight[i+1]
    end
    # Return the result without the wrapped boundaries
    return Δ[2:(end-1)]
end

function Δh(height::Array{T,1}) where {T<:Number}
    # Get the size of the problem
    len = length(height)
    # Build a wrap around to mimic periodic boundaries
    wrapper = 1
    # Ensures stability of height
    copyheight = deepcopy(height)
    # Extend the one dimensional array periodic
    pushfirst!(copyheight, last(copyheight))
    push!(copyheight, copyheight[2])
    # Generate a dummy array for the solution
    Δ = zeros(T, size(copyheight))
    # Exclude the wrapped boundaries
    for i in 2:len+1
        @inbounds Δ[i] = copyheight[i-1] - 2*copyheight[i] + copyheight[i+1]
    end
    # Return the result without the wrapped boundaries
    return Δ[2:(end-1)]
end


include("diffusion.jl")

function assembleDiffusionMatrix(gamma, phi, gradPhi)
    println("Assembling the diffusion matrix")
    fluxCf, fluxFf, fluxVf = assembleDiffusionTerm(gamma, phi, gradPhi)

    A = zeros(mesh.nCells, mesh.nCells)
    B = zeros(mesh.nCells)

    for iFace in 1:mesh.nFaces
        iOwner = mesh.faces[iFace].iOwner
        iNeighbour = mesh.faces[iFace].iNeighbour
        A[iOwner, iOwner] += fluxCf[iFace]
        B[iOwner] += -fluxVf[iFace]
        if iNeighbour != -1
            A[iNeighbour, iNeighbour] += fluxCf[iFace]
            A[iOwner, iNeighbour] += fluxFf[iFace]
            A[iNeighbour, iOwner] += fluxFf[iFace]
        end
    end
    return A, B
end


@doc raw"""Legendre basis

In the original paper [L. Boehnke et al., PRB 84, 075145 (2011)],
they used:

    G(\tau) = \sum_{l=0} \sqrt{2l+1} P_l[x(\tau)] G_l/beta,

where P_l[x] is the $l$-th Legendre polynomial.

In this type, the basis functions are defined by

    U_l(\tau) \equiv c_l (\sqrt{2l+1}/beta) * P_l[x(\tau)],

where c_l are additional l-dependent constant factors.
By default, we take c_l = 1, which reduces to the original definition.
"""
struct LegendreBasis{T<:AbstractFloat} <: AbstractBasis
    statistics::Statistics
    β::Float64
    cl::Vector{T}
    u::PiecewiseLegendrePolyVector{T}
    uhat::PiecewiseLegendreFTVector{T}
end

function LegendreBasis(
    statistics::Statistics,
    beta::Float64,
    size::Int;
    cl::Vector{Float64}=ones(Float64, size),
)
    beta > 0 || throw(DomainError(beta, "inverse temperature beta must be positive"))
    size > 0 || throw(DomainError(size, "size of basis must be positive"))

    # u
    knots = Float64[0, beta]
    data = zeros(Float64, size, length(knots) - 1, size)
    symm = (-1) .^ collect(0:(size - 1))
    for l in 1:size
        data[l, 1, l] = sqrt(((l - 1) + 0.5) / beta) * cl[l]
    end
    u = PiecewiseLegendrePolyVector(data, knots; symm)

    # uhat
    uhat_base = PiecewiseLegendrePolyVector(sqrt(beta) .* data, Float64[-1, 1]; symm)
    even_odd = Dict(fermion => :odd, boson => :even)[statistics]
    uhat = hat.(uhat_base, even_odd)

    return LegendreBasis(statistics, beta, cl, u, uhat)
end

function Base.getproperty(obj::LegendreBasis, d::Symbol)
    if d === :v
        return nothing
    else
        return getfield(obj, d)
    end
end

iswellconditioned(basis::LegendreBasis) = true

function default_tau_sampling_points(basis::LegendreBasis)
    x = gauss(length(basis.u))[1]
    return (getbeta(basis) / 2) .* (x .+ 1)
end

struct _ConstTerm{T<:Number}
    value::T
end

(ct::_ConstTerm)(n::AbstractVector{<:Integer}) = fill(ct.value, (1, length(n)))
(ct::_ConstTerm)(n::Integer) = ct([n])

"""
Constant term in matsubara-frequency domain
"""
struct MatsubaraConstBasis{T<:AbstractFloat} <: AbstractBasis
    statistics::Statistics
    β::Float64
    uhat::_ConstTerm{T}
end

function MatsubaraConstBasis(statistics::Statistics, beta::Float64; value=1)
    beta > 0 || throw(DomainError(beta, "inverse temperature beta must be positive"))
    return MatsubaraConstBasis(statistics, beta, _ConstTerm(value))
end

Base.size(::MatsubaraConstBasis) = (1,)


function mpo_triangular_AF_ising()
    # exact: 0.3230659669
    # ref: Phys. Rev. Res. 3, 013041 (2021)
    t = TensorMap(zeros, ComplexF64, ℂ^2*ℂ^2, ℂ^2)
    p = TensorMap(zeros, ComplexF64, ℂ^2, ℂ^2*ℂ^2)
    t[1, 1, 2] = 1
    t[1, 2, 1] = 1
    t[2, 1, 1] = 1
    t[2, 2, 1] = 1
    t[2, 1, 2] = 1
    t[1, 2, 2] = 1
    p[1, 1, 1] = 1
    p[2, 2, 2] = 1
    T = t*p
    return T
end

function mpo_triangular_AF_ising_alternative()
    # exact: 0.3230659669
    # ref: Phys. Rev. Res. 3, 013041 (2021)
    t = TensorMap(zeros, ComplexF64, ℂ^4*ℂ^4, ℂ^4)
    t[1, 2, 3] = 1
    t[3, 1, 2] = 1
    t[2, 3, 1] = 1
    t[3, 2, 4] = 1
    t[2, 4, 3] = 1
    t[4, 3, 2] = 1
    @tensor T[-1, -2; -3, -4] := t'[-2, 1, -4] * t[-1, 1, -3]
    return T
end

"""
    mpo_triangular_AF_ising_adapter()

    generate an MPO that converts the boundary MPS between the basis of `mpo_triangular_AF_ising_alternative()` and the basis of `mpo_triangular_AF_ising()`
"""
function mpo_triangular_AF_ising_adapter()

    # basis (s1, s2) to basis s
    p = TensorMap(zeros, ComplexF64, ℂ^4*ℂ^4, ℂ^4)
    m = TensorMap(zeros, ComplexF64, ℂ^2, ℂ^4*ℂ^4)

    p[1, 1, 1] = p[2, 2, 3] = p[3, 3, 2] = p[4, 4, 4] = 1
    m[1, 1, 1] = m[1, 1, 2] = m[1, 3, 1] = m[1, 3, 2] = 1
    m[2, 2, 3] = m[2, 2, 4] = m[2, 4, 3] = m[2, 4, 4] = 1
    @tensor T[-1, -2; -3, -4] := p[-1, 1, -3] * m[-2, 1, -4]

    # basis s to basis (s1, s2)
    p = TensorMap(zeros, ComplexF64, ℂ^2*ℂ^2, ℂ^2)
    m = TensorMap(zeros, ComplexF64, ℂ^4, ℂ^2*ℂ^2)

    p[1, 1, 1] = p[2, 2, 2] = 1
    m[1, 1, 1] = m[2, 2, 1] = m[3, 1, 2] = m[4, 2, 2] = 1
    @tensor Trev[-1, -2; -3, -4] := p[-1, 1, -3] * m[-2, 1, -4]

    return T, Trev

end

function mpo_kink_processor(decay_rate::Float64)
    gate = TensorMap(zeros, ℂ^2*ℂ^2, ℂ^2*ℂ^2)
    gate[1,1,1,1] = gate[2,2,2,2] = exp(-decay_rate)
    gate[1,2,1,2] = gate[2,1,2,1] = 1

    u, s, v = tsvd(permute(gate, (1,3), (2,4)))
    p = isometry(ℂ^4, ℂ^2)
    lft = permute(u * sqrt(s) * p, (1,), (2,3))
    rft = permute(p' * sqrt(s) * v, (1,2), (3,))
    T = rft * lft
    return T
end

function mpo_square_ising(beta::Float64)
    # ising model on square lattice
    δ = TensorMap(zeros, ComplexF64, ℂ^2*ℂ^2, ℂ^2*ℂ^2)
    t = TensorMap(zeros, ComplexF64, ℂ^2, ℂ^2)
    δ[1, 1, 1, 1] = 1
    δ[2, 2, 2, 2] = 1
    t[1, 1] = exp(-beta)
    t[2, 2] = exp(-beta)
    t[1, 2] = exp(beta)
    t[2, 1] = exp(beta)

    u, s, v = tsvd(t)
    u = u * sqrt(s)
    v = sqrt(s) * v

    @tensor T[-1, -2; -3, -4] := δ[1, 2, 3, 4] * v[-1, 1] * v[-2, 2] * u[3, -3] * u[4, -4]
    return T
end


using CSV

function LoadLowSchwarzschildQnms()
    A = CSV.read(joinpath(@__DIR__, "SwData.csv"),NamedTuple)
    ωlist = Dict{NamedTuple,Complex{Float64}}()
    for i=1:length(A[:l])
        merge!(ωlist,Dict((l = A[:l][i], s = A[:s][i], n = A[:n][i]) => A[:ωreal][i]+im*A[:ωimag][i]))
    end
    ωlist
end

function Low_l_Guess(l,n,s)::Complex{Float64}
    SchwMODESLOWWℓ[(l=l,s=s,n=n)]
end

SchwMODESLOWWℓ = LoadLowSchwarzschildQnms()

function High_l_Guess(l,n,s)
    #Taken Directly from Eq: 17-22 of https://arxiv.org/pdf/0908.0329.pdf
    L = l+0.5
    N = n+0.5
    β = 1-s^2

    ω₋₁ = 1
    ω₀ᵢ = 1
    ω₁ = β/3 - (5/36)*N^2 - (115/432)
    ω₂ᵢ = β/9 + (235/3888)*N^2 - (1415/15552)
    ω₃ = -(1/27)*β^2 + β*(204*N^2 +211)/3888 + (854160*N^4 - 1664760*N^2 - 776939)/(40310784)
    ω₄ᵢ = (1/27)*β^2 + (1100*N^2 - 2719)/(46656) + (11273136*N^4 - 52753800*N^2 + 66480535)/(2902376448)

    ω = (ω₋₁*L + ω₁*(L^(-1)) + ω₃*(L^(-3))) - im*N*(ω₀ᵢ + ω₂ᵢ*(L^(-2)) - ω₄ᵢ*(L^(-4)))
    ω = (1/sqrt(27))*ω
end

function High_n_Guess(l,n,s)::Complex{Float64}
    print("Used higher n guess")
    # From H.-J. Blome, B. Mashhoon, Phys. Lett. A 110, 231 (1984)
    return (0.5 + l - im*(0.5 + n))/(3.0*sqrt(3))
end

function SchwarzschildGuess(l::Int64,n::Int64,s::Int64)::Complex{Float64}
    if l >= 3
        return High_l_Guess(l,n,s)::Complex{Float64}
    else
        return Low_l_Guess(l,n,s)::Complex{Float64}
    end
end

function SolveForParams(l,m,n,s,a,ωguess,Alm,Nmax,lmax)
    let l=l,m=m,n=n,s=s,a=a,Alm=Alm,Nmax=Nmax,lmax=lmax,ωguess=ωguess
        function f!(F,x)
            val = CF(l,m,n,s,a,x[1]+im*x[2],Alm,Nmax,lmax)
            F[1] = real(val)
            F[2] = imag(val)
        end
        sol = nlsolve(f!, [real(ωguess),imag(ωguess)],ftol = 1e-14, method = :newton)
        ωnews = sol.zero
        ωnews[1]+im*ωnews[2]
    end
end

function CF(l,m,n,s,a,ω,Alm,Nmax,lmax)
    let l=l,m=m,n=n,s=s,a=a,ω=ω,Alm=Alm,Nmax=Nmax,lmax=lmax
        _,_,(D₀,D₁,D₂,D₃,D₄) = ParameterTransformations(l,m,n,s,a,ω,Alm,Nmax,lmax)
        N = Nmax;
        αₙ(n) = n^2 + (D₀+1)*n + D₀
        βₙ(n) = -2*n^2 + (D₁+2)*n +D₃
        γₙ(n) = n^2 + (D₂-3)*n  + D₄ - D₂ + 2
        u1,u2,u3,u4 = rNCoeffs(D₀,D₁,D₂,D₃,D₄)
        rN = 1 + u1*N^(-0.5) + u2*N^(-1) + u3*N^(1.5) + u4*N^(-2)
        ContinuedFraction(αₙ,βₙ,γₙ,n,N; rN = rN)
    end
end

function SchwarzschildModes(l,n,s)
    ωguess = SchwarzschildGuess(l,n,s)::Complex{Float64}
    ωguess = Complex(ωguess)
    #println(ωguess)
    Alm = l*(l+1) - s*(s+1)
    lmax = 12; m=0; a = 0

    NmaxTrials = [100,300,500,1000,5_000,10_000,50_000,100_000]
    ω₀ = ωguess; Alm₀ = Alm
    ωnew = ωguess + 1.0;
    iterations = 1;
    while (abs(ωnew-ω₀) > 1e-10) & (iterations < 8)
        Nmax = NmaxTrials[iterations]
        ω₀ = ωnew;
        ωnew = SolveForParams(l,m,n,s,a,ωguess,Alm,Nmax,lmax)
        iterations += 1
        #println("ω diff = ",abs(ωnew-ω₀))
    end
    #println("Number of Iterations = ", iterations)
    ωnew, Alm
end


struct CompletePolyDR{S<:Grid,T<:Grid,nx} <: AbstractDecisionRule{S,T,nx}
    grid_exo::S
    grid_endo::T
    coefs::Vector{Matrix{Float64}}
    order::Int
end

#####
##### 1-argument decision rule
#####

function CompletePolyDR(
        grid_exo::EmptyGrid, grid_endo::Grid{ns},
        ::Union{Val{nx},Type{Val{nx}}}, order::Int=3
    ) where ns where nx
    coeffs = [Array{Float64}(undef, BM.n_complete(ns, order), nx)]
    CompletePolyDR{EmptyGrid,typeof(grid_endo),nx}(grid_exo, grid_endo, coeffs, order)
end

function set_values!(
        dr::CompletePolyDR{<:G}, values::Vector{Matrix{Float64}}
    ) where G <: Union{<:EmptyGrid,<:UnstructuredGrid}
    # TODO the following should be once for all with the result stored in dr
    B_grid = BM.complete_polynomial(nodes(Matrix,dr.grid_endo), dr.order)
    for i in 1:length(values)
        dr.coefs[i][:,:] = B_grid\values[i]
        # TODO: understand why the following seems to modify values[i]
        # q_B_grid = qr(B_grid, Val(true))
        # ldiv!(dr.coefs[i], q_B_grid, values[i])
    end
end

function set_values!(
        dr::CompletePolyDR{<:G,<:Grid,nx},
        values::Vector{<:Array{Value{nx}}}
    ) where G <: Union{EmptyGrid,UnstructuredGrid} where nx
    B_grid = BM.complete_polynomial(nodes(Matrix,dr.grid_endo), dr.order)
    # q_B_grid = qr(B_grid, Val(true))

    if length(values) != length(dr.coefs)
        msg = "The length of values ($(length(values))) is not the same "
        msg *= "as the length of the coefficient Vector ($(length(dr.coefs)))"
        error(msg)
    end

    for i in 1:length(values)
        N = length(values[i])
        # data = copy(reshape(reinterpret(Float64, vec(values[i])), (nx, N))')
        data = copy(from_LOP(values[i]))
        dr.coefs[i] = B_grid \ data
        # ldiv!(dr.coefs[i], q_B_grid, data)
    end
end

function evaluate(dr::CompletePolyDR{<:EmptyGrid}, z::AbstractMatrix{Float64})
    B = BM.complete_polynomial(z, dr.order)
    B*dr.coefs[1]
end

function evaluate(dr::CompletePolyDR{<:EmptyGrid,<:Grid{d}}, points::AbstractVector{Point{d}}) where d
    N = length(points)
    mat = copy(reshape(reinterpret(Float64, vec(points)), (d, N))')
    evaluate(dr, mat)
end

####
#### UnstructuredGrid × CartesianGrid 2 continous arguments d.r.
####

function CompletePolyDR(
        grid_exo::S, grid_endo::Grid{ns},
        ::Union{Val{nx},Type{Val{nx}}}, order::Int=3
    ) where S <: UnstructuredGrid where ns where nx
    n_coefs = BM.n_complete(ns, order)
    coeffs = [Array{Float64}(undef, n_coefs, nx) for i in 1:n_nodes(grid_exo)]
    CompletePolyDR{S,typeof(grid_endo),nx}(grid_exo, grid_endo, coeffs, order)
end

function evaluate(dr::CompletePolyDR{<:UnstructuredGrid}, i::Int, z::AbstractMatrix{Float64})
    @boundscheck begin
        n_funcs = length(dr.coefs)
        if i > n_funcs
            msg = "Only $n_funcs are known, but function $i was requested"
            throw(BoundsError(msg))
        end
    end
    B = BM.complete_polynomial(z, dr.order)
    B*dr.coefs[i]
end

function evaluate(dr::CompletePolyDR{<:UnstructuredGrid}, i::Int, z::AbstractVector{Point{d}}) where d
    N = length(z)
    mat = reshape(reinterpret(Float64, vec(z)), (d, N))'
    evaluate(dr, i, mat)
end


@testset "noncentral beta cdf" begin
    @test ncbeta(Float32(5.0),Float32(5.0),Float32(54.0),Float32(0.8640)) ≈ Float32(0.456302)
    @test ncbeta(Float32(5.0),Float32(5.0),Float32(140.0),Float32(0.90)) ≈ Float32(0.104134)
    @test ncbeta(Float32(5.0),Float32(5.0),Float32(170.0),Float32(0.9560)) ≈ Float32(0.602235)
    @test ncbeta(Float32(10.0),Float32(10.0),Float32(140.0),Float32(0.90)) ≈ Float32(0.600811)
    @test ncbeta(Float32(10.0),Float32(10.0),Float32(250.0),Float32(0.9)) ≈ Float32(0.902850E-01)
    @test ncbeta(Float32(20.0),Float32(20.0),Float32(54.0),Float32(0.8787)) ≈ Float32(0.999865)
    @test ncbeta(Float32(20.0),Float32(20.0),Float32(140.0),Float32(0.9)) ≈ Float32(0.992600)
    @test ncbeta(Float32(20.0),Float32(20.0),Float32(250.0),Float32(0.922)) ≈ Float32(0.964111)
    @test ncbeta(Float32(10.0),Float32(20.0),Float32(150.0),Float32(0.868)) ≈ Float32(0.937663)
    @test ncbeta(Float32(10.0),Float32(10.0),Float32(120.0),Float32(0.9)) ≈ Float32(0.730682)
    @test ncbeta(Float32(15.0),Float32(5.0),Float32(80.0),Float32(0.88)) ≈ Float32(0.160426)
    @test ncbeta(Float32(20.0),Float32(10.0),Float32(110.0),Float32(0.85)) ≈ Float32(0.186749)
    @test ncbeta(Float32(20.0),Float32(30.0),Float32(65.0),Float32(0.66)) ≈ Float32(0.655939)
    @test ncbeta(Float32(20.0),Float32(50.0),Float32(130.0),Float32(0.720)) ≈ Float32(0.979688)
    @test ncbeta(Float32(30.0),Float32(20.0),Float32(80.0),Float32(0.720)) ≈ Float32(0.116239)
    @test ncbeta(Float32(30.0),Float32(40.0),Float32(130.0),Float32(0.8)) ≈ Float32(0.993043)
    @test ncbeta(Float32(10.0),Float32(5.0),Float32(20.0),Float32(0.644)) ≈ Float32(0.506899E-01)
    @test ncbeta(Float32(10.0),Float32(10.0),Float32(54.0),Float32(0.7)) ≈ Float32(0.103096)
    @test ncbeta(Float32(10.0),Float32(30.0),Float32(80.0),Float32(0.78)) ≈ Float32(0.997842)
    @test ncbeta(Float32(15.0),Float32(20.0),Float32(120.0),Float32(0.76)) ≈ Float32(0.255555)
    @test ncbeta(Float32(10.0),Float32(5.0),Float32(55.0),Float32(0.795)) ≈ Float32(0.668307E-01)
    @test ncbeta(Float32(12.0),Float32(17.0),Float32(64.0),Float32(0.56)) ≈ Float32(0.113601E-01)
    @test ncbeta(Float32(30.0),Float32(30.0),Float32(140.0),Float32(0.80)) ≈ Float32(0.781337)
    @test ncbeta(Float32(35.0),Float32(30.0),Float32(20.0),Float32(0.670)) ≈ Float32(0.886713)
    @test ncF(Float32(2.0), Float32(3.0), Float32(4.0), Float32(5.0)) ≈ Float32(0.3761448105)
end


@testset "Elastic net ($T)" for T in [Float32, Float64, ComplexF32, ComplexF64]

    using ProximalOperators
    using ProximalAlgorithms
    using LinearAlgebra

    A = T[  1.0  -2.0   3.0  -4.0  5.0;
            2.0  -1.0   0.0  -1.0  3.0;
           -1.0   0.0   4.0  -3.0  2.0;
           -1.0  -1.0  -1.0   1.0  3.0]
    b = T[1.0, 2.0, 3.0, 4.0]

    m, n = size(A)

    R = real(T)

    reg = ElasticNet(R(1), R(1))
    reg1 = NormL1(R(1))
    reg2 = SqrNormL2(R(1))
    loss = Translate(SqrNormL2(R(1)), -b)

    L = opnorm(A)^2

    x_star = T[-0.6004983388704322, 0.0, 0.0, 0.195182724252491, 0.764119601328903]

    @testset "DavisYin" begin

        # with known initial iterate

        x0 = zeros(T, n)
        solver = ProximalAlgorithms.DavisYin{R}(tol=R(1e-6))
        xf_dys, xg_dys, it_dys = solver(
            x0, f=reg1, g=reg2, h=loss, A=A, L=L
        )
        @test eltype(xf_dys) == T
        @test eltype(xg_dys) == T
        @test norm(xf_dys - x_star, Inf) <= 1e-3
        @test norm(xg_dys - x_star, Inf) <= 1e-3
        @test it_dys <= 140

        # with random initial iterate

        x0 = randn(T, n)
        solver = ProximalAlgorithms.DavisYin{R}(tol=R(1e-6))
        xf_dys, xg_dys, it_dys = solver(
            x0, f=reg1, g=reg2, h=loss, A=A, L=L
        )
        @test eltype(xf_dys) == T
        @test eltype(xg_dys) == T
        @test norm(xf_dys - x_star, Inf) <= 1e-3
        @test norm(xg_dys - x_star, Inf) <= 1e-3

    end

    afba_test_params = [
        (R(2), R(0), 130),
        (R(1), R(1), 1890),
        (R(0), R(1), 320),
        (R(0), R(0), 194),
        (R(1), R(0), 130)
    ]

    @testset "AFBA" for (theta, mu, maxit) in afba_test_params

        # with known initial iterates

        x0 = zeros(T, n)
        y0 = zeros(T, m)

        solver = ProximalAlgorithms.AFBA{R}(theta=theta, mu=mu, tol=R(1e-6))
        x_afba, y_afba, it_afba = solver(
            x0, y0, f=reg2, g=reg1, h=loss, L=A, beta_f=R(1),
        )
        @test eltype(x_afba) == T
        @test eltype(y_afba) == T
        @test norm(x_afba - x_star, Inf) <= 1e-4
        @test it_afba <= maxit

        # with random initial iterates

        x0 = randn(T, n)
        y0 = randn(T, m)

        solver = ProximalAlgorithms.AFBA{R}(theta=theta, mu=mu, tol=R(1e-6))
        x_afba, y_afba, it_afba = solver(
            x0, y0, f=reg2, g=reg1, h=loss, L=A, beta_f=R(1),
        )
        @test eltype(x_afba) == T
        @test eltype(y_afba) == T
        @test norm(x_afba - x_star, Inf) <= 1e-4

    end
end


module Ex5_6
using FaultDetectionTools
using DescriptorSystems
using LinearAlgebra
using Polynomials
using Test

# Example 5.6 - Solution of an AFDP
println("Example 5.6")

# define s as an improper transfer function
s = rtf('s');
# define Gu(s), Gf(s) and Gw(s)
Gu = [(s+1)/(s+2); (s+2)/(s+3)];     # enter Gu(s)
Gw = [1/(s+2); 0];                   # enter Gw(s)
Gf = [(s+1)/(s+2) 0; 0 1];           # enter Gf(s)
p = 2; mu = 1; mw = 1; mf = 2;       # enter dimensions

Q = dss([eye(p) -Gu]); Rf = dss(Gf); Rw = dss(Gw);
atol = 1.e-7;

# check solvability using a random frequency
if minimum(maximum(abs.(evalfr(Rf,rand())),dims=1)) > 0.01

    # compress Rw to a full row rank matrix
    rw = rank(evalfr(Rw,rand())); nb = size(Q,1);
    if rw < nb
       h = ones(rw,nb);  # usable only for rw = 1
       # use alternatively h = rand(rw,nb);
       Q = h*Q; Rf = h*Rf; Rw = h*Rw;
    end

    # compute the quasi-co-outer-co-inner factorization
    Rwi, Rwo, info = goifac(Rw; atol)

    # compute optimal filter (for standard case)
    Q = gir(Rwo\Q; atol )              # update Q
    Rf = gir(Rwo\Rf; atol); Rw = Rwi;  # update Rf and Rw

    # check for poles on the extended imaginary axis
    poles = gpole([Q Rf])
    if any(isinf.(poles)) || minimum(abs.(real(poles))) < 0.0001
        # compute a stable and proper left coprime factorization
        # of [Q Rf Rw] with desired stability degree -3
        # update if solution is improper or unstable
        Q_Rf_Rw, M = glcf(gir([Q Rf Rw]; atol); atol, sdeg = -3, smarg = -3);

        # adjust denominator to unit infinity norm to match example
        Mnorm = ghinfnorm(M)[1]
        Q_Rf_Rw = Q_Rf_Rw/Mnorm
        Q = Q_Rf_Rw[:,1:p+mu]
        Rf = Q_Rf_Rw[:,p+mu+1:p+mu+mf]
        Rw = Q_Rf_Rw[:,p+mu+mf+1:end]
        println("Q = $(dss2rm(Q, atol = 1.e-7))")
        println("Rf = $(dss2rm(Rf, atol = 1.e-7))")
        println("Rw = $(dss2rm(Rw, atol = 1.e-7))")
        gap = fdhinfminus(Rf)[1]
        println("gap = $gap")
    end
else
    @info "No solution exists"
end

end # module


# sets can only contain unique elements - adding an additional
# 1 will appear to do 'nothing'.
s1 = Set([1,2,3,"Richard", 3])
println(typeof(s1))
push!(s1, 1,55,98)
println(s1)
println("******")

# if I create a set of integers I cannot
# add a string
s2 = Set([1,2,3])
println(typeof(s2))

# to explicitly create a Set of a particular type
s3 = Set{Float64}([1,2,3,4,5,66])
println(typeof(s3))
println(s3)

# I can check if a Set contains a value
println(1 in s3)
println(34 in s3)

# I can iterate over a Set - but order is not guarenteed
# and I can't  use an index to retrieve a element from the Set
for e in s3
    println("$(e * 5)")
end

# with Sets it is possible to calculate the interset (common elements) of two Sets
s4 = Set([1,4,6,7])
s5 = Set([4,7,8,9])
println(intersect(s4, s5))

# to output the 'union' (all unique elements from both sets) of two sets
println(union(s4, s5))

# to calculate the 'elements' that are in set1 but not in set2
println(setdiff(s5, s4))


"""
An abstract type for real-valued 1-d profiles.
"""
abstract type Abstract1DProfile end

using ForwardDiff

import Base: >>, <<

"""
    ConstantProfile(c::Number)

Create a profile consisting of a constant `c`.

# Example

```jldoctest
julia> p = SpaceTimeFields.ConstantProfile(1.0)
Constant (2.3)
```
"""
struct ConstantProfile <: Abstract1DProfile
    c::Number
end

function show(io::IO, p::ConstantProfile)
    print(io, "Constant ($(p.c))")
end

(p::ConstantProfile)(t) = p.c

struct DerivativeProfile{P} <: Abstract1DProfile
    p::P
end

function show(io::IO, ṗ::DerivativeProfile)
    print(io, "d/dt ($(ṗ.p))")
end

(ṗ::DerivativeProfile)(t) = ForwardDiff.derivative(ṗ.p, t)

"""
    d_dt(p::Abstract1DProfile)

Take the time derivative of `p` and return it as a new profile.

# Example

```jldoctest
julia> s = SpaceTimeFields.Sinusoid(π)
Sinusoid (ω = 3.14)

julia> s.([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
 0.0
 1.0
 0.707107

julia> c = SpaceTimeFields.d_dt(s)
d/dt (Sinusoid (ω = 3.14))

julia> c.([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
  3.14159
  1.92367e-16
 -2.22144
```
"""
d_dt(p::Abstract1DProfile) = DerivativeProfile(p)

struct ScaledProfile{N <: Real, P <: Abstract1DProfile} <: Abstract1DProfile
    s::N
    p::P
end
function show(io::IO, p::ScaledProfile)
    print(io, "$(p.s) × ($(p.p))")
end

"""
    s::Number * p::Abstract1DProfile

Returns a scaled profile with `(s*p)(t) = s*p(t)`

# Example

```jldoctest
julia> s = SpaceTimeFields.Sinusoid(π)
Sinusoid (ω = 3.14)

julia> 2s
2 × (Sinusoid (ω = 3.14))

julia> (2s).([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
 0.0
 2.0
 1.41421
```
"""
s::Number * p::Abstract1DProfile = ScaledProfile(s, p)

"""
    -(p₁::Abstract1DProfile, p₂::Abstract1DProfile)

```jldoctest
julia> s = SpaceTimeFields.Sinusoid(π)
Sinusoid (ω = 3.14)

julia> 2s
2 × (Sinusoid (ω = 3.14))

julia> (2s).([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
 0.0
 2.0
 1.41421

julia> s = SpaceTimeFields.Sinusoid(π);

julia> s.([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
 0.0
 1.0
 0.707107

julia> (-s).([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
 -0.0
 -1.0
 -0.707107

julia> (s - s).([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
 0.0
 0.0
 0.0
```
"""
-(p::Abstract1DProfile) = ScaledProfile(-1, p)
(p::ScaledProfile)(t) = p.s*p.p(t)

struct ShiftedProfile{N <: Real, P <: Abstract1DProfile} <: Abstract1DProfile
    Δt::N
    p::P
end
function show(io::IO, p::ShiftedProfile)
    print(io, "$(p.p) >> $(p.Δt)")
end

(p::ShiftedProfile)(t) = p.p(t - p.Δt)

"""
    p::Abstract1DProfile >> Δt::Number

Shift the profile in time so that `(p >> Δt)(t) = p(t - Δt)`

# Example

```jldoctest
julia> s = SpaceTimeFields.Sinusoid(π);

julia> s >> 0.5
Sinusoid (ω = 3.14) >> 0.5

julia> (s >> 0.5).([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
 -1.0
  0.0
  0.707107

julia> (s << 0.5).([0.0, 0.5, 0.75])
3-element Array{Float64,1}:
  1.0
  1.22465e-16
 -0.707107
```
"""
>>(p::Abstract1DProfile,Δt::Number) = ShiftedProfile(Δt, p)
<<(p::Abstract1DProfile,Δt::Number) = ShiftedProfile(-Δt, p)

#p::Abstract1DProfile >> Δt::Number = ShiftedProfile(Δt, p)
#p::Abstract1DProfile << Δt::Number = ShiftedProfile(-Δt, p)

struct AddedProfiles{T <: Tuple} <: Abstract1DProfile
    ps::T
end
function show(io::IO, Σp::AddedProfiles)
    println(io, "AddedProfiles:")
    for p in Σp.ps
        println(io, "  $p")
    end
end

"""
    p₁::Abstract1DProfile + p₂::Abstract1DProfile

Add the profiles so that `(p₁ + p₂)(t) = p₁(t) + p₂(t)`.

# Examples

```jldoctest
julia> ramp₁ = SpaceTimeFields.EldredgeRamp(5)
logcosh ramp (aₛ = 5.0)

julia> ramp₂ = SpaceTimeFields.ColoniusRamp(5)
power series ramp (n = 5.0)

julia> ramp₁ + ramp₂
AddedProfiles:
  logcosh ramp (aₛ = 5.0)
  power series ramp (n = 5.0)

julia> ramp₁ + (ramp₂ + ramp₁) == ramp₁ + ramp₂ + ramp₁
true

```
"""
+(p::Abstract1DProfile, Σp::AddedProfiles) = AddedProfiles((p, Σp.ps...))
+(Σp::AddedProfiles, p::Abstract1DProfile) = AddedProfiles((Σp.ps..., p))
function +(Σp₁::AddedProfiles, Σp₂::AddedProfiles)
    AddedProfiles((Σp₁..., Σp₂...))
end

-(p₁::Abstract1DProfile, p₂::Abstract1DProfile) = p₁ + (-p₂)

+(p::Abstract1DProfile...) = AddedProfiles(p)

function (Σp::AddedProfiles)(t)
    f = 0.0
    for p in Σp.ps
        f += p(t)
    end
    f
end

struct MultipliedProfiles{T <: Tuple} <: Abstract1DProfile
    ps::T
end
function show(io::IO, Πp::MultipliedProfiles)
    println(io, "MultipliedProfiles:")
    for p in Πp.ps
        println(io, "  $p")
    end
end
*(p::Abstract1DProfile, Πp::MultipliedProfiles) = MultipliedProfiles((p, Πp.ps...))
*(Πp::MultipliedProfiles, p::Abstract1DProfile) = MultipliedProfiles((Πp.ps..., p))
function *(Πp₁::MultipliedProfiles, Πp₂::MultipliedProfiles)
    MultipliedProfiles((Πp₁..., Πp₂...))
end

function (Πp::MultipliedProfiles)(t)
    f = 1.0
    for p in Πp.ps
        f *= p(t)
    end
    f
end

## Functional forms of profiles

"""
    Gaussian(σ,x0,A)

Construct a 1-d Gaussian function centered at `x0` with standard deviation `σ`
and amplitude `A`. The resulting function can be evaluated at any real-valued
number.

# Example

```jldoctest
julia> g = Gaussian(0.2,1)
Gaussian(0.2, 1, 2.8209479177387813)

julia> g(0.2)
1.0377687435514866
```
"""
struct Gaussian <: Abstract1DProfile
  σ :: Real
  A :: Real
  fact :: Float64
  Gaussian(σ,A) = new(σ,A,A/sqrt(π*σ^2))
end
show(io::IO, s::Gaussian) = print(io,
      "Gaussian (σ = $(round(s.σ, digits=2)), A = $(round(s.A, digits=2)))")

@inline _gaussian(r;tol=6.0) = abs(r) < tol ? exp(-r^2) : 0.0

radius(g::Gaussian) = g.σ
strength(g::Gaussian) = g.A

(g::Gaussian)(x) = g.fact*_gaussian(x/radius(g))

DGaussian(σ,A) = d_dt(Gaussian(σ,A))

struct Sinusoid <: Abstract1DProfile
    ω::Float64
end
(s::Sinusoid)(t) = sin(s.ω*t)
show(io::IO, s::Sinusoid) = print(io, "Sinusoid (ω = $(round(s.ω, digits=2)))")

struct EldredgeRamp <: Abstract1DProfile
    aₛ::Float64
end
(r::EldredgeRamp)(t) = 0.5(log(2cosh(r.aₛ*t)) + r.aₛ*t)/r.aₛ
show(io::IO, r::EldredgeRamp) = print(io, "logcosh ramp (aₛ = $(round(r.aₛ, digits=2)))")

struct ColoniusRamp <: Abstract1DProfile
    n::Int
end
function (r::ColoniusRamp)(t)
    Δt = t + 0.5
    if Δt ≤ 0
        0.0
    elseif Δt ≥ 1
        Δt - 0.5
    else
        f = 0.0
        for j = 0:r.n
            f += binomial(r.n + j, j)*(r.n - j + 1)*(1 - Δt)^j
        end
        f*Δt^(r.n + 2)/(2r.n + 2)
    end
end
show(io::IO, r::ColoniusRamp) = print(io, "power series ramp (n = $(round(r.n, digits=2)))")


@time a = rand([randstring(rand(0:16)) for i in 1:100_000], 10_000_000);

@time as = StrF{16}.(a);

@time sort(as, alg=RadixSort) |> issorted

@time asb = radixsort(as, rev=true)
@test issorted(asb, rev=true)

@time asb = radixsort(as)
@test issorted(asb)


#   This file is part of Fatou.jl. It is licensed under the MIT license
#   Copyright (C) 2017 Michael Reed

import Base: invokelatest

rdpm(tex) = split(split(tex,"\n\\end{displaymath}")[1],"\\begin{displaymath}\n")[2]

# we can substitute the expression into Newton's method and display it with LaTeX
function newton_raphson(F,m)
    f = RExpr(F)
    return Algebra.:-(R"z",Algebra.:*(m,Algebra.:/(f,Algebra.df(f,:z)))) |> factor |> parse
end

# define recursive composition on functions
recomp(E,x,j::Int) = Algebra.sub((Expr(:(=),:z,j > 1 ? recomp(E,x,j-1) : x),:(c=0)),E)

# we can convert the j-th function composition into a latex expresion
nL(E,m,j) = recomp(newton_raphson(E,m),:z,j) |> Algebra.latex |> rdpm
jL(E,j) = recomp(E,:z,j) |> Algebra.latex |> rdpm

# set of points that are within an ϵ neighborhood of the roots ri of the function f
ds = "\\displaystyle"
set0 = "D_0(\\epsilon) = \\left\\{ z\\in\\mathbb{C}: \\left|\\,z"
setj(j) = "$ds D_$j(\\epsilon) = \\left\\{z\\in\\mathbb{C}:\\left|\\,"
nsetstr = "- r_i\\,\\right|<\\epsilon,\\,\\forall r_i(\\,f(r_i)=0 )\\right\\}"
jsetstr = "\\,\\right|>\\epsilon\\right\\}"
nset0 = latexstring("$set0 $nsetstr")
jset0 = latexstring("$set0 $jsetstr")
nrset(f,m,j) = latexstring(
    j == 0 ? "$set0 $nsetstr" : "$(setj(j))$(nL(f,m,j)) $nsetstr")
jset(f,j) = latexstring(
    j == 0 ? "$set0 $jsetstr" : "$(setj(j))$(jL(f,j)) $jsetstr")


"""
LDA correlation according to Vosko Wilk,and Nusair, (DOI 10.1139/p80-159)
"""
function lda_c_vwn!(ρ; E=nothing, Vρ=nothing)
    x0  = sqrt(5913592351)
    x1  = 2^(2/3)*3^(1/6)
    x2  = pi^(1/6)
    x3  = ρ.^(1/6)
    x4  = x2.*x3
    x5  = 468052052839577158149*x0/928058053502134909615000000
    x6  = 6^(1/3)
    x7  = 12500*x4.*x6
    x8  = pi^(1/3)
    x9  = ρ.^(1/3)
    x10 = x8.*x9
    x11 = 1 ./(46593*x1.*x10 + x7 + 323380*sqrt(pi)*sqrt.(ρ))
    x12 = x1./x2
    x13 = x12./x3
    x14 = x13 .+ 46593/12500
    x15 = x6./x8
    x16 = x15./(2*x9)
    x17 = 46593*x13/25000 .+ x16 .+ 16169/1250
    x18 = 1 ./x17
    x19 = x13/2 .+ 5249/50000
    x20 = x19.^2
    x21 = x14.^(-2)
    x22 = x12./ρ.^(7/6)
    x23 = x15./(6*ρ.^(4/3))
    x24 = (15531*x22/50000 + x23)./x17.^2

    if E !== nothing
        @. E .= x5*atan(x0*x4/(12500*x1 + 46593*x4)) + 310907*log(x11*x7)/10000000 + 76037485627899*log(x11*x4*(25000*x1 + 5249*x4)^2/100000)/78468213432500000
    end
    if Vρ !== nothing
        @. Vρ .= x5*atan(x0/(12500*x14)) + ρ*(310907*6^(2/3)*x10*x17*(x16*x24 - x18*x23)/30000000 + 76037485627899*x17*(-x18*x19*x22/6 + x20*x24)/(78468213432500000*x20) + 156017350946525719383*x21*x22/(3923410671625000000000*(5913592351*x21/156250000 + 1))) + 310907*log(x16*x18)/10000000 + 76037485627899*log(x18*x20)/78468213432500000
    end

    (E=E, Vρ=Vρ)
end


# Gold heater on a film on a silicon substrate for isotropic films

# Load modules
include("ThreeOmegaMethod.jl")
using Main.ThreeOmegaMethod
using PyPlot

### Input data
# half-width of heater line [m]
b = (12.5/2)*1e-6
# length of heater line [m]
l = 1.0e-3
# range of frequencies [Hz]
f = exp10.(LinRange(0, 9, 1500))
# power [W]
p = 0.030^2*22.11
# heater thermal resistance
ρh = [0. 0.] # I only need two actually, see F
# interface thermal resistances
thresistances = [0. 0.]

# Wrap them into structures
heater = Layer(310.0, 1.0, 0.2e-6, 2.441e6)
specimen = Layer(0.1, 1.0, 1.0e-6, 2320*700*0.1)
substrate = Layer(160.0, 1.0, 525.0e-6, 2320*700.)
layers = [heater specimen substrate]
hgeometry = HeaterGeometry(b, l, ρh)
source = Source(p, f)

# call the model
ΔT, int_error = ThreeOmegaMatrix(layers, hgeometry, source, thresistances)

figure()
semilogx(f, real(ΔT), label="real part")
semilogx(f, imag(ΔT), label="imag part")
legend(loc="best")
xlabel("Frequency [Hz]")
ylabel("Temperature rise [C]")


# https://fenicsproject.org/docs/dolfin/latest/python/demos/cahn-hilliard/demo_cahn-hilliard.py.html

module cahn_hilliard

using FenicsPy

py"""
import random
from dolfin import UserExpression, MPI, NonlinearProblem, assemble

# Class representing the intial conditions
class InitialConditions(UserExpression):
    def __init__(self, **kwargs):
        random.seed(2 + MPI.rank(MPI.comm_world))
        super().__init__(**kwargs)
    def eval(self, values, x):
        values[0] = 0.63 + 0.02*(0.5 - random.random())
        values[1] = 0.0
    def value_shape(self):
        return (2,)

# Class for interfacing with the Newton solver
class CahnHilliardEquation(NonlinearProblem):
    def __init__(self, a, L):
        NonlinearProblem.__init__(self)
        self.L = L
        self.a = a
    def F(self, b, x):
        assemble(self.L, tensor=b)
    def J(self, A, x):
        assemble(self.a, tensor=A)
"""

# Model parameters
lmbda  = 1.0e-02  # surface parameter
dt     = 5.0e-06  # time step
theta  = 0.5      # time stepping family, e.g. theta=1 -> backward Euler, theta=0.5 -> Crank-Nicolson

# Form compiler options
parameters["form_compiler"]["optimize"]     = true
parameters["form_compiler"]["cpp_optimize"] = true

# Create mesh and build function space
mesh = UnitSquareMesh.create(96, 96, CellType.Type.quadrilateral)
P1 = FiniteElement("Lagrange", mesh.ufl_cell(), 1)
ME = FunctionSpace(mesh, P1*P1)

# Define trial and test functions
du    = TrialFunction(ME)
q, v  = TestFunctions(ME)

# Define functions
u   = FeFunction(ME)  # current solution
u0  = FeFunction(ME)  # solution from previous converged step

# Split mixed functions
c,  mu  = split(u)
c0, mu0 = split(u0)
dc, dmu = split(du)

# Create intial conditions and interpolate
u_init = Expression(py"InitialConditions"(degree=1))
u.interpolate(u_init)
u0.interpolate(u_init)

# Compute the chemical potential df/dc
c = variable(c)
f    = 100*c^2*(1-c)^2
dfdc = diff(f, c)

# mu_(n+theta)
mu_mid = (1.0-theta)*mu0 + theta*mu

# Weak statement of the equations
L0 = c*q*dx - c0*q*dx + dt*dot(grad(mu_mid), grad(q))*dx
L1 = mu*v*dx - dfdc*v*dx - lmbda*dot(grad(c), grad(v))*dx
L = L0 + L1

# Compute directional derivative about u in the direction of du (Jacobian)
a = derivative(L, u, du)

# Create nonlinear problem and Newton solver
problem = NonlinearProblem(py"CahnHilliardEquation"(a.pyobject, L.pyobject))
solver = NewtonSolver()
solver.parameters["linear_solver"] = "lu"
solver.parameters["convergence_criterion"] = "incremental"
solver.parameters["relative_tolerance"] = 1e-6

# Output file
file = File("cahn_hilliard/output.pvd", "compressed")

# Step in time
t = 0.0
T = 50*dt
while t < T
    global t += dt
    solver.solve(problem, u.vector())
    u0.assign(u)
    file << (u.split()[1], t)
end

end # module cahn_hilliard


using GAIO

# the Lorenz system
const σ, ρ, β = 10.0, 28.0, 0.4
v((x,y,z)) = (σ*(y-x), ρ*x-y-x*z, x*y-β*z)
f(x) = rk4_flow_map(v, x)

center, radius = (0,0,25), (30,30,30)
P = BoxPartition(Box(center, radius), (128,128,128))
F = BoxMap(f, P, no_of_points=200)

x = (sqrt(β*(ρ-1)), sqrt(β*(ρ-1)), ρ-1)         # equilibrium
W = unstable_set!(F, P[x])

T = TransferOperator(F, W)
(λ, ev) = eigs(T)

plot(log∘abs∘ev[1])


# Various ways to query subgraphs.

using JLD, PyPlot, LinearAlgebra, SparseArrays

# Load data
file_dir = joinpath(Base.source_dir(),"./Entity.jld")
E = load(file_dir)["E"]
#E = loadassoc(joinpath(Base.source_dir(),"./Entity.jld2"))
E = logical(E)

# Compute entity (all facet pairs).
A = sqIn(E)
d = diag(getadj(A))
A = putAdj(A,getadj(A)-Diagonal(d))

# Compute normalized correlation.
i,j,v = findnz(getadj(A))
An = putAdj(A, sparse(i,j,v ./ min.(d[i],d[j])))

# Multi-facet queries.
x = "LOCATION/new york,"
p = StartsWith("PERSON/,")
printFull( (A[p,x] > 4) & (An[p,x] > 0.3))

# Triangles.
p0 = "PERSON/john kennedy,"

p1 = getrow(A[p,p0] + A[p0,p])
figure()
spy(A[p1,p1])

p2 = getrow( A[p1,p1] - (A[p,p0]+ A[p0,p]))
A[p2,p2] > 1


using Particles
using StatsBase
using StatsPlots

nx = 16
nv = 12

n = 10000
x = randn(n)
v = randn(n)

xmax = ceil(maximum(abs.(x)))
x .+= xmax
x ./= 2*xmax

vmax = ceil(maximum(abs.(v)))

p = PoissonSolver{eltype(x)}(nx)
solve!(p, x)

xgrid = collect(0:p.Δx:1)
vgrid = LinRange(-vmax, +vmax, nv)

hx = fit(Histogram, x, xgrid)
hv = fit(Histogram, v, vgrid)

plot(hx; legend=nothing, xlabel="x", ylabel="n")
savefig("poisson_histogram_x.png")

plot(hv; legend=nothing, xlabel="v", ylabel="n")
savefig("poisson_histogram_v.png")

plot(p.xgrid[1:end-1], p.ρ; legend=nothing, xlabel="x", ylabel="ρ(x)")
savefig("poisson_ρ.png")

plot(p.xgrid[1:end-1], p.ϕ; legend=nothing, xlabel="x", ylabel="ϕ(x)")
savefig("poisson_ϕ.png")

plot(p.xgrid, x -> eval_field(p, x); legend=nothing, xlabel="x", ylabel="E(x)")
savefig("poisson_E.png")


#=
Copyright (c) 2018-2022 Chris Coey, Lea Kapelevich, and contributors

This Julia package Hypatia.jl is released under the MIT license; see LICENSE
file in the root directory or at https://github.com/chriscoey/Hypatia.jl

strengthening of the theta function towards the stability number of a graph

TODO add sparse PSD formulation
=#

using SparseArrays

struct StabilityNumber{T <: Real} <: ExampleInstanceJuMP{T}
    side::Int
    use_doublynonnegativetri::Bool
end

function build(inst::StabilityNumber{T}) where {T <: Float64}
    side = inst.side
    sparsity = 1 - inv(side)
    inv_graph = tril!(sprand(Bool, side, side, sparsity) + I)
    (row_idxs, col_idxs, _) = findnz(inv_graph)
    diags = (row_idxs .== col_idxs)

    model = JuMP.Model()
    JuMP.@variable(model, X[1:length(row_idxs)])
    X_diag = X[diags]
    JuMP.@objective(model, Max, 2 * sum(X) - sum(X_diag))
    JuMP.@constraint(model, sum(X_diag) == 1)
    X_lifted = sparse(row_idxs, col_idxs, X, side, side)
    X_vec = JuMP.AffExpr[X_lifted[i, j] for i in 1:side for j in 1:i]
    if inst.use_doublynonnegativetri
        cone_dim = length(X_vec)
        X_scal = Cones.scale_svec!(X_vec, sqrt(T(2)))
        JuMP.@constraint(model, X_scal in
            Hypatia.DoublyNonnegativeTriCone{T}(cone_dim))
    else
        JuMP.@constraint(model, X_vec in
            MOI.PositiveSemidefiniteConeTriangle(side))
        JuMP.@constraint(model, X[.!(diags)] .>= 0)
    end

    return model
end


using Primes

ν(n::Int) = sum(digits(n, base=2))
λ(n::Int) = floor(Int, log2(n))
function lowerbound(n::Int)
    if n ≤ 327_678 || ν(n) ≤ 16
        # We add 1 because Julia indexs from 1
        λ(n) + ceil(Int, log2(ν(n))) + 1
    else
        # We add 1 because Julia indexs from 1
        ceil(Int, log2(n) + log2(ν(n)) - 2.13)
    end
end

abstract type BoundingSequence end
abstract type BoundingSequenceA <: BoundingSequence end
abstract type BoundingSequenceC <: BoundingSequence end

function bounds(::Type{BoundingSequenceA}, n::Int, lb::Int)
    seq = zeros(Int, lb + 1)
    seq[end] = n
    for i in 1:lb
        seq[end-i] = ceil(Int, seq[end-i+1] / 2)
    end
    seq
end

function bounds(::Type{BoundingSequenceC}, n::Int, lb::Int)
    t = iseven(n) ? factor(n)[2] : zero(n)

    seq = zeros(Int, lb + 1)
    seq[end] = n
    for i in 1:lb-t-1
        seq[i] = ceil(Int, n / (3 * 2^(lb - (i + 1))))
    end
    for i in lb-t:lb
        seq[i] = ceil(Int, n / 2^(lb - (i - 1)))
    end
    seq
end

function bounds(n::Int, lb::Int)
    if n % 5 != 0
        vertical = bounds(BoundingSequenceC, n, lb)
        vertical, vertical
    else
        vertical = bounds(BoundingSequenceC, n, lb)
        slant = bounds(BoundingSequenceA, n, lb)
        vertical, slant
    end
end

retain(bound::Int, aᵢ::Int) = aᵢ ≥ bound
retain(bound::Int, aᵢ₋₁::Int, aᵢ::Int) = aᵢ + aᵢ₋₁ ≥ bound
retain(n::Int, lb::Int, i::Int, aᵢ::Int) = n != 2^(lb - i + 1) * aᵢ

function retain(n::Int, lb::Int, v::Int, s::Int, i::Int, aᵢ₋₁::Int, aᵢ::Int)
    retain(v, aᵢ) && retain(n, lb, i, aᵢ) && retain(s, aᵢ₋₁, aᵢ)
end

function stackchildren!(n::Int, stack::Vector{Vector{Int}}; verbose=false)
    aᵢ = stack[end][end]
    segment = Int[]
    for i in 1:length(stack), j in i:length(stack)
        aᵢ₊₁ = stack[i][end] + stack[j][end]
        if aᵢ < aᵢ₊₁ ≤ n
            push!(segment, aᵢ₊₁)
        end
    end
    unique!(sort!(segment))
    push!(stack, segment)
end

function backup(stack::Vector{Vector{Int}}; verbose=false)
    while length(stack) > 2
        pop!(stack[end])
        if isempty(stack[end])
            pop!(stack)
        else
            break
        end
    end
    length(stack) == 2
end

function thurber(n::Int; verbose=false)
    if n < one(n)
        error("no chains defined for integers less than 1")
    elseif isone(n)
        return 0
    end

    stack = Vector{Int}[[1],[2]]
    lb = lowerbound(n)

    loop = 1
    while true
        vertical, slant = bounds(n, lb)
        verbose && @info "Outer Loop" lb vertical slant
        while true
            i = length(stack)
            verbose && @info "State" n stack lb loop
            if i ≤ lb
                aᵢ₋₁, aᵢ = stack[i-1][end], stack[i][end]

                if retain(n, lb, vertical[i], slant[i+1], i, aᵢ₋₁, aᵢ)
                    verbose && @info "Retained" aᵢ
                    if aᵢ == n
                        return length(stack) - 1
                    end
                    stackchildren!(n, stack; verbose)
                else
                    verbose && @info "Did not retain" aᵢ loop
                    if backup(stack; verbose)
                        loop += 1
                        break
                    end
                end
            else
                verbose && @info "Reached lower bound" loop
                if backup(stack; verbose)
                    loop += 1
                    break
                end
            end
            loop += 1
        end
        lb += 1
    end
end


using PastaQ
using ITensors
using ITensorsGPU
using Random

Random.seed!(1234)

using CUDA: allowscalar
allowscalar(false)

# Set to identity to run on CPU
gpu = cu

N = 40     # Number of qubits
depth = 18 # Depth of the quantum circuit

# Starting state on GPU
# `|ψ₀⟩ = |0,0,…,0⟩`
println("Make the starting state |ψ₀⟩ = |0,0,…,0⟩...")
ψ₀ = gpu(qubits(N))
@show maxlinkdim(ψ₀)
println()

# Generate random quantum circuit built out of
# layers of single-qubit random rotations + `CX`
# gates, alternating between even and of odd layers.
println("Random circuit of depth $depth on $N qubits:")
gates = randomcircuit(N, depth)
display(gates)
println()

# Build the circuit as tensors and move them to GPU
gate_tensors = gpu.(complex.(buildcircuit(ψ₀, gates)))

# Obtain the approximate circuit evolution
println("Approximating random circuit evolution Û|0,0,…,0⟩...")
ψ = @time runcircuit(ψ₀, gate_tensors)
@show maxlinkdim(ψ)
println()


function mps(s)
# create minimum-phase spectrum sm from complex spectrum s
   return exp( fft( fold( ifft( log( clipdb(complex(s),-100) )))));
end


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/FEMQuad.jl/blob/master/LICENSE

using FEMQuad, Test
using FEMQuad: integrate_3d, get_rule

@testset "Gauss-Legendre quadratures in prismatic domains" begin
    rules = (:GLWED6, :GLWED21)
    f(i) = x -> sum(sum(x)^j for j=0:i)

    @test isapprox(integrate_3d(f(0), :GLWED6), 1.0)
    @test isapprox(integrate_3d(f(1), :GLWED6), 5.0/3.0)
    @test isapprox(integrate_3d(f(0), :GLWED6B), 1.0)
    @test isapprox(integrate_3d(f(1), :GLWED6B), 5.0/3.0)
    @test isapprox(integrate_3d(f(1), :GLWED21), 5.0/3.0)
    @test isapprox(integrate_3d(f(2), :GLWED21), 5.0/2.0)
    @test isapprox(integrate_3d(f(3), :GLWED21), 107.0/30.0)
    @test isapprox(integrate_3d(f(4), :GLWED21), 51.0/10.0)
    @test isapprox(integrate_3d(f(5), :GLWED21), 517.0/70.0)
    #@test isapprox(integrate_3d(f(6), :GLWED21), 4597.0/420.0)
    #@test isapprox(integrate_3d(f(7), :GLWED21), 20959.0/1260.0)
end


using Test
import CryptoGroups: PGroup, Hash, specialize, value, ROPRG, MODP

tobig(x) = parse(BigInt, bytes2hex(reverse(x)), base=16)
interpret(::Type{BigInt}, x::Vector{UInt8}) = tobig(reverse(x))

interpret(::Type{Vector{UInt8}}, x::Integer) = reverse(reinterpret(UInt8, [x]))

function leaf(x::Vector{UInt8})

    N = UInt32(length(x))

    LEAF = UInt8(1)

    Nbin = interpret(Vector{UInt8}, N)
    bin = UInt8[LEAF, Nbin..., x...]

    return bin
end

leaf(x::String) = leaf(Vector{UInt8}(x))

### Let's make the setup complete. From repo I ahve a following public parameters:
nr = 100
rohash = Hash("sha256")
prghash = Hash("sha256")

ρ = hex2bytes("15e6c97600bbe30125cbc08598dcde01a769c15c8afe08fe5b7f5542533159e9")

# Need to unmarchal this into numbers

# group_spec = "00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4d6f645047726f757000000000040100000041009a91c3b704e382e0c772fa7cf0e5d6363edc53d156e841555702c5b6f906574204bf49a551b695bed292e0218337c0861ee649d2fe4039174514fe2c23c10f6701000000404d48e1db8271c17063b97d3e7872eb1b1f6e29e8ab7420aaab8162db7c832ba1025fa4d2a8db4adf69497010c19be0430f7324e97f201c8ba28a7f1611e087b3010000004100300763b0150525252e4989f51e33c4e6462091152ef2291e45699374a3aa8acea714ff30260338bddbb48fc7446b273aaada90e3ee8326f388b582ea8a073502010000000400000001"

p = 8095455969267383450536091939011431888343052744233774808515030596297626946131583007491317242326621464546135030140184007406503191036604066436734360427237223
q = 4047727984633691725268045969505715944171526372116887404257515298148813473065791503745658621163310732273067515070092003703251595518302033218367180213618611

h_str = "(1da949a3dfbeb316e9b225bc7d75b78d0ddd5e44fc382e74f3de95ad10eac798c4cc7be7e57d3afb259964c90fe7eb7e28a7673228d6b35a789dabd0d8351675,2937c1c4771b70c8b3dc935681aa8cef45ed24cf0c74cd2f9599ea5876850936168c5a092270d6396e634b9e46ab59836f509d0a68a65edc1426a87fd157798f,3767a5e108ecc067c31cd8bf544bcebfa3e3002f2af499ad3568e6fdb2775f4147bcf9485aeea3180b81aad365e3f4375c280941fdae10812bf1ba445030ab00,5c83d2d4f9cf3b3c2267788dd95a14740964e89f177690d06c43adfd137a101698c6bdc7ea9565fd0b18b3cfa89a2d5ea860de3912af4ffbae2c1f722912f6b9,2c453b082a201f3410d0b2906e7f2998a2c50d84f8eb0af017bf76124424bd385c15446eaad77b3ef1831798093f688331475c754f9e737d1485d80f0ef8289b,534897b93e335b0de44f3426aa6597170857f610ce0e4ff8af8efc5010ab5dc205ec53da9759e0c5ad0d2fdbf3d116e1ad7a94629c2ee331e8ceb29417983c50,7d09ab7d0704e68376e933ccfbbaa0a2e8b10a270ce72a5add597ae66e692bd930b019cc9be30b25e859dd6ba5e3bd74a872588b1e14885bacaeca3b254cc86a,33ff08f3aae9132accec73805ec2cd98ce871779369d69d135a7e1fb75b653d415480be64116feda3176dc83b2d54cf8240f8c6f1d85168081efb4f013106cdb,921ce5123ccb3b891f9e1f314d84b8ca827c44dc2027dd4fd8272189ba076eba7c2cfe7e8f8a8915b263fc72232e5d5f193f15ff6aece5b9c55f4ca9c1887fcf,410494b95d3e453e46d0b8915765f2316fcf4db7d7eff0349fe674a6498d3aaa2c3bae2b4e3c7ec1a5d5fd90d88a1e9886914f4b8d039cd470d69642b0203b73)"

𝐡 = interpret.(BigInt, hex2bytes.(split(h_str[2:end-1], ",")))

d = [ρ..., leaf("generators")...]
roprg = ROPRG(d, rohash, prghash)
prg = roprg(UInt8[]) # d is a better argument than x

sp = MODP(p; q)

𝐡′ = rand(prg, sp, 10; nr)

@test 𝐡 == 𝐡′


export Signal
export data
export fps
export seconds
export chunks
export chunk
export dft
export fft

import WAV

"""
analyse(signal)

Converts a signal from the time domain to the frequency domain
with the given signal.
"""
function analyse(signal)
    N = size(signal, 1)
    f(ω) = begin
        scale = 1 / sqrt(N)
        estimate = 0
        for t = 0:N-1
            estimate += signal[t+1] * exp(-im * 2π * ω / N * t)
        end
        scale * estimate
    end
    Ω = Array{Complex}(undef, N)
    for ω = 0:N-1
        Ω[ω+1] = f(ω)
    end
    Ω
end

"""
synthesize(signal)

Converts a signal from the frequency domain to the time domain
with the given signal.
"""
function synthesize(signal)
    N = size(signal, 1)
    f(t) = begin
        scale = 1 / sqrt(N)
        estimate = 0
        for ω = 0:N-1
            estimate += signal[ω+1] * exp(im * 2π * ω / N * t)
        end
        scale * estimate
    end
    T = Array{Complex}(undef, N)
    for t = 0:N-1
        D[t+1] = f(t)
    end
    T
end

struct Signal
    data::Array{Float64}
    fps::Float64
end

Signal(s::String; c=1) = begin
    y, fps = WAV.wavread(s)
    y = (y .+ 1) ./ 2
    Signal(y[:, c], fps)
end

data(s::Signal) = s.data

fps(s::Signal) = s.fps

seconds(s::Signal) = Int(size(data(s), 1) ÷ fps(s))

chunks(s::Signal, cps::Int) = seconds(s) * cps

"""
chunk(s::Signal, i::Int, cps::Int)

Gets a chunk of a signal such that there are a certain number of chunks per
second with the given signal, the chunk ordinal number and the number of chunks
per second.
"""
function chunk(s::Signal, i::Int, cps::Int)
    samples_per_chunk = Int(fps(s) ÷ cps)
    window = 2^11
    start = (i - 1) * samples_per_chunk + 1
    finish = start + window
    total_samples = size(data(s), 1)
    if finish < total_samples
        return Signal(data(s)[start:finish-1], fps(s))
    else
        return Signal(data(s)[total_samples-window+1:end], fps(s))
    end
end

function fft(s::Signal)
    x = data(s)
    N = size(x, 1)
    if N % 2 > 0
        println("Must be a power of 2.")
        return analyse(x)
    elseif N ≤ 2
        return analyse(x)
    else
        e = fft(Signal(view(x, 2:2:N), fps(s)))
        o = fft(Signal(view(x, 1:2:N-1), fps(s)))
        r = convert(Array{Int64}, floor.(range(0, stop=N-1, length=N)))
        v = exp.(-im .* 2pi .* r ./ N)
        i = Integer(N ÷ 2)
        return [e .+ v[1:i] .* o; e .+ v[i+1:end] .* o]
    end
end

# Discrete Fourier transform
dft(s::Signal) = analyse(data(s))