#=
using Test
using LatticeTools

using Combinatorics
using LinearAlgebra
using YAML

@testset "Group" begin
    @testset "Exceptions" begin
        @test_throws ArgumentError FiniteGroup([1 1; 1 2])
        @test_throws ArgumentError FiniteGroup([1 2 3; 2 1 1; 3 1 1])
        @test_throws ArgumentError FiniteGroup([2 3; 3 2])

        # Right Bol Loop 8.1.4.0 of order 8
        # http://ericmoorhouse.org/pub/bol/htmlfiles8/8_1_4_0.html
        @test_throws ArgumentError FiniteGroup([
            1 2 3 4 5 6 7 8;
            2 8 6 1 7 3 5 4;
            3 7 8 6 1 4 2 5;
            4 1 7 8 6 5 3 2;
            5 6 1 7 8 2 4 3;
            6 3 4 5 2 8 1 7;
            7 5 2 3 4 1 8 6;
            8 4 5 2 3 7 6 1;
        ])
    end
    @testset "FiniteGroup-Abelian" begin
        @test_throws ArgumentError FiniteGroup([1 1 1; 1 1 1])
        @test_throws ArgumentError FiniteGroup([1 1 1; 1 1 1; 1 1 1])

        # Example: Z₃
        mtab = [
            1 2 3;
            2 3 1;
            3 1 2;
        ]
        group = FiniteGroup(mtab)

        @test group_order(group) == 3
        @test group_order(group, 1) == 1
        @test group_order(group, 2) == 3
        @test group_order(group, 3) == 3
        @test group_order(group, [1,2,3]) == [1,3,3]
        @test period_length(group, 1) == 1
        @test period_length(group, 2) == 3
        @test period_length(group, 3) == 3
        @test period_length(group, [1,2,3]) == [1,3,3]
        @test isabelian(group)
        @test group_multiplication_table(group) == mtab
        @test element(group, 2) == 2
        @test element(group, 1:2) == 1:2
        @test elements(group) == 1:3
        @test element_name(group, 2) == "2"
        @test element_name(group, 1:2) == ["1", "2"]
        @test element_names(group) == ["1", "2", "3"]
        @test_throws BoundsError element(group, 5)
        @test_throws BoundsError element_name(group, 5)

        gp = group_product(group)
        for i in 1:3, j in 1:3
            @test group_product(group, i, j) == mtab[i, j]
            @test gp(i, j) == mtab[i, j]
        end
        @test group_product(group, 2, BitSet([1,2])) == BitSet([2,3])
        @test group_product(group, BitSet([1,2]), 2) == BitSet([2,3])
        @test group_product(group, BitSet([1,2]), BitSet([1,2])) == BitSet([1,2,3])

        @test gp(2, BitSet([1,2])) == BitSet([2,3])
        @test gp(BitSet([1,2]), 2) == BitSet([2,3])
        @test gp(BitSet([1,2]), BitSet([1,2])) == BitSet([1,2,3])

        @test group_inverse(group, 1) == 1
        @test group_inverse(group, 2) == 3
        @test group_inverse(group, 3) == 2
        @test group_inverse(group, [1,2]) == [1,3]

        ginv = group_inverse(group)
        @test ginv(1) == 1
        @test ginv(2) == 3
        @test ginv(3) == 2
        @test ginv([1,2]) == [1,3]

        @test generate_subgroup(group, 1) == BitSet([1])
        @test generate_subgroup(group, 2) == BitSet([1,2,3])
        @test generate_subgroup(group, [1,2]) == BitSet([1,2,3])

        @test issubgroup(group, Set([1]))
        @test !issubgroup(group, Set([1,2]))
        @test minimal_generating_set(group) == [2]

        @test group_multiplication_table([[1 0; 0 1], [1 0; 0 -1]]) == [1 2; 2 1]

        @test ishomomorphic(1:3, group; product=gp)
        @test !ishomomorphic(1:2, group; product=gp)
    end

    @testset "FiniteGroup-Nonabelian" begin
        # C3v, element#2 = C3, element#4 = σᵥₐ
        # (non-abelian)
        group = FiniteGroup([
            1 2 3 4 5 6;
            2 3 1 6 4 5;
            3 1 2 5 6 4;
            4 5 6 1 2 3;
            5 6 4 3 1 2;
            6 4 5 2 3 1;
        ])
        generators = minimal_generating_set(group)
        @test generators == [2, 4]
        @test generate_subgroup(group, generators) == BitSet(1:6) # completely generates

        ϕ = [1, 3, 4, 5, 2, 6] # group isomorphism
        mtab1 = group_multiplication_table(group)
        mtab2 = zeros(Int, (6,6))
        for x in 1:6, y in 1:6
            # ϕ(x)⋅ϕ(y) = ϕ(x⋅y)
            mtab2[ϕ[x], ϕ[y]] = ϕ[mtab1[x,y]]
        end
        group2 = FiniteGroup(mtab2)

        # ϕ: group  →  group2
        #       x   ↦  ϕ(x)
        ϕ2 = group_isomorphism(group, group2)
        mtab3 = zeros(Int, (6,6))
        for x in 1:6, y in 1:6
            # ϕ(x)⋅ϕ(y) = ϕ(x⋅y)
            mtab3[ϕ2[x], ϕ2[y]] = ϕ2[mtab1[x,y]]
        end
        @test !isnothing(group_isomorphism(group2, FiniteGroup(mtab3)))  # ϕ and ϕ2 are equivalent

        for ϕ in permutations(2:6)
            ϕ = vcat([1], ϕ)
            mtab1 = group_multiplication_table(group)
            mtab2 = zeros(Int, (6,6))
            for x in 1:6, y in 1:6
                # ϕ(x)⋅ϕ(y) = ϕ(x⋅y)
                mtab2[ϕ[x], ϕ[y]] = ϕ[mtab1[x,y]]
            end
            group2 = FiniteGroup(mtab2)
            @test !isnothing(group_isomorphism(group, group2))
        end
    end

    @testset "minimal_generating_set" begin
        for i in 1:32
            psym = PointSymmetryDatabase.get(i)
            group = psym.group
            mgs = minimal_generating_set(group)
            @test length(mgs) <= length(psym.generators)
            @test generate_subgroup(group, mgs) == BitSet(1:group_order(group))
        end
    end

    @testset "group isomorphism" begin
        # 4
        group1 = FiniteGroup([
            1 2 3 4;
            2 1 4 3;
            3 4 2 1;
            4 3 1 2;
        ])
        group1p= FiniteGroup([
            1 2 3 4;
            2 3 4 1;
            3 4 1 2;
            4 1 2 3;
        ])
        @test !isnothing(group_isomorphism(group1, group1p))
        # 2/m
        group2 = FiniteGroup([
            1 2 3 4;
            2 1 4 3;
            3 4 1 2;
            4 3 2 1;
        ])
        @test isnothing(group_isomorphism(group1, group2))
    end # @testset "group isomorphism" begin

    @testset "group isomorphism 2" begin
        # D2h
        group1 = FiniteGroup([
            1 2 3 4 5 6 7 8;
            2 1 4 3 6 5 8 7;
            3 4 1 2 7 8 5 6;
            4 3 2 1 8 7 6 5;
            5 6 7 8 1 2 3 4;
            6 5 8 7 2 1 4 3;
            7 8 5 6 3 4 1 2;
            8 7 6 5 4 3 2 1;
        ])
        group2 = FiniteGroup([
            1 2 3 4 5 6 7 8;
            2 1 4 3 6 5 8 7;
            3 4 2 1 7 8 6 5;
            4 3 1 2 8 7 5 6;
            5 6 7 8 1 2 3 4;
            6 5 8 7 2 1 4 3;
            7 8 6 5 3 4 2 1;
            8 7 5 6 4 3 1 2;
        ])
        @test isnothing(group_isomorphism(group1, group2))
    end

    @testset "group isomorphism 3" begin
        tsym1 = FiniteTranslationSymmetry([3 0; 0 3])
        tsym2 = FiniteTranslationSymmetry([3 1; 0 3])
        @test isnothing(group_isomorphism(tsym1.group, tsym2.group))
    end

    # TODO
    # Are there two non-isomorphic groups with
    # - Same conjugacy classes
    # - Same period lengths
end
=#


"""
Fast L2-regularized Linear Regression

Kei Majima developed the original algorithm.
Soma Nonaka implemented the initial Julia version.
Shuntaro C. Aoki modified and refactored the original implementation.
"""

module FastL2LiR

export fit, predict, load_model

using Statistics
using LinearAlgebra
using Glob
using BrainDecoder

struct FastL2LiRModel{T<:Real}
    W::Array{T}
    b::Array{T}
end

function fit(X::Array{T, 2}, Y::Array{T}, alpha::T)::FastL2LiRModel where {T<:Real}
    return _fit(X, Y, alpha, false, 0)
end

function fit(X::Array{T, 2}, Y::Array{T}, alpha::T, num_features::Integer)::FastL2LiRModel where {T<:Real}
    return _fit(X, Y, alpha, true, num_features)
end

function _fit(X::Array{T, 2}, Y::Array{T}, alpha::T, feature_selection::Bool, num_features::Integer)::FastL2LiRModel where {T<:Real}
    """
    Fit L2-regularized linear regression model.
    """

    reshape_y::Bool = false
    if ndims(Y) > 2
        reshape_y = true
        y_shape = size(Y)[2:end]
        Y = reshape(Y, size(Y, 1), :)
    end

    n_sample = size(X, 1)
    n_feat = size(X, 2)
    n_trg = size(Y, 2)

    if n_feat < num_features
        feature_selection = false
    end

    # Append bias term in X
    Xb = [X ones(T, n_sample)]

    if feature_selection
        # With feature selection
        W = zeros(T, n_feat, n_trg)
        b = zeros(T, n_trg)

        C = cor(X, Y, dims=1)
        C = @. abs(C)
        C[C .== NaN] .= 0

        W0 = Xb' * Xb + alpha * I(n_feat + 1)
        W1 = Xb' * Y

        # @inbounds causes seg fault with MKL-linked Julia
        for i = 1:n_trg
            C0 = C[:, i]
            feature_index = sortperm(C0, rev=true)[1:num_features]
            push!(feature_index, size(Xb, 2))  # バイアス項のインデックスを追加する

            try
                Wb = W0[feature_index, feature_index] \ W1[feature_index, i]
            catch
                println("Singular matrix: use QR decomposition")
                Q, R = qr(W0)
                Wb = inv(R) * (Q' * W1[feature_index, i])
            end

            W[feature_index[1:end - 1], i] = Wb[1:end - 1, 1]
            b[i] = Wb[end, 1]
        end
    else
        # Without feature selection
        Wb = (Xb' * Xb + alpha * I(n_feat + 1)) \ (Xb' * Y)
        W = Wb[1:end - 1, :]
        b = Wb[end, :]
    end

    # Rerurns b as a row vector (compat. for PyFastL2LiR)
    b = collect(b')

    if reshape_y
        W = reshape(W, n_feat, y_shape...)
        b = reshape(b, 1, y_shape...)
    end

    return FastL2LiRModel(W, b)
end

function predict(model::FastL2LiRModel, X::Array{T, 2})::Array{T} where {T<:Real}
    """
    Predict y with given x and L2-regularized linear regression model.
    """

    reshape_y::Bool = false
    if ndims(model.W) > 2
        reshape_y = true
        w_shape = size(model.W)[2:end]
        W = reshape(model.W, size(model.W, 1), :)
        b = reshape(model.b, 1, :)
    else
        W = model.W
        b = model.b
    end

    Y_pred = X * W + repeat(b, size(X, 1), 1)

    if reshape_y
        Y_pred = reshape(Y_pred, size(Y_pred, 1), w_shape...)
    end

    return Y_pred
end

function load_model(model_dir::String; chunk_axis=0)::FastL2LiRModel
    """
    Load FastL2LiR model.
    """

    function _load_chunks(dir, name)
        files = sorted(glob(joinpath(dir, name, "*.mat")))
        return cat([load_array(f) for f in files]..., dims=chunk_axis)
    end

    # Load model
    if isfile(joinpath(model_dir, "W.mat"))
        W = load_array(joinpath(model_dir, "W.mat"), "W")
    else
        W = _load_chunks(model_dir, "W")
    end

    if isfile(joinpath(model_dir, "b.mat"))
        b = load_array(joinpath(model_dir, "b.mat"), "b")
    else
        b = _load_chunks(model_dir, "b")
    end

    return FastL2LiRModel(W, b)
end

end # module FastL2LiR


include("../src/components/radiativeforcing_component.jl")

@testset "radiativeforcing" begin

    Precision = 1.0e-11
    T = length(MimiDICE2010.model_years)
    f = readxlsx(joinpath(@__DIR__, "..", "data", "DICE2010_082710d.xlsx"))

    m = Model()

    set_dimension!(m, :time, MimiDICE2010.model_years)

    add_comp!(m, radiativeforcing, :radiativeforcing)

# Set the parameters that would normally be internal connection from their Excel values
    set_param!(m, :radiativeforcing, :MAT, read_params(f, "B112:BI112", T))
    set_param!(m, :radiativeforcing, :MAT_final, read_params(f, "BJ112"))

# Load the rest of the external parameters
    p = dice2010_excel_parameters(joinpath(@__DIR__, "..", "data", "DICE2010_082710d.xlsx"))
    set_param!(m, :radiativeforcing, :forcoth, p[:forcoth])
    set_param!(m, :radiativeforcing, :fco22x, p[:fco22x])

# Run the one-component model
    run(m)

# Extract the generated variables
    FORC = m[:radiativeforcing, :FORC]

# Extract the true values
    True_FORC    = read_params(f, "B122:BI122", T)

# Test that the values are the same
    @test maximum(abs, FORC .- True_FORC) ≈ 0. atol = Precision

end


using Turing
using Turing.RandomMeasures
using Distributions
using Random
using Libtask
using AutoGibbs

const data = [-1.48, -1.40, -1.16, -1.08, -1.02, 0.14, 0.51, 0.53, 0.78]

function update_histogram!(histogram, bin)
    if bin > length(histogram)
        push!(histogram, 1)
    else
        histogram[bin] += 1
    end

    return histogram
end

function histogram(d, z)
    nk = Int[]
    K = 0

    for n in 1:d.N
        nk = update_histogram!(nk, z[n])
        K = max(K, z[n])
    end

    return nk, K
end

mutable struct CRP <: Distributions.DiscreteMultivariateDistribution
    N::Int
    α::Float64
end

CRP(N, α, G₀) = CRP(N, α)

function Distributions.rand(rng::AbstractRNG, d::CRP)
    nk = Int[]
    D = ChineseRestaurantProcess(DirichletProcess(d.α), nk)
    z = rand(rng, D, d.N)
end

function Distributions.logpdf(d::CRP, z::AbstractVector{<:Int})
    nk, K = histogram(d, z)
    D = ChineseRestaurantProcess(DirichletProcess(d.α), nk)
    return mapreduce(zi -> logpdf(D, zi), +, z; init=0.0)
end

function (c::GibbsConditional{V, L})(θ) where {
    V<:VarName, L<:LogLikelihood{<:CRP}}

    Ω = c.base.dist.N

    conditionals = similar(Ωs, DiscreteNonParametric)

    for index in eachindex(Ωs, independent_distributions, conditionals)
        sub_vn = DynamicPPL.VarName(c.vn, (DynamicPPL.getindexing(c.vn)..., (index,)))
        θs_on_support = fixvalues(θ, sub_vn => Ωs[index])
        logtable = map(θs_on_support) do θ′
            c.base(θ′) + reduce(+, (β(θ′) for (vn, β) in c.blanket if vn == sub_vn), init=0.0)
        end
        conditionals[index] = DiscreteNonParametric(Ωs[index], softmax!(vec(logtable)))
    end

    return Product(conditionals)
end

@model function imm_oneshot(y, α, ::Type{T}=Array{Float64, 1}) where {T}
    N = length(y)

    G₀ = Normal()
    z ~ CRP(N, α, G₀)
    K = maximum(z)

    μ = T(undef, K)
    for k = 1:K
        μ[k] ~ G₀
    end

    for n = 1:N
        y[n] ~ Normal(μ[z[n]], 1.0)
    end
end

model_imm_oneshot = imm_oneshot(data[5:7], 10.0)

sample(model_imm_oneshot, Gibbs(AutoConditional(:z), HMC(0.01, 10, :μ)), 2)


module SSmodule
    using LinearAlgebra
    function eigs_SS(A,x)
    end

    function shiftedbicg_Frommer2003(σ,A,b;maxsteps=3000,eps = 1e-15)
        N = length(σ)
        btype = typeof(b)
        q = zero(b)
        x = zero(b)
        r = deepcopy(b)
        rt = deepcopy(b)
        u = zero(b)
        ut = zero(b)
        vec_u = Vector{btype}(undef,N)
        vec_x = Vector{btype}(undef,N)
        for j=1:N
            vec_u[j] = zero(b)
            vec_x[j] = zero(b)
        end
        ρkold = ones(ComplexF64,N)
        ρk = ones(ComplexF64,N)
        πkold = ones(ComplexF64,N)
        πk = ones(ComplexF64,N)
        πknew = ones(ComplexF64,N)
        ρold = 1
        αold = 1
        residual = 0

        for i=1:maxsteps
            ρ = dot(rt,r)
            β = -ρ/ρold
            axpby!(1,r,-β,u)
            axpby!(1,rt,-β',ut)
            mul!(q,A,u)
            qrt = dot(rt,q)
            α = ρ/qrt
            axpy!(α,u,x)

            for k=1:N
                πknew[k] = (1+α*σ[k])*πk[k]+ (α*β/αold)*(πkold[k]- πk[k])
                βk = (πkold[k]/πk[k])^2*β
                αk =  (πk[k]/πknew[k])*α
                axpby!(1/πk[k],r,-βk,vec_u[k])
                axpy!(αk,vec_u[k],vec_x[k])
            end

            axpy!(-α,q,r)
            mul!(q,A',ut)
            axpy!(-α',q,rt)
            αold = α
            ρold  = ρ

            residual = dot(r,r)
            println(dot(r,r))

            if abs(residual) < eps
                println("Converged at $i-th step. eps: $residual")
                println("--------------------------------------")
                return vec_x
            end

            for k=1:N
                πkold[k] = πk[k]
                πk[k] = πknew[k]
            end

        end

        error("bicg is not converged. The residual is $residual")

    end

    function shiftedbicg_Frommer2003_seed(σ,A,b;maxsteps=3000,eps = 1e-15,zseedin = 0im)
        zseed = zseedin
        N = length(σ)
        btype = typeof(b)
        q = zero(b)
        x = zero(b)
        r = deepcopy(b)
        rt = deepcopy(b)
        u = zero(b)
        ut = zero(b)
        vec_u = Vector{btype}(undef,N)
        vec_x = Vector{btype}(undef,N)
        for j=1:N
            vec_u[j] = zero(b)
            vec_x[j] = zero(b)
        end
        ρkold = ones(ComplexF64,N)
        ρk = ones(ComplexF64,N)
        πkold = ones(ComplexF64,N)
        πk = ones(ComplexF64,N)
        πknew = ones(ComplexF64,N)
        ρold = 1
        αold = 1
        residual = 0

        for i=1:maxsteps
            ρ = dot(rt,r)
            β = -ρ/ρold
            axpby!(1,r,-β,u)
            axpby!(1,rt,-β',ut)
            mul!(q,A,u)
            axpby!(zseed,u,1,q)

            qrt = dot(rt,q)
            α = ρ/qrt
            axpy!(α,u,x)

            for k=1:N
                πknew[k] = (1+α*(σ[k]-zseed))*πk[k]+ (α*β/αold)*(πkold[k]- πk[k])
                βk = (πkold[k]/πk[k])^2*β
                αk =  (πk[k]/πknew[k])*α
                axpby!(1/πk[k],r,-βk,vec_u[k])
                axpy!(αk,vec_u[k],vec_x[k])
            end

            axpy!(-α,q,r)
            mul!(q,A',ut)
            axpby!(zseed',ut,1,q)
            axpy!(-α',q,rt)

            residual = dot(r,r)
            println(dot(r,r),"\t",zseed)

            for k=1:N
                πkold[k] = πk[k]
                πk[k] = πknew[k]
            end

            if false

            πkseed = 1
            πkseedold = 1
            kseed = 0
            for k=1:N
                if abs(πk[k]) < abs(πkseed)
                    πkseed =πk[k]
                    πkseedold = πkold[k]
                    zseed = σ[k]
                    kseed = k
                end
            end

            println(kseed)
            if kseed != 0
                axpby!(0,rt,1/πkseed,r)
                axpby!(0,r,1/πkseed,rt)
                axpby!(0,r,1/πkseed,u)
                axpby!(0,r,1/πkseed,ut)
                axpby!(1,vec_x[kseed],0,x)
                ρ = ρ/(πkseedold*πkseedold)
                α = (πkseedold/πkseed)*α

                for k=1:N
                    πkold[k] = πkold[k]/πkseedold
                    πk[k] = πk[k]/πkseed
                end
            end

            end

            αold = α
            ρold  = ρ

            if abs(residual) < eps
                println("Converged at $i-th step. eps: $residual")
                println("--------------------------------------")
                return vec_x
            end

        end

        error("bicg is not converged. The residual is $residual")

    end

    function shiftedbicg_Frommer2003_G_seed(σ,A,b;maxsteps=3000,eps = 1e-15,zseedin = 0im)
        zseed = zseedin
        N = length(σ)
        btype = typeof(b)
        q = zero(b)
        x = zero(b)
        r = deepcopy(b)
        rt = deepcopy(b)
        u = zero(b)
        ut = zero(b)
        vec_u = Vector{btype}(undef,N)
        vec_x = Vector{btype}(undef,N)
        for j=1:N
            vec_u[j] = zero(b)
            vec_x[j] = zero(b)
        end
        ρkold = ones(ComplexF64,N)
        ρk = ones(ComplexF64,N)
        πkold = ones(ComplexF64,N)
        πk = ones(ComplexF64,N)
        πknew = ones(ComplexF64,N)
        ρold = 1
        αold = 1
        residual = 0

        for i=1:maxsteps
            ρ = dot(rt,r)
            β = -ρ/ρold
            axpby!(1,r,-β,u)
            axpby!(1,rt,-β',ut)
            mul!(q,A,u)
            axpby!(zseed,u,-1,q)

            qrt = dot(rt,q)
            α = ρ/qrt
            axpy!(α,u,x)

            for k=1:N
                πknew[k] = (1+α*(σ[k]-zseed))*πk[k]+ (α*β/αold)*(πkold[k]- πk[k])
                βk = (πkold[k]/πk[k])^2*β
                αk =  (πk[k]/πknew[k])*α
                axpby!(1/πk[k],r,-βk,vec_u[k])
                axpy!(αk,vec_u[k],vec_x[k])
            end

            axpy!(-α,q,r)
            mul!(q,A',ut)
            axpby!(zseed',ut,-1,q)
            axpy!(-α',q,rt)

            #ρMAX = maximum(abs.(πknew))^2
            residual = abs(dot(r,r))#*ρMAX)

            #println(residual,"\t",zseed,"\t")#,dot(r,r),"\t",ρMAX)

            for k=1:N
                πkold[k] = πk[k]
                πk[k] = πknew[k]
            end

            if false

            πkseed = 1
            πkseedold = 1
            kseed = 0
            for k=1:N
                if abs(πk[k]) < abs(πkseed)
                    πkseed =πk[k]
                    πkseedold = πkold[k]
                    zseed = σ[k]
                    kseed = k
                end
            end

            println(kseed)
            if kseed != 0
                axpby!(0,rt,1/πkseed,r)
                axpby!(0,r,1/πkseed',rt)
                #axpby!(0,r,1/πkseedold,u)
                #axpby!(0,r,1/πkseedold',ut)
                axpby!(1,vec_x[kseed],0,x)
                ρ = ρ/(πkseedold'*πkseedold)
                α = (πkseedold/πkseed)*α

                for k=1:N
                    πkold[k] = πkold[k]/πkseedold
                    πk[k] = πk[k]/πkseed
                end
            end

            end

            αold = α
            ρold  = ρ

            if abs(residual) < eps
                println("Converged at $i-th step. eps: $residual")
                println("--------------------------------------")
                return vec_x
            end

        end

        error("bicg is not converged. The residual is $residual")

    end

    function shiftedbicgstab_inSS(σjin,A,b;maxsteps=3000,eps = 1e-15,zseed= 0im) #(sigma_i I + A)*x = b
        σj = deepcopy(σjin) .- zseed
        N = length(σj)
        btype = typeof(b)
        r = deepcopy(b)
        temp1 = similar(b)#temps[2]
        mul!(temp1,A,x)
        axpy!(-1,temp1,r) #add!(r,-1,temp1)

        rs = deepcopy(r)#temps[3]
        #substitute_fermion!(rs,r)
        p = deepcopy(r)#temps[4]
        #substitute_fermion!(p,r)
        Ap = similar(b)#temps[5]
        s = similar(b)#temps[6]
        t = similar(b)#temps[7]

        #r = deepcopy(b)
        #p = deepcopy(b)
        #q = similar(b)
        #s = similar(b)
        x = zero(b)

        vec_r = Vector{btype}(undef,N)
        vec_p = Vector{btype}(undef,N)
        vec_x = Vector{btype}(undef,N)
        for j=1:N
            vec_r[j] = deepcopy(b)
            vec_p[j] = deepcopy(b)
            vec_x[j] = zero(b)
        end

        αm = 1.0
        βm = 0.0

        ρm = ones(ComplexF64,N)
        ρ0 = ones(ComplexF64,N)
        ρp = ones(ComplexF64,N)
        τj = deepcopy(ρ0)
        #τjm = deepcopy(ρ0)
        residual = 0

        mul!(p,A',r) #p = (A + zseed*I)'*r
        axpy!(zseed',r,p)
        c1 = dot(p,p)

        for i=1:maxsteps
            c1 = dot(rs,r)
            mul!(Ap,A,p)
            c2 = dot(rs,Ap)
            α = c1/c2
            #s = r - α*A*p
            #axpby!(1,r,β,p) #add!(β,p,1,r)
            # s = r - α Ap
            axpby!(1,r,0,s) #add!(0,s,1,r)
            axpy!(-α,Ap,s) #add!(s,-α,Ap)
            mul!(t,A,s)
            d1 = dot(t,s)
            d2 = dot(t,t)
            ζk = d1/d2

            #r = (1-ω A)s
            axpby!(1,s,0,r)#add!(0,r,1,s)
            axpy!(-ζk,t,r)  #add!(r,-ω,t)

            #x = x + ωs+ αp
            axpy!(ζk,s,x) #add!(x,ω,s)
            axpy!(α,p,x) #add!(x,α,p)

            β = (dot(rs,r)/c1)*(α/ζk)

            #p = r + β*(1-ωA)*p
            axpby!(1,r,β,p) #add!(β,p,1,r)
            axpy!(-ζk*β,Ap,p) #add!(p,-ω*β,Ap)

            αk = alpha
            βk = beta
            println(dot(r,r))

            for j=1:N
                ρkj = ρ0[j]
                if abs(ρkj) < eps
                    continue
                end
                ρkmj =ρm[j]
                ρp[j] = ρkj*ρkmj*αm/(ρkmj*αm*(1.0+αk*σj[j])+αk*βm*(ρkmj-ρkj))
                αkj = (ρp[j]/ρkj)*αk
                ζkj = ζk/(1+ζk*σj[j])

                #! ...  vec_x[j]   = vec_x[j]   + αkj * vec_p[j] +ζkj*τj*ρp[j]*s
                axpy!(αkj,vec_p[j],vec_x[j])
                axpy!(ζkj*τj[j]*ρp[j],s,vec_x[j])

                τjm = τj[j]
                τj[j] = (1/(1+ζk*σj[j]))*τj[j]
                βkj = (ρp[j]/ρkj)^2*βk
                # vec_p[j] = τj[j]*ρp[j]*r + β*(vec_p[j] + (ζkj/αkj)*τjm*(ρp[j]*s-ρkj*r) )
                # vec_p[j] = β*vec_p[j] + β*(τj[j]*ρp[j]-(ζkj/αkj)*τjm*ρkj)*r + β*(ζkj/αkj)*τjm*ρp[j]*s
                axpby!(βkj*(ζkj/αkj)*τjm*ρp[j],s,βkj,vec_p[j])
                axpy!(βkj*(τj[j]*ρp[j]-(ζkj/αkj)*τjm*ρkj),r,vec_p[j])

                #add!(vec_x[j],αkj,vec_p[j])
                #βkj = (ρp[j]/ρkj)^2*βk
                #add!(βkj,vec_p[j],ρp[j],r)
                axpby!(ρp[j],s,βkj,vec_p[j]) #p = beta*p + s
                #add!(βkj,vec_p[j],ρp[j],s)

            end

            ρm .= ρ0
            ρ0 .= ρp
            αm = αk
            βm = βk

            ρMAX = maximum(abs.(ρp))^2
            residual = abs(rr*ρMAX)

            if abs(residual) < eps
                println("Converged at $i-th step. eps: $residual")
                println("--------------------------------------")
                return x,vec_x
            end

        end
        error("bicg is not converged. The residual is $residual")

    end

    function shiftedbicgstab(x,A,b;eps=1e-10,maxsteps = 1000) #Ax=b
        #println_verbose_level3(verbose,"--------------------------------------")
        #println_verbose_level3(verbose,"bicg-stab method")

        #=
        r = deepcopy(b)
        temp1 = similar(x)
        mul!(temp1,A,x)
        add!(r,-1,temp1)

        rs = deepcopy(r)
        p = deepcopy(r)
        Ap = similar(r)
        s = similar(r)
        t = similar(r)
        =#

        #temps = get_temporaryvectors_forCG(A)
        #r = temps[1]
        #substitute_fermion!(r,b)
        r = deepcopy(b)
        temp1 = similar(b)#temps[2]
        mul!(temp1,A,x)
        axpy!(-1,temp1,r) #add!(r,-1,temp1)

        rs = deepcopy(r)#temps[3]
        #substitute_fermion!(rs,r)
        p = deepcopy(r)#temps[4]
        #substitute_fermion!(p,r)
        Ap = similar(b)#temps[5]
        s = similar(b)#temps[6]
        t = similar(b)#temps[7]

        rnorm = real(dot(r,r))

        if rnorm < eps
            return
        end

        for i=1:maxsteps
            c1 = dot(rs,r)
            mul!(Ap,A,p)
            c2 = dot(rs,Ap)
            α = c1/c2
            #s = r - α*A*p
            #axpby!(1,r,β,p) #add!(β,p,1,r)
            # s = r - α Ap
            axpby!(1,r,0,s) #add!(0,s,1,r)
            axpy!(-α,Ap,s) #add!(s,-α,Ap)
            mul!(t,A,s)
            d1 = dot(t,s)
            d2 = dot(t,t)
            ω = d1/d2

            #r = (1-ω A)s
            axpby!(1,s,0,r)#add!(0,r,1,s)
            axpy!(-ω,t,r)  #add!(r,-ω,t)

            #x = x + ωs+ αp
            axpy!(ω,s,x) #add!(x,ω,s)
            axpy!(α,p,x) #add!(x,α,p)

            β = (dot(rs,r)/c1)*(α/ω)

            #p = r + β*(1-ωA)*p
            axpby!(1,r,β,p) #add!(β,p,1,r)
            axpy!(-ω*β,Ap,p) #add!(p,-ω*β,Ap)

            rnorm = real(r ⋅ r)
            println("$i-th eps: $rnorm")

            if rnorm < eps
                println("Converged at $i-th step. eps: $rnorm")
                #println_verbose_level3(verbose,"--------------------------------------")
                return
            end

        end

        error("""
        The BICGstab is not converged! with maxsteps = $(maxsteps)
        residual is $rnorm
        maxsteps should be larger.""")

    end

    function shiftedbicgstab_inSS(x,A,b;eps=1e-10,maxsteps = 1000) #Ax=b

        r = deepcopy(b)
        temp1 = similar(x)
        mul!(temp1,A,x)
        axpy!(-1,temp1,r)
        #add!(r,-1,temp1)

        rnorm = real(dot(r,r))
        println(rnorm)

        if rnorm < eps
            return
        end
        rs = deepcopy(r)
        p = deepcopy(r)
        s = similar(r)

        s = similar(r)
        Ap = similar(x)
        As = similar(x)
        β = 0.0im
        ω = 0.0im

        for i=0:maxsteps
            if i != 0
                #add!(β,p,1,r)
                #add!(p,-ω*β,Ap)
                axpby!(1,r,β,p) #add!(β,p,1,r)
                axpy!(-ω*β,Ap,p) #add!(p,-ω*β,Ap)
            end

            c1 = dot(rs,r)
            mul!(Ap,A,p)
            c2 = dot(rs,Ap)
            α = c1/c2
            #add!(0,s,1,r)
            axpby!(1,r,0,s)
            #add!(s,-α,Ap)
            axpy!(-α,Ap,s)
            mul!(As,A,s)
            s1 = dot(As,s)
            s2 = dot(As,As)

            ω = s1/s2
            axpy!(α,p,x) #add!(x,α,p)

            axpy!(ω,s,x) #add!(x,ω,s)

            axpby!(1,s,0,r) #add!(0,r,1,s)

            axpy!(-ω,As,r) #add!(r,-ω,As)

            rnorm = real(dot(r,r))
            println("$i-th eps: $rnorm")

            if rnorm < eps
                println("Converged at $i-th step. eps: $rnorm")
                #println_verbose_level3(verbose,"--------------------------------------")
                return
            end

            β = (α/ω)*dot(rs,r)/c1

            #=
            axpby!(1,r,β,p) #add!(β,p,1,r)
            axpy!(-ω*β,Ap,p) #add!(p,-ω*β,Ap)
    =#
        end

        error("""
        The BICG is not converged! with maxsteps = $(maxsteps)
        residual is $rnorm
        maxsteps should be larger.""")

    end

    function shiftedbicg_inSS(σjin,A,b;maxsteps=3000,eps = 1e-15,zseedin = 0) #(sigma_i I + A)*x = b
        zseed = zseedin
        σj = deepcopy(σjin) #.- zseed
        N = length(σj)
        btype = typeof(b)
        r = deepcopy(b)
        p = deepcopy(b)
        q = similar(b)
        s = similar(b)
        x = zero(b)

        vec_r = Vector{btype}(undef,N)
        vec_p = Vector{btype}(undef,N)
        vec_x = Vector{btype}(undef,N)
        for j=1:N
            vec_r[j] = deepcopy(b)
            vec_p[j] = deepcopy(b)
            vec_x[j] = zero(b)
        end

        αm = 1.0
        βm = 0.0

        ρm = ones(ComplexF64,N)
        ρ0 = ones(ComplexF64,N)
        ρp = ones(ComplexF64,N)
        residual = 0

        #=
        mul!(p,A',r) #p = (A + zseed*I)'*r
        axpy!(zseed',r,p)
        =#
        c1 = dot(p,p)

        for i=1:maxsteps
            mul!(q,A,p) #q = (A + zseed*I)*p
            axpy!(zseed,p,q)

            c2 = dot(q,q)
            alpha = c1 / c2
            rr = dot(r,r)
            #! ...  x   = x   + alpha * p
            axpy!(alpha,p,x)
            #axpy!(a, X, Y) #Overwrite Y with X*a + Y, where a is a scalar. Return Y.
            #axpby!(a, X, b, Y) #Overwrite Y with X*a + Y*b

            #...  res = res - alpha * q
            axpy!(-alpha,q,r)

            mul!(s,A',r)#s = (A + zseed*I)'*r
            axpy!(zseed',r,s)

            c3 = dot(s,s)

            beta = c3 / c1
            c1 = c3
            #axpby!(a, X, b, Y) #Y = X*a + Y*b
            axpby!(1,s,beta,p) #p = beta*p + s

            αk = alpha
            βk = beta
            println(dot(r,r))
            ρmin = 1
            jmin = 0
            ρkjmin = 1

            for j=1:N
                ρkj = ρ0[j]
                if abs(ρkj) < eps
                    continue
                end
                ρkmj =ρm[j]
                ρp[j] = ρkj*ρkmj*αm/(ρkmj*αm*(1.0+αk*(σj[j]-zseed))+αk*βm*(ρkmj-ρkj))
                if abs(ρmin) > abs(ρp[j])
                    ρmin = ρp[j]
                    jmin = j
                    ρkjmin = ρkj
                end
                #println(ρp[j])
                αkj = (ρp[j]/ρkj)*αk
                #println("$αk alpha = ",αkj)

                #! ...  vec_x[j]   = vec_x[j]   + αkj * vec_p[j]
                axpy!(αkj,vec_p[j],vec_x[j])
                #println("dot ",dot(x,vec_x[j]))
                #add!(vec_x[j],αkj,vec_p[j])
                βkj = (ρp[j]/ρkj)^2*βk
                #add!(βkj,vec_p[j],ρp[j],r)
                #println("ρp[j] ",ρp[j])
                axpby!(ρp[j],s,βkj,vec_p[j]) #p = beta*p + s
                #add!(βkj,vec_p[j],ρp[j],s)
                #println("dot p ",dot(p,vec_p[j]))

            end

            ρm .= ρ0
            ρ0 .= ρp
            αm = αk
            βm = βk

            ρMAX = maximum(abs.(ρp))^2

            if jmin != 0
                zseed = σj[jmin]
                axpby!(0,s,1/ρmin,p)
                axpby!(1,vec_x[jmin],0,x)
                axpby!(0,p,1/ρmin,s)
                c1 /= ρmin
            end

            residual = abs(rr*ρMAX)

            #println(abs.(ρp))

            if abs(residual) < eps
                println("Converged at $i-th step. eps: $residual")
                println("--------------------------------------")
                return x,vec_x
            end

        end
        error("bicg is not converged. The residual is $residual")

    end

end


function filter_records(records, gCols, eCols)
	#Helper function to transform and integrate the measurement records

	#Extract the mean ground and excited traces
	meanGround = squeeze(mean(records[:,gCols,:], [2,3]), [2,3]);
	meanExcited = squeeze(mean(records[:,eCols,:], [2,3]), [2,3]);

	#Take the total mean as the shift operator
	centres = 0.5*(meanGround+meanExcited);

	#Take the difference to get the weighting and angle for the optimum quadrature
	D = meanExcited-meanGround;
	rotAngles = angle(D);

	#We square the weights to normalize to +/- 0.5
	weights = abs(D)/(sum(abs(D).^2));

	#Now we can unwind
	unwoundRecords = (records .- centres) .* exp(-1im*rotAngles);

	#Take the weighted sum and look at the good quadrature
	intRecords = squeeze(sum(weights.*real(unwoundRecords),1),1);

	return intRecords

end

function comp_to_paulis(records)
	#Helper function to convert from computational basis calibrations to pauli basis II, IZ, ZI, ZZ etc.

	#Matrix to convert from computation basis to Pauli basis diagonals
	A = 0.5*[1 1 1 1; 1 -1 1 -1; 1 1 -1 -1; 1 -1 -1 1]

	#Look at mean and variance transformed
	meanSig = mean(records, 2)
	varSig = cov(transpose(records))

	betas = A*meanSig;
	varBetas = A*varSig*A';

	#And the standard error of the mean
	betasErrors = sqrt(diag(varBetas)/size(records,2));

	return betas, betasErrors

end


import LinearAlgebra.dot
import GeometryBasics.Point
import Random.GLOBAL_RNG

export random_vector_matrix, perlin_noise

"""
    random_vector_matrix([rng,] rows, cols)

Produce a matrix with the given number of rows and columns, in which every
entry is a random unit vector. If provided, the given random number generator
is used to determine the vectors.
"""
function random_vector_matrix(rng, rows, cols)
	map(frac_rotation.(rand(rng, rows, cols))) do rot
		rot * unitvec
	end
end

function random_vector_matrix(rows, cols)
    random_vector_matrix(GLOBAL_RNG, rows, cols)
end

function smoothstep(v1, v2, w)
	w = clamp(w, 0., 1.)
	w = 6. * w^5 - 15. * w^4 + 10. * w^3
	(1. - w) * v1 + w * v2
end

"""
    perlin_noise(control_points, point)

Compute the noise gradient for the given point, given a grid of control points.
"""
function perlin_noise(control_points, point::Point)
    rows, cols = size(control_points)
    cell = convert.(Int, (floor.(point)))
    xi, yi = cell

    offset = point - cell
    xf, yf = offset

    n00 = dot(control_points[xi % rows + 1, yi % cols + 1],
        Point(xf, yf))
    n01 = dot(control_points[xi % rows + 1, (yi + 1) % cols + 1],
        Point(xf, yf - 1))
    n10 = dot(control_points[(xi + 1) % rows + 1, yi % cols + 1],
        Point(xf - 1, yf))
    n11 = dot(control_points[(xi + 1) % rows + 1, (yi + 1) % cols + 1],
        Point(xf - 1, yf - 1))

    smoothstep(
        smoothstep(n00, n01, yf),
        smoothstep(n10, n11, yf),
        xf
    )
end


"""
Perform one leapfrog time step with or without Robert+William's filter (see William (2009),
Montly Weather Review, Eq. 7-9)
"""
function leapfrog!( A::AbstractArray{Complex{NF},3},        # a prognostic variable (spectral)
                    tendency::AbstractArray{Complex{NF},2}, # its tendency (dynamics+physics)
                    dt::NF,                                 # time step (=2Δt, but for init steps =Δt,Δt/2)
                    l1::Int,                                # leapfrog index to dis/enable William's filter
                    C::Constants{NF}                        # struct with constants used at runtime
                    ) where {NF<:AbstractFloat}             # number format NF

    mx,nx,nleapfrog = size(A)                           # spectral mx x nx, 2 leapfrog steps

    @boundscheck (mx,nx) == size(tendency) || throw(BoundsError())
    @boundscheck nleapfrog == 2 || throw(BoundsError()) # last dim is 2 for leapfrog
    @boundscheck l1 in [1,2] || throw(BoundsError())    # index l1 calls leapfrog dim

    @unpack robert_filter, williams_filter = C          # coefficients for the Robert and William's filter
    two = convert(NF,2)

    # LEAP FROG time step with or without Robert+William's filter
    # Robert time filter to compress computational mode, Williams' filter for 3rd order accuracy
    # see William (2009), Eq. 7-9
    # for l1 == 1 (initial time step) no filter applied (w1=w2=0)
    # for l1 == 2 (later steps) Robert+William's filter is applied
    w1 = l1 == 1 ? zero(NF) : robert_filter*williams_filter/two         # = ν*α/2 in William (2009, Eq. 8)
    w2 = l1 == 1 ? zero(NF) : robert_filter*(1-williams_filter)/two     # = ν(1-α)/2 in William (2009, Eq. 9)

    @inbounds for j in 1:nx
        for i in 1:mx
            Aold = A[i,j,1]                         # double filtered value from previous time step (t-Δt)
            Anew = Aold + dt*tendency[i,j]          # Leapfrog/Euler step depending on dt=Δt,2Δt (unfiltered at t+Δt)
            Aupdate = Aold - two*A[i,j,l1] + Anew   # Eq. 8&9 in William (2009), calculate only once
            A[i,j,1] = A[i,j,l1] + w1*Aupdate       # Robert's filter: A[i,j,1] becomes the double filtered value at t
            A[i,j,2] = Anew - w2*Aupdate            # Williams' filter: A[i,j,2] becomes the single filtred value at t+Δt
        end
    end
end

"""Leapfrog! for 3D arrays that loops over all vertical layers."""
function leapfrog!( A::AbstractArray{Complex{NF},4},        # a prognostic variable (spectral)
                    tendency::AbstractArray{Complex{NF},3}, # its tendency (dynamics + physics)
                    dt::NF,                                 # time step (mostly =2Δt, but for init steps =Δt,Δt/2)
                    l1::Int,                                # leapfrog index to dis/enable William's filter
                    C::Constants{NF}                        # struct containing all constants used at runtime
                    ) where {NF<:AbstractFloat}             # number format NF

    _,_,_,nlev = size(A)        # A is of size mx x nx x nlev x 2

    for k in 1:nlev
        # extract vertical layers as views to not allocate any memory
        A_layer = view(A,:,:,k,:)
        tendency_layer = view(tendency,:,:,k)

        # make a timestep forward for each layer
        leapfrog!(A_layer,tendency_layer,dt,l1,C)
    end
end

"""Call initialization of semi-implicit scheme and perform initial time step."""
function first_timestep!(   Prog::PrognosticVariables{NF},  # all prognostic variables
                            Diag::PrognosticVariables{NF},  # all pre-allocated diagnostic variables
                            C::Constants{NF},               # struct containing constants
                            G::GeoSpectral{NF},             # struct containing geometry and spectral transform constants
                            HD::HorizontalDiffusion{NF}     # struct containing horizontal diffusion constants
                            ) where {NF<:AbstractFloat}

    @unpack Δt = C

    # FIRST TIME STEP (l1=l2=1, dt=Δt/2)
    IMP = initialize_implicit(half*Δt)
    timestep!(Prog,Diag,1,1,Δt/2,C,G,HD)

    # SECOND TIME STEP (l1=1,l2=2,dt=Δt)
    IMP = initialize_implicit(Δt)
    timestep!(Prog,Diag,1,2,Δt,C,G,HD)

    # Initialize implicit arrays for further time steps (dt=2Δt)
    IMP = initialize_implicit(2Δt)
    return IMP
end

"""Calculate a single time step for SpeedyWeather.jl"""
function timestep!( Prog::PrognosticVariables{NF},  # all prognostic variables
                    Diag::PrognosticVariables{NF},  # all pre-allocated diagnostic variables
                    l1::Int,                        # leapfrog index 1 (en/disables Robert+William's filter)
                    l2::Int,                        # leapfrog index 2 (time step used for tendencies)
                    dt::NF,                         # time step (mostly =2Δt, but for init steps =Δt,Δt/2)
                    C::Constants{NF},               # struct containing constants
                    G::GeoSpectral{NF},             # struct containing geometry and spectral transform constants
                    HD::HorizontalDiffusion{NF}     # struct containing horizontal diffusion constants
                    ) where {NF<:AbstractFloat}

    @unpack vor,div,ps,temp = Prog
    @unpack vor_tend,div_tend,ps_tend,temp_tend = Diag.Tendencies
    @unpack dmp,dmpd,dmps,dmp1,dmp1d,dmp1s = HD
    @unpack tcorh,tcorv,qcorh,qcorv = HD
    @unpack sdrag = C

    # COMPUTE TENDENCIES OF PROGNOSTIC VARIABLES
    get_tendencies!(Prog,Diag,l2,C)

    # DIFFUSION FOR WIND
    vor_l1 = view(vor,:,:,1,:)                                  # array view for leapfrog index
    div_l1 = view(div,:,:,1,:)                                  # TODO l1/l2 dependent?
    horizontal_diffusion!(vor_l1,vor_tend,dmp,dmp1)             # diffusion of vorticity
    horizontal_diffusion!(div_l1,div_tend,dmpd,dmp1d)           # diffusion of divergence

    # DIFFUSION FOR TEMPERATURE
    orographic_correction!(temp_corrected,temp,1,tcorh,tcorv)   # orographic correction for temperature
    horizontal_diffusion!(temp_corrected,temp_tend,dmp,dmp1)    # diffusion for corrected abs temperature

    # DISSIPATION IN THE STRATOSPHERE
    stratospheric_zonal_drag!(vor,vor_tend,sdrag)               # zonal drag for wind
    stratospheric_zonal_drag!(div,div_tend,sdrag)

    horizontal_diffusion!(vor_l1,vor_tend,dmps,dmp1s)           # stratospheric diffusion for wind
    horizontal_diffusion!(div_l1,div_tend,dmps,dmp1s)
    horizontal_diffusion!(temp_corrected,temp_tend,dmps,dmp1s)  # stratospheric diffusion for temperature

    # DIFFUSION OF HUMIDITY
    orographic_correction!(humid_corrected,humid,1,qcorh,qcorv) # orographic correction for humidity
    horizontal_diffusion!(humid_corrected,humid_tend,dmp,dmp1)  # diffusion for corrected humidity

    # if ntracers > 1
    #     for i in 2:ntracers
    #         tracer          = view(tracers,:,:,:,1,i)                   # the i-th tracer, leapfrog index 1
    #         tracer_tendency = view(tracers_tendencies,:,:,:,i)          # its tendency
    #         horizontal_diffusion!(tracer,tracer_tendency,dmp,dmp1)
    #         leapfrog!(tracer,tracer_tendency,j1,C)
    #     end
    # end

    # SPECTRAL TRUNCATION of all tendencies to the spectral resolution
    for tendency in (logp0_tend,vor_tend,div_tend,temp_tend,humid_tend)
        spectral_truncation!(tendency,G)
    end

    # Time integration via leapfrog step forward (filtered with Robert+William's depending on l1)
    leapfrog!(logp0,logp0_tend,dt,l1,C)
    leapfrog!(vor,vor_tend,dt,l1,C)
    leapfrog!(div,div_tend,dt,l1,C)
    leapfrog!(temp,temp_tend,dt,l1,C)
    leapfrog!(humid,humid_tend,dt,l1,C)
end

"""Calculate a single time step for SpeedyWeather.jl"""
function time_stepping!(Prog::PrognosticVariables{NF},  # all prognostic variables
                        Diag::PrognosticVariables{NF},  # all pre-allocated diagnostic variables
                        C::Constants{NF},               # struct containing constants
                        G::GeoSpectral{NF},             # struct containing geometry and spectral transform constants
                        HD::HorizontalDiffusion{NF},    # struct containing horizontal diffusion constants
                        P::Parameters                   # struct containing all model parameters
                        ) where {NF<:AbstractFloat}

    @unpack n_timesteps, Δt = C
    @unpack output = P

    # FEEDBACK, OUTPUT INITIALISATION AND STORING INITIAL CONDITIONS
    # feedback = feedback_initialise(S)
    # ncfile = output_initialise(feedback,S)

    first_timestep!(Prog,Diag,C,G,HD)

    for i in 1:n_timesteps
        timestep!(Prog,Diag,2,2,2Δt,C,G,HD)

        # FEEDBACK AND OUTPUT
        # feedback.i = i
        # feedback!(Prog,feedback,S)
        # output_nc!(i,netCDFfiles,Prog,Diag,S)
    end

    return Prog
end


module Tube

function get_fn(argd::Dict)
    radius = float(argd["radius"])
    k = float(argd["k"])
    center = [float(x) for x in argd["center"]]
    power = argd["exponent"]
    function nomalize{T<:AbstractFloat}(xs::Array{T,1})
        if norm(xs) < 1e-7
            return xs
        else
            return xs / norm(xs)
        end
    end
    direction = normalize([float(x) for x in argd["direction"]])

    function distance{T<:AbstractFloat}(point::Array{T,1})
        p = point - center
        return norm(p - dot(p, direction)*direction)
    end
    function fn_compute(coordinates)
        d = distance(coordinates)
        if d <= radius
            return 0.0
        else
            return 0.5 * k * d^power
        end
    end

    return fn_compute
end

export get_fn
end


export exactpruning!

# Exact pruning
"""
    exactpruning!(man::AbstractCutPruner, optimizer_constructor;
                  ub=Inf, lb=-Inf, epsilon=1e-5)

Remove dominated cuts in CutPruner `man`.

We use a LP solver to determine whether a cut is dominated or not.

# Arguments
* `man::AbstractCutPruner`
    Cut pruner where to remove cuts
* `optimizer_constructor`
    Optimizer used to solve LP
* `ub::Union{Float64, Vector{Float64}}`
    State x upper bound
* `lb::Union{Float64, Vector{Float64}}`
    State x lower bound
* `epsilon::Float64`
    Pruning's tolerance
"""
function exactpruning!(man::AbstractCutPruner, optimizer_constructor; ub = Inf, lb = -Inf, epsilon = 1e-5)
    K = getdominated(man.A, man.b, man.islb, man.isfun, optimizer_constructor, lb, ub, epsilon)
    removecuts!(man, K)
end

"""Return dominated cuts."""
function getdominated(A, b, islb, isfun, optimizer_constructor, lb, ub, epsilon)
    red = Int[]
    if size(A, 1) == 1
        return red
    end
    for i in 1:size(A, 1)
        if isdominated(A, b, islb, isfun, i, optimizer_constructor, lb, ub, epsilon)
            push!(red, i)
        end
    end
    red
end

"""State whether a cut is dominated with a tolerance epsilon."""
function isdominated(A, b, islb, isfun, k, optimizer_constructor, lb, ub, epsilon)
    # we use JuMP to solve the test
    # For instance, if islb & isfun, the LP becomes:
    # min - y
    #     x ∈ R^n, y ∈ R
    #     y <= (A[k, :] - A[i, :])*x + b[k] - b[i]     ∀ i != k

    # we formulate an equivalent problem as
    # min c'*z
    # s.t H*z <= h

    # get problem's dimension
    ncuts, nx = size(A)
    # allocate arrays
    h = zeros(ncuts - 1)
    H = zeros(ncuts - 1, nx + 1)
    c = zeros(nx + 1)
    c[1] = (islb) ? -1 : 1

    λk = @view A[k, :]

    ic = 0
    for ix in 1:ncuts
        if ix != k
            ic += 1
            δb = b[k] - b[ix]
            @inbounds h[ic] = ((isfun & islb) || (~isfun & ~islb)) ? δb : -δb
            @inbounds H[ic, 1] = (islb) ? 1 : -1

            for jx in 1:nx
                dl = A[ix, jx] - λk[jx]
                @inbounds H[ic, jx + 1] = (islb) ? dl : -dl
            end
        end
    end

    # update lower and upper bound if Vector
    lbx = isa(lb, Vector) ? vcat(-Inf, lb) : lb
    ubx = isa(ub, Vector) ? vcat(Inf, ub) : ub

    # solve the LP with JuMP
    model = Model(optimizer_constructor)
    z = @variable(model, [1:nx+1])
    if lb isa Vector
        set_lower_bound.(z, lb)
    end
    if ub isa Vector
        set_upper_bound.(z, ub)
    end

    @constraint(model, H * z .≤ h)
    @objective(model, Min, c ⋅ z)
    optimize!(model)

    status = termination_status(model)

    if status == MOI.INFEASIBLE
        return true
    elseif status == MOI.DUAL_INFEASIBLE
        return false
    elseif status == MOI.OPTIMAL
        res = objective_value(model)
        return (islb) ? -res < epsilon : res > -epsilon
    else
        error("Solver returned status $status: $(raw_status(model)).")
    end
end


@testset "Power Series Differentiation" begin

    T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model=:capped_absolute)

    @test ps_diff(zero(T)) == zero(T)

    @test ps_diff(one(T)) == zero(T)

    @test ps_diff(gen(T)) == one(T)

    @test ps_diff(t^3 - t^4 * (1 // 8) + 5 * t^10) == 3 * t^2 - t^3 * (1 // 2) + 50 * t^9

    @test ps_diff( (1 + t)^1000 ) == truncate(1000 * (1 + t)^999, 299)

end


#!/usr/bin/env julia

# The code for 01b is identical but needs a 3-point sliding sum, which
# I've adapted from SO code for a moving average.
#   https://stackoverflow.com/questions/59562325/moving-average-in-julia
moving_sum(vs,n) = [sum(@view vs[i:(i+n-1)]) for i in 1:(length(vs)-(n-1))]

function main()
    f_int = parse.(Int, readlines("input"))
    f_sum = moving_sum(f_int, 3)

    # `hold` will contain the previous depth
    # `counter' the number of times the depth increases
    hold = 0
    counter = 0

    for i in range(1, length(f_sum))
        val = f_sum[i]

        # There's no depth before the first one
        if i > 1
            if val > hold
                counter += 1
            end
        end
        hold = val
    end

    print(counter)
end

main()


"""
"""
mask

mask(n::NTuple{N}) where {N} =
    mask(ntuple(i -> dirichlet, Val(N)), n)

function mask(bc::NTuple{N}, n::NTuple{N}) where {N}
    opn = _mask.(bc, n)

    ntuple(Val(N)) do i

    end
    center = kron(first.(opn)...)

    center, face, edge, node =
end

function mask(bc::NTuple{1}, n::NTuple{1})
    opn = _mask.(bc, n)
    opn[1]
end

function mask(bc::NTuple{2}, n::NTuple{2})
    opn = _mask.(bc, n)
    ApplyArray(kron, opn[2], opn[1])
end

function mask(bc::NTuple{3}, n::NTuple{3})
    opn = _mask.(bc, n)
    ApplyArray(kron, opn[3], opn[2], opn[1])
end

_mask(::Dirichlet, n::Int) =
    (Diagonal(BitVector(i != n for i in 1:n)),
     Eye{Bool}(n))

_mask(::Periodic, n::Int) =
    (Eye{Bool}(n),
     Eye{Bool}(n))


# This file implements the loss functions
# For now it only includes the generator loss
using Knet
using Statistics

function HingeAdversarialGenerator(fake)
    return mean(-1.*fake)
end

function AdversarialGenerator(fake)
    return mean(softplus(-1.*fake))
end

function softplus(x;beta=1)
    1/beta * log.(1 + exp.(beta.*x))
end


import Distributions

using Distributions, UncertainData

# Create an uncertain dataset containing both theoretical values with known parameters,
# theoretical values with fitted parameters and kernel density estimated distributions.
u1 = UncertainValue(Gamma, rand(Gamma(), 500))
u2 = UncertainValue(rand(MixtureModel([Normal(1, 0.3), Normal(-3, 3)]), 500))
uvals3 = [UncertainValue(Normal, rand(), rand()) for i = 1:11]
measurements = [u1; u2; uvals3]
d = UncertainDataset(measurements)

# Test all available constraints
constraints = [
    NoConstraint(),
    TruncateLowerQuantile(0.2),
    TruncateUpperQuantile(0.3),
    TruncateQuantiles(0.2, 0.6),
    TruncateMaximum(0.2),
    TruncateMinimum(0.2),
    TruncateRange(0.2, 0.6),
    TruncateStd(1)
]

for i = 1:length(constraints)

    # A single constraint applied to all values in the dataset
    @test constrain(d, constraints[i]) isa ConstrainedUncertainDataset

    # Element-wise application of different constraints to the values in the dataset
    @test constrain(d, [constraints[i] for k = 1:length(d)]) isa ConstrainedUncertainDataset

    # Constraining constrained datasets (might be nested several times)
    constrained_dataset = constrain(d, constraints[i])
    @test constrain(constrained_dataset, constraints[i]) isa ConstrainedUncertainDataset
    @test constrain(constrained_dataset, [constraints[i] for k = 1:length(d)]) isa ConstrainedUncertainDataset

end


type LSC
    n_clusters::Int64
    n_landmarks::Int64
    method::Symbol
    non_zero_landmarks::Int64
    bandwidth::Float64
    cluster_result::Vector
end

function LSC(;
             n_clusters::Int64 = 2,
             n_landmarks::Int64 = 150,
             method::Symbol = :Kmeans,
             non_zero_landmarks::Int64 = 4,
             bandwidth::Float64 = 0.4,
             cluster_result::Vector = zeros(10))

    return LSC(n_clusters, n_landmarks, method, non_zero_landmarks,
               bandwidth, cluster_result)

end

function gaussianKernel(distance, bandwidth)
    exp(-distance / (2*bandwidth^2));
end

function get_landmarks(X, p;method=:Kmeans)
    if(method == :Random)
        numberOfPoints = size(X,2);
        landmarks = X[:,randperm(numberOfPoints)[1:p]];
        return landmarks;
    end

    if(method == :Kmeans)
        kmeansResult = kmeans(X,p)
        return kmeansResult.centers;
    end
    throw(ArgumentError("method can only be :Kmeans or :Random"));
end

function compose_sparse_Z_hat_matrix(X, landmarks, bandwidth, r)

    distances = pairwise(Distances.Euclidean(), landmarks, X);
    similarities = map(x -> gaussianKernel(x, bandwidth), distances);
    ZHat = zeros(size(similarities));

    for i in 1:(size(similarities,2))
        topLandMarksIndices = selectperm(similarities[:,i], 1:r, rev=true);
        topLandMarksCoefficients = similarities[topLandMarksIndices, i];
        topLandMarksCoefficients = topLandMarksCoefficients / sum(topLandMarksCoefficients);
        ZHat[topLandMarksIndices,i] = topLandMarksCoefficients;
    end
    return diagm(sum(ZHat,2)[:])^(-1/2) * ZHat;

end

function train!(model::LSC, X::Matrix)
    if size(X,1) > size(X,2)
        X = transpose(X)
    end
    landmarks = get_landmarks(X, model.n_landmarks, method = model.method)
    Z_hat = compose_sparse_Z_hat_matrix(X, landmarks, model.bandwidth,
                                        model.non_zero_landmarks)
    svd_result = svd(transpose(Z_hat))
    temp = transpose(svd_result[1][:,1:model.n_clusters])
    model.cluster_result = kmeans(temp, model.n_clusters).assignments
end

function plot_in_2d(model::LSC, X::Matrix)
    if size(X,1) > size(X,2)
        X = transpose(X)
    end
    Gadfly.plot(x = X[1,:], y = X[2, :], color = model.cluster_result)
end

function test_LSC()
    path_ = "../data"
    data_name = ["smiley", "spirals", "shapes", "cassini1"]
    datasets = copy(data_name)
    for i in 1:4
        datasets[i] = datasets[i] * ".csv"
    end
    for i in 1:4
        datasets[i] = joinpath(path_, datasets[i])
    end
    clusters = [4, 2, 4, 3]
    n_landmarks = [50, 150, 50, 50]
    bandwidth = [0.4, 0.04, 0.04, 0.4]
    count = 0
    df = DataFrame()
    for i in 1:4
        data = readdlm(datasets[i])
        data = transpose(convert(Array{Float64,2}, data[:,1:2]))
        model = LSC(n_clusters = clusters[i], n_landmarks = n_landmarks[i], bandwidth = bandwidth[i])
        train!(model,data)
        dataframe = DataFrame(x=data[1,:],y=data[2,:],group=model.cluster_result,datasets=data_name[i])
        if count == 0
            df = dataframe
            count += 1
        else
            df = vcat(df, dataframe)
        end
        println("Progress: $(i/4*100)%....")
        #println("$(df)")
    end
    println("$(df)")
    println("computing finied, drawing the plot......")
    set_default_plot_size(25cm, 14cm)
    Gadfly.plot(df, x="x", y = "y", color = "group",xgroup = "datasets", Geom.subplot_grid(Geom.point), Guide.title("Large Scale Spectral Clustering"))
end


using FixedSizeArrays
using PlotlyJS

import Base: *, isless

include("utils.jl");
include("algorithms.jl");

p = [Point(randn(), randn()) for i=1:250];
ep = _akltoussaint(p)

# a = Point(0.0, 0.0)
# b = Point(1.0, 0.1)
# c = Point(0.5, 0.4)
# d = Point(1.0, 1.0)
# e = Point(0.1, 1.0)

# wrappoints([a, b, c, d, e, a])

ep_g = _grahamscan(p)
ep_m = _monotonechain(p)

# Plot all points
t1 = scatter(;x=[i[1] for i in p], y=[i[2] for i in p], mode="markers")

# Plot Convex hull from graham scan
t2 = scatter(;x=[i[1] for i in ep_g], y=[i[2] for i in ep_g], mode="lines")

# Plot upper hull
t3 = scatter(;x=[i[1] for i in ep_m], y=[i[2] for i in ep_m], mode="lines")

show(Plot([t1, t2, t3]))


#####
##### Viscous fluxes
#####

@inline viscous_flux_ux(i, j, k, grid, clock, νᶜᶜᶜ::Number, u) = νᶜᶜᶜ * ℑxᶜᵃᵃ(i, j, k, grid, Axᵃᵃᶜ) * ∂xᶜᵃᵃ(i, j, k, grid, u)
@inline viscous_flux_uy(i, j, k, grid, clock, νᶠᶠᶜ::Number, u) = νᶠᶠᶜ * ℑyᵃᶠᵃ(i, j, k, grid, Ayᵃᵃᶜ) * ∂yᵃᶠᵃ(i, j, k, grid, u)
@inline viscous_flux_uz(i, j, k, grid, clock, νᶠᶜᶠ::Number, u) = νᶠᶜᶠ * ℑzᵃᵃᶠ(i, j, k, grid, Azᵃᵃᵃ) * ∂zᵃᵃᶠ(i, j, k, grid, u)

@inline viscous_flux_vx(i, j, k, grid, clock, νᶠᶠᶜ::Number, v) = νᶠᶠᶜ * ℑxᶠᵃᵃ(i, j, k, grid, Axᵃᵃᶜ) * ∂xᶠᵃᵃ(i, j, k, grid, v)
@inline viscous_flux_vy(i, j, k, grid, clock, νᶜᶜᶜ::Number, v) = νᶜᶜᶜ * ℑyᵃᶜᵃ(i, j, k, grid, Ayᵃᵃᶜ) * ∂yᵃᶜᵃ(i, j, k, grid, v)
@inline viscous_flux_vz(i, j, k, grid, clock, νᶜᶠᶠ::Number, v) = νᶜᶠᶠ * ℑzᵃᵃᶠ(i, j, k, grid, Azᵃᵃᵃ) * ∂zᵃᵃᶠ(i, j, k, grid, v)

@inline viscous_flux_wx(i, j, k, grid, clock, νᶠᶜᶠ::Number, w) = νᶠᶜᶠ * ℑxᶠᵃᵃ(i, j, k, grid, Axᵃᵃᶜ) * ∂xᶠᵃᵃ(i, j, k, grid, w)
@inline viscous_flux_wy(i, j, k, grid, clock, νᶜᶠᶠ::Number, w) = νᶜᶠᶠ * ℑyᵃᶠᵃ(i, j, k, grid, Ayᵃᵃᶜ) * ∂yᵃᶠᵃ(i, j, k, grid, w)
@inline viscous_flux_wz(i, j, k, grid, clock, νᶜᶜᶜ::Number, w) = νᶜᶜᶜ * ℑzᵃᵃᶜ(i, j, k, grid, Azᵃᵃᵃ) * ∂zᵃᵃᶜ(i, j, k, grid, w)

# Viscosities-as-functions

@inline viscous_flux_ux(i, j, k, grid, clock, ν::Function, u) =
        viscous_flux_ux(i, j, k, grid, clock, ν(xnode(Cell, i, grid), ynode(Cell, j, grid), znode(Cell, k, grid), clock.time), u)

@inline viscous_flux_uy(i, j, k, grid, clock, ν::Function, u) =
        viscous_flux_uy(i, j, k, grid, clock, ν(xnode(Cell, i, grid), ynode(Face, j, grid), znode(Face, k, grid), clock.time), u)

@inline viscous_flux_uz(i, j, k, grid, clock, ν::Function, u) =
        viscous_flux_uz(i, j, k, grid, clock, ν(xnode(Face, i, grid), ynode(Cell, j, grid), znode(Face, k, grid), clock.time), u)

@inline viscous_flux_vx(i, j, k, grid, clock, ν::Function, v) =
        viscous_flux_vx(i, j, k, grid, clock, ν(xnode(Face, i, grid), ynode(Face, j, grid), znode(Cell, k, grid), clock.time), v)

@inline viscous_flux_vy(i, j, k, grid, clock, ν::Function, v) =
        viscous_flux_vy(i, j, k, grid, clock, ν(xnode(Cell, i, grid), ynode(Cell, j, grid), znode(Cell, k, grid), clock.time), v)

@inline viscous_flux_vz(i, j, k, grid, clock, ν::Function, v) =
        viscous_flux_vz(i, j, k, grid, clock, ν(xnode(Cell, i, grid), ynode(Face, j, grid), znode(Face, k, grid), clock.time), v)

@inline viscous_flux_wx(i, j, k, grid, clock, ν::Function, w) =
        viscous_flux_wx(i, j, k, grid, clock, ν(xnode(Face, i, grid), ynode(Cell, j, grid), znode(Face, k, grid), clock.time), w)

@inline viscous_flux_wy(i, j, k, grid, clock, ν::Function, w) =
        viscous_flux_wy(i, j, k, grid, clock, ν(xnode(Cell, i, grid), ynode(Face, j, grid), znode(Face, k, grid), clock.time), w)

@inline viscous_flux_wz(i, j, k, grid, clock, ν::Function, w) =
        viscous_flux_wz(i, j, k, grid, clock, ν(xnode(Cell, i, grid), ynode(Cell, j, grid), znode(Cell, k, grid), clock.time), w)

#####
##### Viscous dissipation operators
#####

"""
    ∂ⱼνᵢⱼ∂ᵢu(i, j, k, grid, νˣ, νʸ, νᶻ, u)

Calculates viscous dissipation for the u-velocity via

    1/V * [δxᶠᵃᵃ(νˣ * ℑxᶜᵃᵃ(Ax) * δxᶜᵃᵃ(u)) + δyᵃᶜᵃ(νʸ * ℑyᵃᶠᵃ(Ay) * δyᵃᶠᵃ(u)) + δzᵃᵃᶜ(νᶻ * ℑzᵃᵃᶠ(Az) * δzᵃᵃᶠ(u))]

which will end up at the location `fcc`.
"""
@inline function ∂ⱼνᵢⱼ∂ᵢu(i, j, k, grid, clock, νˣ, νʸ, νᶻ, u)
    return 1/Vᵃᵃᶜ(i, j, k, grid) * (δxᶠᵃᵃ(i, j, k, grid, viscous_flux_ux, clock, νˣ, u) +
                                    δyᵃᶜᵃ(i, j, k, grid, viscous_flux_uy, clock, νʸ, u) +
                                    δzᵃᵃᶜ(i, j, k, grid, viscous_flux_uz, clock, νᶻ, u))
end

"""
    ∂ⱼνᵢⱼ∂ᵢu(i, j, k, grid, νˣ, νʸ, νᶻ, v)

Calculates viscous dissipation for the v-velocity via

    1/Vᵛ * [δxᶜᵃᵃ(νˣ * ℑxᶠᵃᵃ(Ax) * δxᶠᵃᵃ(v)) + δyᵃᶠᵃ(νʸ * ℑyᵃᶜᵃ(Ay) * δyᵃᶜᵃ(v)) + δzᵃᵃᶜ(νᶻ * ℑzᵃᵃᶠ(Az) * δzᵃᵃᶠ(v))]

which will end up at the location `cfc`.
"""
@inline function ∂ⱼνᵢⱼ∂ᵢv(i, j, k, grid, clock, νˣ, νʸ, νᶻ, v)
    return 1/Vᵃᵃᶜ(i, j, k, grid) * (δxᶜᵃᵃ(i, j, k, grid, viscous_flux_vx, clock, νˣ, v) +
                                    δyᵃᶠᵃ(i, j, k, grid, viscous_flux_vy, clock, νʸ, v) +
                                    δzᵃᵃᶜ(i, j, k, grid, viscous_flux_vz, clock, νᶻ, v))
end

"""
    ∂ⱼνᵢⱼ∂ᵢu(i, j, k, grid::Grid, νˣ, νʸ, νᶻ, w)

Calculates viscous dissipation for the w-velocity via

    1/Vʷ * [δxᶜᵃᵃ(νˣ * ℑxᶠᵃᵃ(Ax) * δxᶠᵃᵃ(w)) + δyᵃᶜᵃ(νʸ * ℑyᵃᶠᵃ(Ay) * δyᵃᶠᵃ(w)) + δzᵃᵃᶠ(νᶻ * ℑzᵃᵃᶜ(Az) * δzᵃᵃᶜ(w))]

which will end up at the location `ccf`.
"""
@inline function ∂ⱼνᵢⱼ∂ᵢw(i, j, k, grid, clock, νˣ, νʸ, νᶻ, w)
    return 1/Vᵃᵃᶠ(i, j, k, grid) * (δxᶜᵃᵃ(i, j, k, grid, viscous_flux_wx, clock, νˣ, w) +
                                    δyᵃᶜᵃ(i, j, k, grid, viscous_flux_wy, clock, νʸ, w) +
                                    δzᵃᵃᶠ(i, j, k, grid, viscous_flux_wz, clock, νᶻ, w))
end

#####
##### Viscous dissipation for isotropic viscosity
#####

@inline ∂ⱼνᵢⱼ∂ᵢu(i, j, k, grid, clock, ν, u) = ∂ⱼνᵢⱼ∂ᵢu(i, j, k, grid, clock, ν, ν, ν, u)
@inline ∂ⱼνᵢⱼ∂ᵢv(i, j, k, grid, clock, ν, v) = ∂ⱼνᵢⱼ∂ᵢv(i, j, k, grid, clock, ν, ν, ν, v)
@inline ∂ⱼνᵢⱼ∂ᵢw(i, j, k, grid, clock, ν, w) = ∂ⱼνᵢⱼ∂ᵢw(i, j, k, grid, clock, ν, ν, ν, w)


abstract AbstractFeedForwardUnit

type RectifiedLinearUnit <: AbstractFeedForwardUnit
    W:: Vector{Float64}
    bias:: Float64
    activationfunction:: Function

    function RectifiedLinearUnit(W:: Vector{Float64}, bias:: Float64)
        function activationfunction(x)
            max(0,x)
        end
        new(W, bias, activationfunction)
    end
end

type LogisticUnit <: AbstractFeedForwardUnit
    W:: Vector{Float64}
    bias:: Float64
    activationfunction:: Function

    function LogisticUnit(W:: Vector{Float64}, bias:: Float64)
        new(W, bias, σ)
    end
end

function forward(u:: AbstractFeedForwardUnit, X::Array{Float64,2})
    u.activationfunction(X*u.W + u.bias)
end

function forward(u:: AbstractFeedForwardUnit, x::Vector{Float64})
    u.activationfunction(u.W'*x + u.bias)
end


using Statistics
using LinearAlgebra
using PyPlot
using PolynomialBasis
using ImplicitDomainQuadrature
using Revise
using CutCellDG
include("../../../test/useful_routines.jl")
include("transformation-elasticity-solver.jl")

function lame_lambda(k, m)
    return k - 2m / 3
end

function bulk_modulus(l, m)
    return l + 2m / 3
end

function angular_position(points)
    cpoints = points[1, :] + im * points[2, :]
    return rad2deg.(angle.(cpoints))
end

polyorder = 3
nelmts = 33
penaltyfactor = 1e2

folderpath = "examples/potential/cube/"
filename =
    "polyorder-" * string(polyorder) * "-nelmts-" * string(nelmts) * ".png"

width = 1.0              # mm
K1, K2 = 247.0, 192.0    # GPa
mu1, mu2 = 126.0, 87.0   # GPa
lambda1 = lame_lambda(K1, mu1)
lambda2 = lame_lambda(K2, mu2)
stiffness = CutCellDG.HookeStiffness(lambda1, mu1, lambda2, mu2)

rho1 = 3.93e3           # Kg/m^3
rho2 = 3.68e3           # Kg/m^3
V01 = 1.0 / rho1
V02 = 1.0 / rho2

ΔG0 = -14351.0/0.147

theta0 = -0.067
transfstress =
    CutCellDG.plane_strain_transformation_stress(lambda1, mu1, theta0)

meshwidth = [width, width]
numqp = required_quadrature_order(polyorder) + 2

dx = width / nelmts
penalty = penaltyfactor / dx * 0.5 * (lambda1 + mu1 + lambda2 + mu2)

basis = TensorProductBasis(2, polyorder)
corner = [0.8,0.8]

mesh, cellquads, facequads, interfacequads = construct_mesh_and_quadratures(
    meshwidth,
    nelmts,
    basis,
    corner,
    numqp,
)

nodaldisplacement = free_slip_bc_nodal_displacement(
    mesh,
    basis,
    cellquads,
    facequads,
    interfacequads,
    stiffness,
    theta0,
    penalty,
)

refseedpoints, spatialseedpoints, seedcellids =
    CutCellDG.seed_zero_levelset_with_interfacequads(interfacequads, mesh)
normals = CutCellDG.collect_interface_normals(interfacequads, mesh)

spatialpoints = spatialseedpoints[1, :, :]
productreferencepoints = refseedpoints[1, :, :]
parentreferencepoints = refseedpoints[2, :, :]
productreferencecellids = seedcellids[1, :]
parentreferencecellids = seedcellids[2, :]

angularposition = angular_position(spatialpoints)
sortidx = sortperm(angularposition)

parentreferencepoints = parentreferencepoints[:, sortidx]
productreferencepoints = productreferencepoints[:, sortidx]
parentreferencecellids = parentreferencecellids[sortidx]
productreferencecellids = productreferencecellids[sortidx]
spatialpoints = spatialpoints[:, sortidx]
normals = normals[:, sortidx]
angularposition = angularposition[sortidx]

parentstrain = parent_strain_at_reference_points(
    nodaldisplacement,
    basis,
    parentreferencepoints,
    parentreferencecellids,
    mesh,
)
parentstress = parent_stress(parentstrain, stiffness)

productstrain = product_strain_at_reference_points(
    nodaldisplacement,
    basis,
    theta0,
    productreferencepoints,
    productreferencecellids,
    mesh,
)
productstress = product_stress(productstrain, stiffness, theta0)

parentstrainenergy = V02 * strain_energy(parentstress, parentstrain)
productstrainenergy = V01 * strain_energy(productstress, productstrain)

parentradialtraction = CutCellDG.traction_force_at_points(parentstress, normals)
parentsrr = CutCellDG.traction_component(parentradialtraction, normals)

productradialtraction =
    CutCellDG.traction_force_at_points(productstress, normals)
productsrr = CutCellDG.traction_component(productradialtraction, normals)

parentdilatation = CutCellDG.dilatation(parentstrain)
productdilatation = CutCellDG.dilatation(productstrain)

parentcompwork = V02 * (1 .+ parentdilatation) .* parentsrr
productcompwork = V01 * (1 .+ productdilatation) .* productsrr

parentpotential = parentstrainenergy - parentcompwork
productpotential = productstrainenergy - productcompwork

potentialdifference = (productpotential - parentpotential) * 1e9 / abs(ΔG0)

fig, ax = PyPlot.subplots()
ax.plot(angularposition, potentialdifference, color = "black")
ax.grid()
ax.set_ylim(0.0,0.45)
ax.set_xlabel("Angular Position (deg)")
ax.set_ylabel(L"([\Phi] - [G0])/[G0]")
fig.tight_layout()
fig
fig.savefig(folderpath * filename)


using Distributions

m = constructdice()
run(m)

# Uncertain parameters defined in "Projections and Uncertainties about Climate
# Change in an Era of Minimal Climate Policies" (Nordhaus, 2018) EViews file
# provided by the AEJ : https://www.aeaweb.org/articles?id=10.1257/pol.20170046

mcs = @defsim begin
    t2xco2 = LogNormal(1.106, 0.2646)
	ga0 = Normal(0.076, 0.056)
	gsigma1 = Normal(-0.0152, 0.0032)
	mueq = LogNormal(5.851, 0.2649)
	a2 = Normal(0.00227, 0.001135)

    save(damages.DAMAGES)
	save(grosseconomy.YGROSS)
	save(emissions.E)
	save(co2cycle.MU)
	save(damages.DAMFRAC)
end

run(mcs, m, 1000; trials_output_filename = "/tmp/dice-2016/trialdata.csv", results_output_dir="/tmp/dice-2016")


using JuPOT

            # data = ARGS[1]
             d = float(split(ARGS[1], ","))

             meanReturns =  ARGS[2] # Returns a matrix of size(n) with entries between 0-1s
             returns = round(float(split(meanReturns, ",")),3)

             stocks = ARGS[3]
             tickers = split(stocks, ",")

             m = 10
             n = length(tickers)

             matrix = reshape(d,m,n)
             covariance = cov(matrix) #covariance matrix of returns

             # Assets data structure containing, names, expected returns, covarariance
             assets = AssetsCollection(tickers, returns, covariance)
             if minimum(returns) < 0
             min = 0
             else
             min = minimum(returns)
             end
             max = maximum(returns)

             target_ret_test = collect(min:(max-min)/10:max)

             function efficientFrontierMVO(assets, target_ret_test)

                 # Create the Arrays to be returned, and set them to be the length of the
                 # target_ret_test from the input.
                 n = length(target_ret_test)
                 risk = Array(Float64,n)
                 index = Array(Float64,n)

                 for i in 1:n
                     # Get the target return we want to test.
                     target_ret = target_ret_test[i]

                     # Setup and Optimize the SimpleMVO with the new Target Return.
                     mvo = SimpleMVO(assets, target_ret; short_sale=false)
                     result = optimize(mvo)

                     # Risk is the Objective Value. Index the Target Return.
                     risk[i] = result[1]
                     index[i] = round(target_ret,3)
                 end

                 # Convert arrays to strings
               risk_string = ""
              index_string = ""

              for j in 1:length(risk)
                if j < length(risk)
                    risk_string = risk_string * string(risk[j]) * ","
                    else
                    risk_string = risk_string * string(risk[j])
                end
              end

              for k in 1:length(index)
                if k < length(index)
                    index_string = index_string * string(index[k]) * ","
                    else
                    index_string = index_string * string(index[k])
                end
              end

                 # Return as a Tuple of the Arrays
                 text = risk_string * "\n" * index_string
                  return text
             end

             efd = efficientFrontierMVO(assets, target_ret_test)

             outfile = "output.txt"
             f = open(outfile, "w")
             println(f, efd)
             close(f)


@system VaporPressure begin
    # Campbell and Norman (1998), p 41 Saturation vapor pressure in kPa
    a => 0.611 ~ preserve(u"kPa", parameter)
    b => 17.502 ~ preserve(parameter)
    c => 240.97 ~ preserve(parameter) # °C

    es(a, b, c; T(u"°C")): saturation => (t = Cropbox.deunitfy(T); a*exp((b*t)/(c+t))) ~ call(u"kPa")
    ea(es; T(u"°C"), RH(u"percent")): ambient => es(T) * RH ~ call(u"kPa")
    D(es; T(u"°C"), RH(u"percent")): deficit => es(T) * (1 - RH) ~ call(u"kPa")
    RH(es; T(u"°C"), VPD(u"kPa")): relative_humidity => 1 - VPD / es(T) ~ call(u"NoUnits")

    # slope of the sat vapor pressure curve: first order derivative of Es with respect to T
    Δ(es, b, c; T(u"°C")): saturation_slope_delta => (e = es(T); t = Cropbox.deunitfy(T); e*(b*c)/(c+t)^2 / u"K") ~ call(u"kPa/K")
    s(Δ; T(u"°C"), P(u"kPa")): saturation_slope => Δ(T) / P ~ call(u"K^-1")
end


using FFTW,LinearAlgebra,Plots,SpecialFunctions;pic=()->(N=64;P=32N;dt=1/6N;
T=2^13;W=32π^2/3;w=W/P*N;ξ=im*zeros(N);x=(bitreverse.(0:P-1).+2.0^63)/2.0^64;
v=collect(1:P.>P/2).*2 .-1.;r=zeros(N);X=copy(x);V=copy(v);l=1e-14;
E=zeros(3,N);F=copy(E);ik=2π*im*vcat(1,1:N/2,-N/2+1:-1);D=zeros(T,4);
d(y)=(i=Int(mod1(ceil(y*N),N));o=ceil(y*N)-y*N;((i,1-o),(mod1(i-1,N),o)));
ρ(x,y=x)=(r.*=0;for l∈d.((x.+y)./2);for k∈l;r[k[1]]+=k[2]*w;end;end;r);
@time @gif for t ∈1:T;X.=x;V.=v;F.*=NaN;
 E[1,:].=real.(ifft((ξ.=fft(ρ(X,X))./ik;ξ[1]*=0;ξ)));
 for _∈0:9;≈(F,E,rtol=l,atol=0)&&break;F.=E;
 x.=X.+(v.+V)/2*dt;
 @inbounds for j∈1:P;v[j]=V[j]+sum(k->E[1,k[1]]*k[2],d((X[j]+X[j])/2))*dt/6;end;
 E[2,:].=real.(ifft((ξ.=fft(ρ(X,x))./ik;ξ[1]*=0;ξ)));
 @inbounds for j∈1:P;v[j]=v[j]+sum(k->E[2,k[1]]*k[2],d((X[j]+x[j])/2))*4dt/6;end;
 E[3,:].=real.(ifft((ξ.=fft(ρ(x,x))./ik;ξ[1]*=0;ξ)));
 @inbounds for j∈1:P;v[j]=v[j]+sum(k->E[3,k[1]]*k[2],d((x[j]+x[j])/2))*dt/6;end;
 end;x.=mod.(x,1);
 D[t,1:2].=(sum(E[end,:].^2)/N,sum(v.^2)*W/P)./2;D[t,3:4].=sum.((D[t,1:2],v/P));
 scatter(x[1:P÷2],v[1:P÷2],c=1);scatter!(x[P÷2+1:P],v[P÷2+1:P],ylims=(-3,3),c=2);
 D[t,1:3].*=2/W;plot!((1:t)./T,D[1:t,:],legend=0==1,xlims=(0,1)); @show t/T
 plot!((0.5:N)/N,ρ(x[1:P÷2])./W,c=1);plot!((0.5:N)/N,ρ(x[P÷2+1:P])./W,c=2)
end every 8;return (D,N,P,dt,T,W,w););(D,N,P,dt,T,W,w)=pic()
la(x)=log10(abs(x));t=(1:T).*dt;plot(t,la.(D[:,1]),label="electric field energy")
plot!(t,la.(1 .-D[:,3]),label="total energy change");yticks!(-35:1)
plot!(t,la.(D[:,4]),label="total momentum",legend=:bottomright)
γ(x)=imag(sqrt(Complex(x^2+1-sqrt(4*x^2+1))))*sqrt(W/2)/log(10);ylims!(-35,1)
plot!(t,2*γ(2π/sqrt(W/2)).*(t.-T÷8*dt).+la.(D[T÷8,1]),label="predicted")
xlabel!("Time");ylabel!("log 10");savefig("AreaFixedPointQuietSimpson13.pdf")


# The functions dogravdlnMh() and dothermdlnMh() are needed only for "PlotFigure2.jl"
using MatterPower
using HaloMF
using DoubleExponentialFormulas

function dogravdlnMh(
   pk,
   z::Real,
   Ωm::Real,
   lnMh::Real,
   Ωcb = Ωm;
   virial = false,
   t10MF = false,
)
   ρc = 2.775e11 # in units of h^2 M⊙/Mpc^3
   GN = 3 / 8π / 2998^2 / ρc # = 4.786e-20 Mpc/M⊙ is Newton's constant
   if virial # Determine Δm used by the mass function
      E2 = Ωm * (1 + z)^3 + 1 - Ωm # flat Universe
      Ωmz = Ωm * (1 + z)^3 / E2
      Δvir = 18 * π^2 + 82 * (Ωmz - 1) - 39 * (Ωmz - 1)^2
      Δm = Δvir / Ωmz
   else
      Δm = 200
   end
   spl = dndlnMh(pk, z, Δm, Ωcb, lnMh, t10MF = t10MF)
   # %% Compute ρgrav = (1/2) * \int dlnM dn/dlnM Ag*GM^2/R, in units of h^2 M⊙/Mpc^3
   # c(M) relation is due to Equation (2) and "NFW, F, 0-2" of Table 1 in
   # Duffy et al., MNRAS, 390, L64 (2008)
   if virial # For Δ = Δvir(z)
      RΔh = cbrt(exp(lnMh) * 3 / 4π / (ρc * E2 * Δvir)) # in h^-1 Mpc
      A0, B0, C0 = 7.85, -0.081, -0.71 # Avir, Bvir, Cvir in Table 1 of Duffy et al.
   else # For Δ = Δm = 200
      RΔh = cbrt(exp(lnMh) * 3 / 4π / (ρc * Ωcb * Δm)) / (1 + z) # in h^-1 Mpc
      A0, B0, C0 = 10.14, -0.081, -1.01 # Amean, Bmean, Cmean in Table 1 of Duffy et al.
   end
   c = A0 * (exp(lnMh) / 2e12)^B0 * (1 + z)^C0
   dρdlnMh = 0.5 * spl * GN * exp(2 * lnMh) / RΔh * Ag(c)
   halfW = -dρdlnMh / ρc / Ωcb
   Ωgrav = halfW * Ωcb
end

function dothermdlnMh(
   pk,
   z::Real,
   Ωm::Real,
   h0::Real,
   lnMh::Real,
   Ωcb = Ωm;
   massbias = 1.266,
   t10MF = false,
   xmax = 6.0,
)
   ρc = 2.775e11 # in units of h^2 M⊙/Mpc^3
   ρceVcm3 = 1.05375e4 # in units of h^2 eV/cm^3
   Δc = 500 # 500 times the critical density of the Universe
   E2 = Ωm * (1 + z)^3 + 1 - Ωm  # flat Universe
   Δm = Δc * E2 / Ωm / (1 + z)^3 # Δm for mass function
   spl = dndlnMh(pk, z, Δm, Ωcb, lnMh, t10MF = t10MF)
   # %% Pressure profile integral
   # Planck's universal pressure profile
   # Reference: Planck Collaboration, A&A, 550, A131 (2013)
   c, γ, α, β, P0 = [1.81, 0.31, 1.33, 4.13, 6.41]
   upp(x) =
      x^2 * P0 * (0.7 / h0)^1.5 * (c * x)^(-γ) * (1 + (c * x)^α)^((γ - β) / α)
   uppint, err = quadde(upp, 0, xmax)
   # %% Compute ρth = \int dlnM dn/dlnM \int dV Pe, in units of h^2 eV/cm^3
   αp = 0.12
   E2 = Ωm * (1 + z)^3 + 1 - Ωm # for a flat Universe
   RΔh = cbrt(exp(lnMh) * 3 / 4π / (ρc * E2 * Δc)) # in h^-1 Mpc
   Pe =
      1.65 * # eV/cm^3
      (1 / 0.7)^2 * # h^2 cancels h^2 in the critical density in the denominator
      E2^(4 / 3) *
      (exp(lnMh) / massbias / 3e14 / 0.7)^(2 / 3 + αp)
   dρdlnMh = spl * Pe * 4π * RΔh^3 / massbias
   Y = 0.24 # helium abundance
   Ωtherm = (8 - 5Y) / (4 - 2Y) * dρdlnMh * uppint / ρceVcm3
end

"""
    dndlnMh(pk, z, Δm, Ωm, lnMh; t10MF = false)

This function returns a value of the comoving halo number density per log mass interval in units of h^3/Mpc^3 at a specified value of the log(halo mass).

# Arguments
- `pk`(k): a function which returns a linear matter power spectrum with the argument k being the comoving wavenumber.
   - `pk` is in units of (Mpc/h)^3.
- `z::Real`: redshift.
- Δm: halo overdensity with respect to the mean mass density of the Universe
- `Ωm::Real`: present-day matter density parameter. When excluding the neutrino contribution, use Ωcb (baryon + cold dark matter density parameter).
- `lnMh::Real`: log(halo mass)
   - halo mass is in units of M⊙/h.

# Optional keyword arguments
- `t10MF::Bool=false`: if `true`, use `tinker10MF` for the halo multiplicity function. If `false` (the default), use `tinker08MF`.
"""
function dndlnMh(pk, z, Δm, Ωm, lnMh; t10MF = false)
   ρc = 2.775e11 # in units of h^2 M⊙/Mpc^3
   Rh = cbrt(exp(lnMh) * 3 / 4π / ρc / Ωm) # in units of Mpc/h
   σ2 = sigma2(pk, Rh)
   dlnσ2dlnRh = Rh * dsigma2dR(pk, Rh) / σ2
   lnν = 2 * log(1.6865) - log(σ2)
   if t10MF
      MF = tinker10MF(lnν, z, Δm)
   else
      MF = tinker08MF(lnν, z, Δm)
   end
   dndlnMh = -dlnσ2dlnRh * MF / 4π / Rh^3 # in units of h^3 Mpc^-3
end

"""
   Ag(c:Real; method = "poly4")

Integral of the squared Fourier transform of an NFW density profile, ``(1/π)∫_0^∞ dx |u(x,c)|^2``.

*Reference*: Equation (TBD) of Chiang, Makiya, Komatsu & Ménard (in prep)

# Arguments
- `c::Real`: concentration parameter.

# Optional keyword arguments
- `method::String="poly4"`: approximation method.
   - `poly4` (the default) or other values for 4th order polynomial fit.
   - `poly3` for 3rd order polynomial fit.
   - `exact` for numerical integration.
"""
function Ag(c::Real; method = "poly4")
   if method == "poly3" # 3rd order polynomial fit
      y = c - 5
      Ag = 1.0184 + 5.389e-2 * y - 1.514e-3 * y^2 + 3.845e-5 * y^3
   elseif method == "exact" # exact integration
      f = log(1 + c) - c / (1 + c)
      u2(x) =
         (
            sin(x / c) * (sinint((1 / c + 1) * x) - sinint(x / c)) +
            cos(x / c) * (cosint((1 / c + 1) * x) - cosint(x / c)) -
            sin(x) / (1 / c + 1) / x
         )^2
      res, err = quadde(u2, 0, 100)
      Ag = res / π / f^2
   else # 4th order polynomial fit
      y = c - 5
      Ag =
         1.0202 + 5.376e-2 * y - 1.842e-3 * y^2 + 9.489e-5 * y^3 -
         2.352e-6 * y^4
   end
end


module PolyChaos

using SpecialFunctions, SparseArrays, Distributions
import LinearAlgebra: I, dot, SymTridiagonal, eigen, issymmetric
import FFTW: ifft
import Combinatorics: with_replacement_combinations
import Base: show
import AdaptiveRejectionSampling: RejectionSampler, run_sampler!
import Statistics: mean, std, var
import GaussQuadrature: special_eigenproblem!

include("types.jl")
include("multiIndices.jl")
include("evaluate.jl")
include("quadrature_rules.jl")
include("recurrence_coefficients_monic.jl")
include("stieltjes.jl")
include("densities.jl")
include("auxfuns.jl")
include("scalarproduct.jl")
include("tensor.jl")
include("show.jl")
include("polynomial_chaos.jl")

end


using DifferentialEquations
using ProgressMeter
include("YfindBIG.jl")

using PyCall
pygui(:qt)
using PyPlot
pygui(true)

condition(u,t,integrator)= u[5]>max_hit
function condition2(u,t,integrator) # Event when event_f(u,t) == 0
 u[1]
end
affect!(integrator) = terminate!(integrator)
function affect2!(integrator)
integrator.u[5]=integrator.u[5]+1
 end
cb2 = ContinuousCallback(condition2,affect2!,nothing,rootfind = true,   save_positions = (false,true))
cb1 = DiscreteCallback(condition,affect!)
cb=CallbackSet(cb2,cb1)
Hamil(XX,YY,QQ,PP)=( (QQ-XX)^2+(PP-YY)^2 )*( (QQ+XX)^2+(PP+YY)^2 )/((PP^4+2*PP^2*(XX^2-1)+(1+XX^2)^2 )*(QQ^4+2*QQ^2*(YY^2+1)+(YY^2-1)^2 ))
H_test(u)=( (u[3]-u[1])^2+(u[2]-u[4])^2 )*( (u[3]+u[1])^2+(u[2]+u[4])^2 )/ ((u[2]^4+2*u[2]^2*(u[1]^2-1)+(1+u[1]^2)^2 )*(u[3]^4+2*u[3]^2*(u[4]^2+1)+(u[4]^2-1)^2 ))
ODE1(z,w)=conj(  im * w.*( BigFloat("1") ./(z.^2-w.^2)+BigFloat("1") ./(1+w.^2) ))
ODE2(z,w)=conj(  im * z.*( BigFloat("1") ./(w.^2-z.^2)+BigFloat("1") ./(1+z.^2) ))
function Eq_of_M(du,u,p,t)
    du[1]=real(ODE1(u[1]+im*u[2],u[3]+im*u[4]))
    du[2]=imag(ODE1(u[1]+im*u[2],u[3]+im*u[4]))
    du[3]=real(ODE2(u[1]+im*u[2],u[3]+im*u[4]))
    du[4]=imag(ODE2(u[1]+im*u[2],u[3]+im*u[4]))
    du[5]=0
    return
end

function PSS_function_BIG(Q,P,Energy,t_end, max_hit)
    H=(2*Energy)^2
    Y=YfindBIG(Q,P,H)

    if ~isempty(Y) && Y>0

        u0=zeros(BigFloat ,5)
        u0[2]=P #P
        u0[3]=Q #Q
        u0[4]=Y #Y
        #u0[1]=u0[5]=0

        prob = ODEProblem(Eq_of_M,u0,(BigFloat("0."), t_end))
        sol=solve(prob, Vern9(),maxiters=1e20, reltol=1e-30,abstol=1e-28,callback=cb,save_start=true,save_end=true,save_everystep=false)
        # sol=solve(prob, Vern9(),maxiters=1e20, reltol=1e-10,abstol=1e-12,callback=cb,save_start=true,save_end=true,save_everystep=false)
        # sol=solve(prob, RK4(),reltol=1e-8,abstol=1e-10,callback=cb,save_start=true,save_end=true,save_everystep=false)

        uf=zeros(BigFloat ,5)
        uf[1]=sol[1,end] #X
        uf[2]=sol[2,end] #P
        uf[3]=sol[3,end] #Q
        uf[4]=sol[4,end] #Y
        uf[5]=0
        dH=abs(H_test(uf)-H)
        #
        if dH<1e-10
            return Y, sol[:,2:end-1][3,:],sol[:,2:end-1][2,:]
        end

    end

end


@testset "generic.jl" begin
    # alternative implementation of white kernel
    struct WhiteKernel2 <: Kernel end
    (::WhiteKernel2)(x, y) = x == y

    # alternative implementation TensorProductKernel
    struct TensorProduct2{K1<:Kernel,K2<:Kernel} <: Kernel
        kernel1::K1
        kernel2::K2
    end
    function (kernel::TensorProduct2)((x1, x2), (y1, y2))
        return kernel.kernel1(x1, y1) * kernel.kernel2(x2, y2)
    end

    @testset "binary classification" begin
        # probabilities and boolean targets
        p, p̃ = rand(2)
        y, ỹ = rand(Bool, 2)
        scale = rand()
        kernel = SqExponentialKernel() ∘ ScaleTransform(scale)
        val = unsafe_skce_eval(kernel ⊗ WhiteKernel(), p, y, p̃, ỹ)
        @test unsafe_skce_eval(kernel ⊗ WhiteKernel2(), p, y, p̃, ỹ) ≈ val
        @test unsafe_skce_eval(TensorProduct2(kernel, WhiteKernel()), p, y, p̃, ỹ) ≈ val
        @test unsafe_skce_eval(TensorProduct2(kernel, WhiteKernel2()), p, y, p̃, ỹ) ≈ val

        # corresponding values and kernel for full categorical distribution
        pfull = [p, 1 - p]
        yint = 2 - y
        p̃full = [p̃, 1 - p̃]
        ỹint = 2 - ỹ
        kernelfull = SqExponentialKernel() ∘ ScaleTransform(scale / sqrt(2))

        @test unsafe_skce_eval(kernelfull ⊗ WhiteKernel(), pfull, yint, p̃full, ỹint) ≈ val
        @test unsafe_skce_eval(kernelfull ⊗ WhiteKernel2(), pfull, yint, p̃full, ỹint) ≈
              val
        @test unsafe_skce_eval(
            TensorProduct2(kernelfull, WhiteKernel()), pfull, yint, p̃full, ỹint
        ) ≈ val
        @test unsafe_skce_eval(
            TensorProduct2(kernelfull, WhiteKernel2()), pfull, yint, p̃full, ỹint
        ) ≈ val
    end

    @testset "multi-class classification" begin
        n = 10
        p = rand(n)
        p ./= sum(p)
        y = rand(1:n)
        p̃ = rand(n)
        p̃ ./= sum(p̃)
        ỹ = rand(1:n)

        kernel = SqExponentialKernel() ∘ ScaleTransform(rand())
        val = unsafe_skce_eval(kernel ⊗ WhiteKernel(), p, y, p̃, ỹ)

        @test unsafe_skce_eval(kernel ⊗ WhiteKernel2(), p, y, p̃, ỹ) ≈ val
        @test unsafe_skce_eval(TensorProduct2(kernel, WhiteKernel()), p, y, p̃, ỹ) ≈ val
        @test unsafe_skce_eval(TensorProduct2(kernel, WhiteKernel2()), p, y, p̃, ỹ) ≈ val
    end
end


"""
This function makes an elliptic solenoid and calculates the inductance.
    The function calculates the flux through the center of each loop's cross-section
        Lₛ = Φ/I (self inductance = flux per amp)
        Lₘ₂₁ = (Φ₂₁ ⋅ Φ₂₂/|Φ₂₂|) /I
            -mutual inductance from the 1ˢᵗ wire on 2ⁿᵈ is flux from 1 in the 2ⁿᵈ's cross section
            that is aligned with the flux from the second on itself. All per ampere
        Lₘ₂₁ = Lₘ₁₂
        Total L = ∑ᵢ∑ⱼ(Lᵢⱼ)
    The inputs are:
    N - Number of turns
    r₁ - one radius of the ellipse
    r₂ - Second radius of ellipse
    L - length of solenoid (axially)
    kwargs:
    MeasLayers - Number of concentric layers to calc field
                    The field is calculated over concentric ellipses (linearly spaced)
                    This is the number of concentric test point layers
    NLayers -  Number of test points per layer
    NPtsPath  - number of points that each wire loop will be discretized into,
    PlotOn deternimes if the coil will be plotted as it builds

"""
function eval_Solenoid_Induct(
    N, #Number of turns
    r₁, #Radius 1 of ellipse
    r₂,#Radius 2 of ellipse
    L; #Length of solenoid in meters
    DownSampleFac=1,PlotOn=false,NPtsPath=100,NLayers=20)

    if PlotOn
        pygui(true)
        gcf()
    end
    PointPath_SingleLoop =  MakeEllip(r₁, r₂;NPts=NPtsPath)

    LMat = zeros(N, N)
    if PlotOn
        scatter3D(PointPath_SingleLoop[:,1], PointPath_SingleLoop[:,2], PointPath_SingleLoop[:,3])
    end
    NewPP = MakeEllip(r₁,r₂;Center = [0, 0, 1/ N * L],NPts = NPtsPath)

    if PlotOn
        scatter3D(NewPP[:,1], NewPP[:,2], NewPP[:,3])
    end

    Mut, Self, SavedBSelfArr = Mutual_L_TwoLoops(PointPath_SingleLoop, NewPP;DownSampleFac=DownSampleFac,MeasLayers=NLayers,MinThreshold=1e-10,IncludeWireInduct=false,SaveΦ₁=true)
    LMat .+= Self .* OffDiagOnes(N, 1)
    LMat .+= Mut .* OffDiagOnes(N, 2)
    for i in 3:N
        NewPP = MakeEllip(r₁,r₂;Center = [0, 0, (i-1)/ N * L],NPts = NPtsPath)

        if PlotOn
            scatter3D(NewPP[:,1], NewPP[:,2], NewPP[:,3])
        end
        Mut = Mutual_L_TwoLoops(PointPath_SingleLoop, NewPP, SavedBSelfArr;DownSampleFac=DownSampleFac,MeasLayers=NLayers,MinThreshold=1e-10)
        println(Mut)

        LMat .+= Mut .* OffDiagOnes(N, i)

        println(i)
    end
    LMat[N,N] = LMat[1,1]
    return LMat, sum(LMat)

end

"""
This function makes an elliptic solenoid and calculates the field at the center of the solenoid
        Lₘ₂₁ = Lₘ₁₂
        Total L = ∑ᵢ∑ⱼ(Lᵢⱼ)
    The inputs are:
    N - Number of turns
    r₁ - one radius of the ellipse
    r₂ - Second radius of ellipse
    L - length of solenoid (axially)
    kwargs:

    NPts_Coil  - number of points that each wire loop will be discretized into,
"""
function EvalField_Centered(
    N, #Number of turns
    r₁, #Radius 1 of ellipse
    r₂,#Radius 2 of ellipse
    L; #Length of solenoid in meters
    NPts_Coil=100
    )

            FieldCentered = [
                BiotSav(
                    MakeEllip(
                                r₁, #Radius 1 of ellipse
                                r₂;#Radius 2 of ellipse
                                Center = [0, 0, (Wᵢ - 1) / N * L], #The solenoid's axis is in Z
                                NPts = NPts_Coil, #How discretized the windings are
                                ),
                        [0, 0, L/2];
                        MinThreshold = 0.001,
                        )[3]
                        for Wᵢ in 1:N
                        ]

    return sum(FieldCentered)
end


"""
    WebMercatorfromLLA(datum=wgs84)

Convert from LLA to Web Mercator / Pseudo Mercator, following the convention of
proj, which uses a scaling factor of the semi-major axis of the ellipsoid
https://proj.org/operations/projections/webmerc.html.

!!! warning
    For web mapping applications this projection is ubiquitous due to its
    simplicity of implementation, but this simplicity gives rise to poor
    mathematical properties: it doesn't preserve angles away from the equator.
    Other projections should be preferred if possible, and especially when
    measurement is important. See
    https://earth-info.nga.mil/GandG/wgs84/web_mercator/(U)%20NGA_SIG_0011_1.0.0_WEBMERC.pdf
    for an extended discussion.
"""
struct WebMercatorfromLLA <: Transformation
    el::Ellipsoid
end

WebMercatorfromLLA(d::Datum=wgs84) = WebMercatorfromLLA(ellipsoid(d))

"""
    LLAfromWebMercator

Inverse of WebMercatorfromLLA — see the docs for that transformation.
"""
struct LLAfromWebMercator <: Transformation
    el::Ellipsoid
end

LLAfromWebMercator(d::Datum=wgs84) = LLAfromWebMercator(ellipsoid(d))

function (trans::WebMercatorfromLLA)(lla::LLA)
    xy = trans(LatLon(lla.lat, lla.lon))
    SA[xy[1], xy[2], lla.alt]
end

function (trans::WebMercatorfromLLA)(ll::LatLon)
    lat_lim = 85.06 # according to https://epsg.io/3857
    if abs(ll.lat) > lat_lim
        throw(ArgumentError("Exceeded Web Mercator latitude bounds"))
    end
    x, y = web_mercator_forward(ll.lat, ll.lon, trans.el.a)
    SA[x, y]
end

function (trans::LLAfromWebMercator)(point::AbstractVector)
    x = point[1]
    y = point[2]
    lat, lon = web_mercator_reverse(x, y, trans.el.a)
    if length(point) == 2
        LatLon(lat, lon)
    elseif length(point) == 3
        LLA(lat, lon, point[3])
    else
        throw(ArgumentError("Expected input vector of length 2 or 3"))
    end
end

Base.inv(trans::WebMercatorfromLLA) = LLAfromWebMercator(trans.el)
Base.inv(trans::LLAfromWebMercator) = WebMercatorfromLLA(trans.el)

# Web / Psuedo Mercator. Following the scaling convention of proj, the scaling
# will be set to the ellipsoid semi-major axis.
#   https://proj.org/operations/projections/webmerc.html
function web_mercator_forward(lat, lon, scaling)
    x = scaling * deg2rad(lon)
    y = scaling * log(tand((90 + lat)/2))
    (x,y)
end

function web_mercator_reverse(x, y, scaling)
    lon = rad2deg(x / scaling)
    lat = 2 * atand(exp(y / scaling)) - 90
    (lat,lon)
end


# Adapted from:
# SOSDEMO3 --- Bound on Global Extremum
# Section 3.3 of SOSTOOLS User's Manual

@testset "SOSDEMO3 with $(factory.optimizer_constructor)" for factory in sdp_factories
    @polyvar x1 x2

    m = SOSModel(factory)

    @variable m γ

    # Constraint : r(x)*(f(x) - gam) >= 0
    # f(x) is the Goldstein-Price function
    f1 = x1+x2+1
    f2 = 19-14*x1+3*x1^2-14*x2+6*x1*x2+3*x2^2
    f3 = 2*x1-3*x2
    f4 = 18-32*x1+12*x1^2+48*x2-36*x1*x2+27*x2^2

    f = (1+f1^2*f2)*(30+f3^2*f4)

    @constraint m f >= γ

    @objective m Max γ

    JuMP.optimize!(m)

    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT || JuMP.primal_status(m) == MOI.NEARLY_FEASIBLE_POINT

    @test isapprox(JuMP.objective_value(m), 3; rtol=1e-2)
end


using TensorKit
using statmech_tm_solver

#log(1+sqrt(2))/2
#T = mpo_square_ising(log(1+sqrt(2))/2)
T = mpo_triangular_AF_ising_alternative()
#T = mpo_triangular_AF_ising()

psi = bimps(rand, 12, 4)
for ix in 1:50
    global psi
    λA, psi = A_canonical(T, psi)
    λB, psi = B_canonical(T, psi)
    print("$ix, $λA  $λB ")
    println(' ', free_energy(T, psi.B), ' ', free_energy(T, psi.A))
end


include("DenseSync.jl")
using FFTW
using LinearAlgebra
using Plots
gr()

function rungekutta(func, θ0, δt, tmax, rec_func)
    ans = []
    for _ in 1:tmax÷δt
        k1 = func(θ0)
        k2 = func(θ0 + 0.5*δt*k1)
        k3 = func(θ0 + 0.5*δt*k2)
        k4 = func(θ0 + δt*k3)
        θ0 += δt*(k1 + 2k2 + 2k3 + k4)/6.0
        append!(ans, rec_func(θ0))
    end
    return ans
end

function DenseSyncODE(ds::DenseSync, δt, tmax)
    x = append!([0], copy(ds.x))
    y = fft(x)
    function ODEVec(θs)
        es = exp.(1im*θs)
        return imag.(conj.(es).*(ifft(y.*fft(es))))
    end
    N = ds.N
    θ_twisted = [2π*l*ds.p/N for l in 0:N-1]
    function TwistedNorm(θs)
        vec = mod2pi.(θs - θ_twisted)
        vec = [(x≥π ? 2π-x : x) for x in vec]
        return norm(vec)
    end
    # initial value
    σ = 0.1π
    noises = σ*randn(N)/√(N)
    noises = noises .- sum(noises)/N
    θ0 = θ_twisted + noises
    return rungekutta(ODEVec, θ0, δt, tmax, TwistedNorm)
end

N, p = 1900, 100
ds = exact_sol(N, p)

δt, tmax = 10^(-3), 4
ts = [i*δt for i in 1:tmax÷δt]
norms = [DenseSyncODE(ds, δt, tmax) for _ in 1:5]
plot(
    ts, [log.(norm) for norm in norms],
    xlims=(0, 4), label=false,
    xlabel="t", ylabel="log norm"
)
savefig("ode.png")


using RandomProcesses

d = CRP(100, 0.01)
x = rand(d)
pdf(d, x)

d = CRP(100, 1.0)
x = rand(d)
pdf(d, x)
shuffle!(x)
pdf(d, x)

d = CRP(100, 10000.0)
x = rand(d)
pdf(d, x)


using MultivariatePolynomials
using DynamicPolynomials
using SemialgebraicSets
using JuMP

import MultivariateMoments.moment_matrix
import SemialgebraicSets.inequalities

# Pirates an AlgebraicSet as a workaround for a design problem.
function SemialgebraicSets.inequalities(::AlgebraicSet)
	[]
end

struct Moments
        maxdegree::Integer
        domain::AbstractSemialgebraicSet
end
struct MomentsVar{T}
        maxdegree::Integer
        pvars::Vector{PolyVar{T}}
        mvars::AbstractArray{VariableRef,1}
        model
end
function JuMP.build_variable(_error, info, s::Moments; extra_kw_args...)
        return s
end

"""
Creates truncated moment matrices M_t(μ), and localizing matrices M_t(gμ),
where _g_ is a polynomial-like type, _μ_ is a measure, and _t_ is an integer.

Lasserre, Jean-Bernard. (2004). Global Optimization With Polynomials And The
Problem Of Moments. SIAM Journal on Optimization. 11. 10.1137/S1052623400366802.
"""
function moment_matrix(m::MomentsVar)
	ys = reverse(monomials(m.pvars, 0:m.maxdegree÷2))
	MomentMatrix(value.(linearize.(m, ys * ys')), ys)
end
function moment_matrix(m::MomentsVar, t::Integer)
	ys = reverse(monomials(m.pvars, 0:t÷2))
	linearize.(m, ys * ys')
end
function moment_matrix(m::MomentsVar, t::Integer, g::Polynomial)
	round_up_even(i::Integer) = (i + 1) & ~(1)
	tg = round_up_even(maxdegree(g))
	ys = reverse(monomials(m.pvars, 0:t÷2-tg÷2))
	linearize.(m, ys * ys' * g)
end

function JuMP.add_variable(model, s::Moments, name::String)
        pvars = variables(s.domain)
        monos = monomials(pvars, 0:s.maxdegree)
        mvars = @variable(model, [monos], base_name = name)
        mseq = MomentsVar(s.maxdegree, pvars, mvars, model)

        Mt = moment_matrix(mseq, s.maxdegree)
        Mi = moment_matrix.(mseq, s.maxdegree, inequalities(s.domain))
        Me = moment_matrix.(mseq, s.maxdegree, equalities(s.domain))

        @constraint(model, mvars[end] == 1)
        @constraint(model, Mt in PSDCone())
        @constraint(model, [i in Mi], i in PSDCone())
        @constraint(model, [i in Me], i .== zero(i))

        return mseq
end

"""
Computes the expectation of a polynomial-like type with respect to a truncated
moment sequence.
"""
linearize(m::MomentsVar, t::Term) = coefficient(t) * linearize(m, monomial(t))
linearize(m::MomentsVar, t::Polynomial) = sum(linearize.(m, terms(t)))
function linearize(m::MomentsVar, mono::Monomial)
	# workaround DynamicPolynomials' type instability

        if m.pvars == variables(mono)
                m.mvars[mono]
        else
                x = m.pvars
                ty = subs(mono, x => ones(length(x)))
                y = variables(ty)
                tx = subs(mono, y => ones(length(y)))

                ty * m.mvars[tx]
        end
end

Base.broadcastable(x::MomentsVar) = Ref(x)