#!/usr/bin/env julia

Fract_KelvinVoigt =  RheoModelClass(
        # Model name
        name="fractKV",
        # Model parameters,
        p = (:cₐ, :a, :cᵦ, :β),
        # Relaxation modulus
        G = quote
              cₐ*t^(-a)/gamma(1 - a) + cᵦ*t^(-β)/gamma(1 - β)
            end,
        # Creep modulus
        J = quote
              (t^(a)/cₐ)*mittleff(a - β, 1 + a, -cᵦ*t^(a - β)/cₐ)
            end,
        # Storage modulus
        Gp = quote
                cₐ*ω^a*cos(a*π/2) + cᵦ*ω^β*cos(β*π/2)
             end,
        # Loss modulus
        Gpp = quote
                cₐ*ω^a*sin(a*π/2) + cᵦ*ω^β*sin(β*π/2)
              end,
        # Constraints
        constraint = quote
                 all([  (a<1) & (a>0)
                         (β<1) & (β>0)
                          -a+β < 0])
                end,
        # Network
        info= "
                ________ ╱╲ ________
               |         ╲╱  cₐ, a  |
           ____|                    |____
               |                    |
               |________ ╱╲ ________|
                         ╲╱  cᵦ, β
                "
        )

FractS_KelvinVoigt =  RheoModelClass(
        # Model name
        name="fractSpringKV",
        # Model parameters,
        p = (:cₐ, :a, :k),
        # Relaxation modulus
        G = quote
              cₐ*t^(-a)/gamma(1 - a) + k
            end,
        # Creep modulus
        J = quote
              (t^(a)/cₐ)*mittleff(a, 1 + a, -k*t^a/cₐ)
            end,
        # Storage modulus
        Gp = quote
                #if a!=1.0        #  This is to be removed as the condition contradicts the constraints
                    cₐ*ω^a*cos(a*π/2) + k
                #else
                #    k
                #end
             end,
        # Loss modulus
        Gpp = quote
                cₐ*ω^a*sin(a*π/2)
              end,
        # Constraints
        constraint = quote
                 (a<1) & (a>0)
                end,
        # Network
        info= "
                ________ ╱╲ ________
               |         ╲╱  cₐ, a  |
           ____|                    |____
               |                    |
               |____╱╲  ╱╲  ╱╲  ____|
                      ╲╱  ╲╱  ╲╱
                                k
                "
        )

FractD_KelvinVoigt =  RheoModelClass(
        # Model name
        name="fractDashpotKV",
        # Model parameters,
        p = (:η, :cᵦ, :β),
        # Relaxation modulus
        G = quote
            cᵦ*t^(-β)/gamma(1 - β)
            #t!=0.0 ? cᵦ*t^(-β)/gamma(1 - β) : Inf
        end,
        # Creep modulus
        J = quote
              (t/η)*mittleff(1 - β, 1 + 1, -cᵦ*t^(1.0 - β)/η)
            end,
        # Storage modulus
        Gp = quote
                # cosine floating point error work-around
                if β==1.0
                    0.0
                else
                    cᵦ*ω^β*cos(β*π/2)
                end
             end,
        # Loss modulus
        Gpp = quote
                η*ω + cᵦ*ω^β*sin(β*π/2)
              end,
        # Constraints
        constraint = quote
                 (β<1) & (β>0)
                end,
        # Network
        info= "
                        ___
                _________| |________
               |        _|_| η      |
           ____|                    |____
               |                    |
               |________ ╱╲ ________|
                         ╲╱  cᵦ, β
                "
                )

KelvinVoigt =  RheoModelClass(
        # Model name
        name="KelvinVoigt",
        # Model parameters,
        p = (:η, :k),
        # Relaxation modulus  -    Should be singular
        G = quote
            k
        end,
        # Creep modulus
        J = quote
              (1 - exp(-k*t/η))/k
            end,
        # Storage modulus
        Gp = quote
                k
             end,
        # Loss modulus
        Gpp = quote
                η*ω
              end,
        # Network
        info= "
                        ___
                _________| |________
               |        _|_| η      |
           ____|                    |____
               |                    |
               |____╱╲  ╱╲  ╱╲  ____|
                      ╲╱  ╲╱  ╲╱
                                k
                "
                )


function solve()
    m = 1000000007
    s = parse(Int, readline())
    dp = zeros(Int, s)
    for i=3:s
        dp[i] = 1
        for j=3:i-3
            dp[i] = (dp[i] + dp[j]) % m
        end
    end
    dp[s]
end

println(solve())


module InformationInequalities
using Combinatorics
using Plots
using LinearAlgebra
using Graphs
using GraphRecipes
using LaTeXStrings
# Write your package code here.

include("utils/utils.jl")
include("entropy/entropies.jl")
include("mutual_information/mi_measures.jl")
include("i_measures/imeasures.jl")
include("visualization/ploting.jl")

export ConditionalEntropyList
export ConditionalMutualInformationList
export ElementalMeasures
export Elemental2Canonical
export Elemental2Canonical_H
export Elemental2Canonical_MI
export InformationExpressionToCanonical
export InformationMeasureToCanonical
export LinearInformationExpressionToCanonical

export entropic_matrix
export find_matrixG
export find_entropic_vector
export unique_entropy_vector
export entropy_vector
export find_entropic_vector
export find_entropic_vector_int

export minimal_EIM_list_canonical
export order_entropic
export order_entropic_expression

export plotEntropyTree
export GeometryConeGamma2
export elementsGamma2

export order_string
export find_subset
export order_entropic1
export numEIM

export plotInformationExpression
export plotIE

export simplify
export simplifyH
export entropic_terms
export minimalE

end


mutable struct pbtfProblem{T<:Real}
    y       :: Vector{T}               # response
    x       :: Vector{T}               # predictor
    k       :: Int                     # order of desired polynomial, only 1 or 2 is allowed
    λ       :: Union{T,Nothing}        # Moreau-Yosida envelope parameter
    s       :: T                       # IG(s, r) prior for σ²
    r       :: T
    s₂      :: Union{T,Nothing}        # β'(n-k,s₂) prior for α
    m       :: Int                     # total number of observations
    n       :: Int                     # number of grid locations
    xgrid   :: Vector{T}               # strictly increasing grid locations (length n)
    w       :: Vector{T}               # number of observations at each location (length n)
    ybar    :: Vector{T}               # the average of y at each location (length n)
    sse     :: T                       # sum of squared errors
    TM      :: SparseMatrixCSC{T,Int}  # n×n transformation matrix
    TMᵀ     :: SparseMatrixCSC{T,Int}  # n×n transformation matrix transposed
    D       :: SparseMatrixCSC{T,Int}  # (n-k-1)×n difference matrix
    inv_DDᵀ_D :: Matrix{T}             # used to determine the bisection interval
    θ_proj  :: Vector{T}               # the following arrays are pre-created containers to avoid creating
    β       :: Vector{T}               # new arrays during the course of MCMC sampling
    res     :: Vector{T}
end

function pbtfProblem(y::AbstractArray{T}, x::AbstractArray{T}, k::Int;
                     thinning::Bool=false,nbins::Int=100,λ::Union{T,Nothing}=nothing,
                     s::T=1e-3,r::T=1e-3,s₂::Union{T,Nothing}=nothing) where T<: Real
    if (k!=1) & (k!=2)
        throw(ArgumentError("Currenly only k=1 and k=2 are accepted for pbtf."))
    end
    m = length(x)
    xgrid = sort(unique(x))
    n = length(xgrid)
    if ((k==1) & (n>1000)) || ((k==2) & (n>200))
        @warn "Transformation matrix is likely too ill-conditioned, setting thinning=true is recommended."
    end
    w = zeros(T,n)
    ybar = zeros(T,n)
    sse = 0.0
    for i in 1:n
        w[i] = length(x[x.==xgrid[i]])
        ybar[i] = mean(y[x.==xgrid[i]])
        sse += sum(abs2, y[x.==xgrid[i]].-ybar[i])
    end
    if thinning
        xgrid,w,ybar,sse = thin(xgrid,w,ybar,sse,nbins)
        n = nbins
    end
    if λ==nothing
        λ = min(1/n^2,1e-4*var(y))
    end
    if s₂==nothing
        s₂ = sqrt(n)
    end
    TM = getTM(k,n,xgrid)
    TMᵀ = SparseMatrixCSC(TM')
    D1 = getD(0,n-k)
    inv_DDᵀ_D = inv(Matrix(D1*D1'))*D1
    D = getD(k,n,xgrid)
    θ_proj = zeros(T,n)
    β = zeros(T,n)
    res = zeros(T,n)
    pbtfProblem{T}(y,x,k,λ,s,r,s₂,m,n,xgrid,w,ybar,sse,TM,TMᵀ,D,inv_DDᵀ_D,θ_proj,β,res)
end

dimension(problem::pbtfProblem) = problem.n + 2
capabilities(::Type{<:pbtfProblem}) = LogDensityOrder{1}()

function logdensity_and_gradient(problem::pbtfProblem, z)
    @unpack y,x,k,λ,s,r,s₂,m,n,xgrid,w,ybar,sse,TM,TMᵀ,D,inv_DDᵀ_D,θ_proj,β,res=problem
    θ         = view(z,1:n)
    logσ²     = z[n+1]
    σ²        = exp(logσ²)
    logα      = z[n+2]
    α         = exp(logα)
    α_proj    = 0.0
    if (k==1) & (n<=200)
        α_proj = absepi_projection!(θ_proj,k+1,θ,α)
        forward_solve!(β,k+2,TM,θ)
    else
        α_proj = diffepi_projection!(θ_proj,k,θ,α,inv_DDᵀ_D)
        forward_solve!(β,k+1,TM,θ)
    end
    res      .= ybar .- β
    ssm       = sum(res .* w .* res)
    qf        = (ssm+sse+2*r)/(2*σ²)
    dist²     = norm(θ.-θ_proj,2)^2+(α-α_proj)^2
    logl      = -dist²/(2*λ)-qf-(m/2+s)*logσ²
    ∇         = similar(z)
    if (k==1) & (n<=200)
        back_solve!(view(∇,1:n),k+2,TMᵀ,w.*res)
    else
        back_solve!(view(∇,1:n),k+1,TMᵀ,w.*res)
    end
    logl += logα-(n-k+s₂)*log(1+α)
    ∇[n+2]  = 1 -α/λ*(α-α_proj)-(n-k+s₂)*α/(1+α)
    ∇[1:n]  ./= σ²
    ∇[1:n]  .-= (θ.-θ_proj)./λ
    ∇[n+1]    = qf-(m/2+s)
    logl, ∇
end


using LinearAlgebra
"""
    get_hermite_basis(n = 3, varcoeff = false)

Get Hermite basis function of order `n`.
"""
function get_hermite_basis(n = 3, varcoeff = false)
    global x
    global h
    xa = SymPy.symbols("xa")
    xb = SymPy.symbols("xb")
    if (n < 3) || (n % 2 != 1)
        println("n = ", n)
        error("The degree of Hermite polynomials should be odd and >= 3")
    end
    E = Matrix(1I, n + 1, n + 1)
    symString = ""
    for i = 1:n
        symString *= ("a" * string(i - 1, base=10) * " ")
    end
    symString *= ("a" * string(n, base=10))

    L = symbols(symString)
    basis = Array{SymPy.Sym}(undef, n+1)
    # p is a polynomial of degree n
    p = 0
    for i = 1:n+1
        p = p + L[i]*x^(i-1)
    end

    P = [] # the list containing the derivatives of p
    push!(P, p)
    for i = 1:((n+1)/2 - 1)
        push!(P, diff(p, x, convert(Int64, i)))
    end

    for i = 1:(n + 1)
        Leq = [];
        for j = 1:convert(Int64, (n+1)/2 )
            if (varcoeff)
                push!(Leq, subs(P[j], x, xa))
            else
                push!(Leq, subs(P[j], x, 0))
            end
        end
        for j = 1:convert(Int64, (n+1)/2 )
            if (varcoeff)
                push!(Leq, subs(P[j], x, xb))
            else
                push!(Leq, subs(P[j], x, h))
            end
        end
        Leq = Leq - E[1:(n+1), i]
        LL = solve(convert(Array{SymPy.Sym, 1}, Leq), collect(L[1:(n+1)]))
        LL = convert(Dict{SymPy.Sym, SymPy.Sym}, LL)
        p = 0
        for j = 1:(n+1)
            p = p + LL[L[j]] * x^(j - 1)
        end
        basis[i] = p
    end
    basis
end

"""
    get_hermite_em(p = 3, m = 0, n = 0)

Get Hermite finite elements elementary matrices.

# Arguments
  * `p`: degree of polynomials in the basis.
  * `m`: number of derivatives on the first function.
  * `n`: number of derivatives on the second function.
"""
function get_hermite_em(p = 3, m = 0, n = 0)
    global x
    global h
    M = Array{SymPy.Sym}(undef, p+1, p+1)
    F = get_hermite_basis(p)
    for i = 1:p+1
        for j = 1:p+1
            M[i, j] = simplify(integrate(diff(F[i], x, m) * diff(F[j], x, n), (x, 0, h)))
        end
    end
    M
end

"""
    get_hermite_em_varcoeff(p = 3, m = 0, n = 0, f = 1)
    Get Hermite finite elements elementary matrices for variable coefficients.

# Arguments
  * `p`: degree of polynomials in the basis.
  * `m`: number of derivatives on the first function.
  * `n`: number of derivatives on the second function.
  * `f`: the variable coefficient.
"""
function get_hermite_em_varcoeff(p = 3, m = 0, n = 0, f = 1)
    global x
    global h
    xa = SymPy.symbols("xa")
    xb = SymPy.symbols("xb")
    M = Array{SymPy.Sym}(undef, p+1, p+1)
    F = get_hermite_basis(p, true)
    for i = 1:p+1
        for j = 1:p+1
            M[i, j] = simplify(integrate(diff(F[i], x, m) * diff(F[j], x, n) * f, (x, xa, xb)))
            M[i, j] = simplify(subs(M[i, j], h, xb - xa))
        end
    end
    M
end

"""
    get_square_hermite_em((px, py) = (3, 3), (mx, my) = (0, 0), (nx, ny) = (0, 0))

Get Hermite finite elements elementary matrices for a squared element.

# Arguments
  * `(px, py)` : degree of polynomials in the basis.
  * `(mx, my)` : number of derivatives on the first function wrt x and y
  * `(nx, ny)` : number of derivatives on the second function wrt x and y

"""
function get_square_hermite_em((px, py) = (3, 3), (mx, my) = (0, 0), (nx, ny) = (0, 0))
    Mx = get_hermite_em(px, mx, nx)
    My = get_hermite_em(py, my, ny)

    node_x = [(1, 2, 1, 2, 3, 4, 3, 4, 3, 4, 3, 4, 1, 2, 1, 2)]
    node_y = [(1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 4, 4)]

    kx = div(px, 2)
    ky = div(py, 2)
    p = (px + 1)*(py + 1)
    M = Array{SymPy.Sym}(undef, p, p)

    for i = 1:p
        for j = 1:p
            M[i,j] = Mx[node_x[kx][i], node_x[kx][j]] * My[node_y[ky][i], node_y[ky][j]]
        end
    end
    M
end

"""
    interpolate(fd::Matrix{Float64}, t::Vector{Float64}, ti::Vector{Float64})

Interpolates `fd` from `t` to `ti`.

# Arguments:
  * `fd`: values and derivatives of function to interpolate.
  * `t`: values in which `fd` is known.
  * `ti`: values in which `fd` is interpolated.
"""
function interpolate(fd, t, ti)
    global x
    global h
    p = size(fd, 1) # 2*p -1 is the degree of polynomials
    n = size(fd, 2) # number of samples
    if (n != length(t))
        error("The number of columns in the Matrix fd should be egal to the number of elements in the Vector t.")
    end
    if (norm(diff(diff(t))) > 1e-12 || norm(diff(diff(ti))) > 1e-12 ||
        abs(t[1]-ti[1])>1e-12 || abs(t[end] - ti[end]) > 1e-12)
        error("Vectors t and ti should be uniform partitions of [a,b] interval.")
    end
    F = get_hermite_basis(2*p - 1)
    DT = t[2] - t[1];
    FDT = []
    for i = 1:2*p
        push!(FDT, SymPy.subs(F[i], h, DT))
    end
    ni = length(ti)
    fi = collect(zeros(ni , 1))
    for i = 1:ni-1
        j = floor(ti[i] / DT) + 1
        j = convert(Int64, j)
        xx = ti[i] - t[j]
        S = 0;
        for l = 1:p
            S = S + fd[l, j] * SymPy.subs(FDT[l], x, xx)
            S = S + fd[l, j + 1] * SymPy.subs(FDT[l+p], x, xx)
        end
        fi[i] = S
        if (isnan(S))
            fi[i] = 0
        end

    end
    fi[ni] = fd[1, n]
    fi
end


# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors    #src
# This Source Code Form is subject to the terms of the Mozilla Public License   #src
# v.2.0. If a copy of the MPL was not distributed with this file, You can       #src
# obtain one at https://mozilla.org/MPL/2.0/.                                   #src

# # K-means clustering via SDP

# From "Approximating K-means-type clustering via semidefinite programming" By
# Jiming Peng and Yu Wei.
#
# Given a set of points $a_1, \ldots, a_m$  in $R_n$, allocate them to k clusters.

using JuMP
import LinearAlgebra
import SCS
import Test  #src

function example_cluster(; verbose = true)
    ## Data points
    n = 2
    m = 6
    a = Any[
        [2.0, 2.0], [2.5, 2.1], [7.0, 7.0], [2.2, 2.3], [6.8, 7.0], [7.2, 7.5]
    ]
    k = 2
    ## Weight matrix
    W = zeros(m, m)
    for i in 1:m
        for j in i + 1:m
            W[i, j] = W[j, i] = exp(-LinearAlgebra.norm(a[i] - a[j]) / 1.0)
        end
    end
    model = Model(SCS.Optimizer)
    set_silent(model)
    ## Z >= 0, PSD
    @variable(model, Z[1:m, 1:m], PSD)
    @constraint(model, Z .>= 0)
    ## min Tr(W(I-Z))
    I = Matrix(1.0 * LinearAlgebra.I, m, m)
    @objective(model, Min, LinearAlgebra.tr(W * (I - Z)))
    ## Z e = e
    @constraint(model, Z * ones(m) .== ones(m))
    ## Tr(Z) = k
    @constraint(model, LinearAlgebra.tr(Z) == k)
    optimize!(model)
    Z_val = value.(Z)
    ## A simple rounding scheme
    which_cluster = zeros(Int, m)
    num_clusters = 0
    for i in 1:m
        Z_val[i, i] <= 1e-6 && continue
        if which_cluster[i] == 0
            num_clusters += 1
            which_cluster[i] = num_clusters
            for j in i + 1:m
                if LinearAlgebra.norm(Z_val[i, j] - Z_val[i, i]) <= 1e-6
                    which_cluster[j] = num_clusters
                end
            end
        end
    end
    Test.@test which_cluster == [1, 1, 2, 1, 2, 2]  #src
    if verbose
        ## Print results
        for cluster in 1:k
            println("Cluster $cluster")
            for i in 1:m
                if which_cluster[i] == cluster
                    println(a[i])
                end
            end
        end
    end
    return
end

example_cluster()


#
# # OnlinePCA: Thomas Breloff (Cointegrated Technologies)
#
#
#
# # solving for a dimension-reduced Y = XV', where X (n x d) is the original data, and Y (n x k) is projected
# # V (k x d) is a matrix where the columns are the first k eigenvectors of X'X/n (the covariance of X)
# # e (k x 1) is a vector of eigenvectors of the covariance matrix
#
# # We compute e and V by incrementally updating e and U, where U is a (k x d) matrix with the properties:
# #		Uᵢ = iᵗʰ column of U
# #		Vᵢ = iᵗʰ column of V
# #			 = iᵗʰ eigenvector of X-covariance
# #			 = Uᵢ / ‖Uᵢ‖
# #		eᵢ = iᵗʰ eigenvalue of X-covariance
# #		   = ‖Uᵢ‖
#
# # note: V is the normalized version of U
#
# type OnlinePCA{W<:Weighting} <: OnlineStat
#
# 	d::Int  # number of input vars
#   k::Int  # number of principal components
#   weighting::W
#   n::Int
#
# 	U::MatF  # (k x d)
# 	V::MatF  # (k x d)
# 	e::VecF	 # (k x 1)
#   xmeans::Means{W}
#  end
#
# function OnlinePCA(d::Integer, k::Integer, wgt::Weighting = default(Weighting))
# 	OnlinePCA(d, k, wgt, 0, zeros(k,d), zeros(k,d), zeros(k), Means(d, wgt))
# end
#
#
# function OnlinePCA(x::AVecF, k::Integer, wgt::Weighting = default(Weighting))
# 	o = OnlinePCA(length(x), k, wgt)
# 	update!(o, x)
# 	o
# end
#
# function OnlinePCA(X::AMatF, k::Integer, wgt::Weighting = default(Weighting))
# 	o = OnlinePCA(ncols(X), k, wgt)
# 	update!(o, X)
# 	o
# end
#
#
#
# #-----------------------------------------------------------------------# state
#
# statenames(o::OnlinePCA) = [:U, :V, :e, :xmeans, :nobs]
# state(o::OnlinePCA) = Any[copy(o.U), copy(o.V), copy(o.e), copy(mean(o.xmeans)), nobs(o)]
#
#
# #---------------------------------------------------------------------# update!
#
# # TODO: roughly based on Weng et al (2003): "Candid covariance-free incremental principal component analysis"
# # used https://github.com/kevinhughes27/pyIPCA/blob/master/pyIPCA/ccipca.py as a reference
#
#
# # TODO: optimize, potentially by using view(X, ...) instead of X[...]
#
# function update!(o::OnlinePCA, x::AVecF)
#
# 	x = center!(o.xmeans, x)
# 	λ = weight(o)
#
# 	@inbounds for i in 1:min(o.k, o.n)
#
# 		if o.e[i] == 0. # this should be more robust than checking i == o.n
#
# 			# initialize ith principal component
# 			Ui = x
# 			ei = norm(Ui)
# 			Vi = Ui / ei
#
# 		else
#
# 			# update the ith eigvec/eigval
# 			Ui = row(o.U, i)
# 			smooth!(Ui, x * (dot(x, Ui) / o.e[i]), λ)
# 			ei = norm(Ui)
# 			Vi = Ui / ei
#
# 			# subtract projection on ith PC
# 			x -= dot(x, Vi) * Vi
#
# 		end
#
# 		# store these updates
# 		row!(o.U, i, Ui)
# 		row!(o.V, i, Vi)
# 		o.e[i] = ei
# 	end
#
# 	o.n += 1
# 	nothing
# end
#
#
# # function update!(o::OnlinePCA, X::AMatF)
# # 	for i in 1:nrows(X)
# # 		update!(o, row(X,i))
# # 	end
# # end
#
#
#
# function Base.empty!(o::OnlinePCA)
# 	o.U = zeros(o.k, o.d)
# 	o.V = zeros(o.k, o.d)
# 	o.e = zeros(o.k)
# 	o.n = 0
# 	o.xmeans = Means(o.d, o.weighting)
# 	nothing
# end
#
# function Base.merge!(o1::OnlinePCA, o2::OnlinePCA)
# 	error("Merging undefined for PCA")
# end
#
#
# # returns a vector z = Vx
# StatsBase.predict(o::OnlinePCA, x::AVecF) = o.V * center(o.xmeans, x)
#
# function StatsBase.predict(o::OnlinePCA, X::AMatF)
# 	n = size(X,1)
# 	Z = zeros(n, o.k)
# 	for i in 1:n
# 		row!(Z, i, predict(o, row(X,i)))
# 	end
# 	Z
# end


# Code for a generic orthogonal polynomial, defined in terms of its recurrence coefficients

"""
A generic orthogonal polynomial sequence is determined by its recurrence
coefficients. The `GenericOPS` type stores the coefficients `A_n`, `B_n` and
`C_n` from the recurrence relation in the following form:
```
p_{n+1}(x) = (A_n x + B_n) * p_n(x) - C_n * p_{n-1}(x).
p_{-1} = 0, p_0 = p0
```
"""
struct GenericOPS{T} <: OPS{T}
    moment  ::  T
    p0      ::  T
    rec_a   ::  Vector{T}
    rec_b   ::  Vector{T}
    rec_c   ::  Vector{T}
    support
    weight

    function GenericOPS{T}(moment, rec_a, rec_b, rec_c, support, p0=one(T), weight=nothing) where {T}
        @assert length(rec_a) == length(rec_b) == length(rec_c)
        new(moment, p0, rec_a, rec_b, rec_c, support, weight)
    end
end

GenericOPS(moment::T, rec_a::Vector{A}, rec_b::Vector{B}, rec_c::Vector{C}, support, p0=one(T), weight=nothing) where {T,A,B,C} =
    GenericOPS{promote_type(T,A,B,C)}(moment, rec_a, rec_b, rec_c, support, p0, weight)

function MonicOPSfromQuadrature(n, my_quadrature_rule, other...; options...)
    α, β = adaptive_stieltjes(n,my_quadrature_rule; options...)
    MonicOPSfromMonicCoefficients(α, β, other...)
end

function OrthonormalOPSfromQuadrature(n, my_quadrature_rule, other...; options...)
    α, β = adaptive_stieltjes(n+1,my_quadrature_rule; options...)
    ONPSfromMonicCoefficients(α, β, other...)
end

MonicOPSfromMonicCoefficients(α::Vector{A}, β::Vector{B}, support::Domain{T}, other...) where {T,A,B} =
    GenericOPS{promote_type(T,A,B)}(β[1], ones(T,length(α)), -α, β, support, one(T), other...)

function ONPSfromMonicCoefficients(α::Vector{A}, β::Vector{B}, support::Domain{T}, other...) where {T,A,B}
    a,b,c = monic_to_orthonormal_recurrence_coefficients(α,β)
    GenericOPS{promote_type(T,A,B)}(β[1], a, b, c, support, 1/sqrt(β[1]), other...)
end

support(b::GenericOPS) = b.support
support(b::GenericOPS, idx) = support(b)

size(b::GenericOPS) = (length(b.rec_a),)

name(b::GenericOPS) = "Generic OPS"

weightfun(b::GenericOPS, x) = (b.weight==nothing) ? error("weight not defined for this Generic OPS") : b.weight(x)

function similar(b::GenericOPS, ::Type{T}, n::Int) where {T}
    @assert n <= length(b)
    GenericOPS{T}(b.moment, b.rec_a[1:n], b.rec_b[1:n], b.rec_c[1:n], b.support, b.p0, b.weight)
end

first_moment(b::GenericOPS) = b.moment

rec_An(b::GenericOPS, n::Int) = b.rec_a[n+1]

rec_Bn(b::GenericOPS, n::Int) = b.rec_b[n+1]

rec_Cn(b::GenericOPS, n::Int) = b.rec_c[n+1]

p0(b::GenericOPS) = b.p0


module CP_Exact

include("../Misc.jl")
using .Misc

using Distributions, SpecialFunctions, Einsum, Combinatorics
import Base.Iterators: product

export generate, log_marginal

function generate(I::Ƶ, J::Ƶ, K::Ƶ, R::Ƶ; μ::ℜ=3.5, γ::ℜ=0.1) where {ℜ<:Real, Ƶ<:Int}
    a::ℜ, b::ℜ = I*J*K*γ, γ/μ
    α_R, α_IR, α_JR, α_KR = fill(a/R,R), fill(a/(I*R),I,R), fill(a/(J*R),J,R), fill(a/(K*R),K,R)

    λ = rand(Gamma(a,1.0/b))
    θ_R = rand(Dirichlet(α_R))
    θ_IR = reshape([θ_ir for r=1:R for θ_ir=rand(Dirichlet(α_IR[:,r]))],I,R)
    θ_JR = reshape([θ_jr for r=1:R for θ_jr=rand(Dirichlet(α_JR[:,r]))],J,R)
    θ_KR = reshape([θ_kr for r=1:R for θ_kr=rand(Dirichlet(α_KR[:,r]))],K,R)

    θ_IJK = Array{ℜ}(undef,I,J,K)
    @einsum θ_IJK[i,j,k] = θ_R[r]*θ_IR[i,r]*θ_JR[j,r]*θ_KR[k,r]
    Λ_IJK = λ.*θ_IJK

    X::Array{ℜ} = map.(λ_ijk -> rand(Poisson(λ_ijk)), Λ_IJK)
    return X, λ, (θ_R, θ_IR, θ_JR, θ_KR)
end

function generate(I::Ƶ, J::Ƶ, K::Ƶ, R::Ƶ, T::Ƶ; μ::ℜ=3.5, γ::ℜ=0.1) where {ℜ<:Real, Ƶ<:Int}
    a::ℜ, b::ℜ = I*J*K*γ, γ/μ
    α_R, α_IR, α_JR, α_KR = fill(a/R,R), fill(a/(I*R),I,R), fill(a/(J*R),J,R), fill(a/(K*R),K,R)

    λ = rand(Gamma(a,1.0/b))
    θ_R = rand(Dirichlet(α_R))
    θ_IR = reshape([θ_ir for r=1:R for θ_ir=rand(Dirichlet(α_IR[:,r]))],I,R)
    θ_JR = reshape([θ_jr for r=1:R for θ_jr=rand(Dirichlet(α_JR[:,r]))],J,R)
    θ_KR = reshape([θ_kr for r=1:R for θ_kr=rand(Dirichlet(α_KR[:,r]))],K,R)

    θ_IJK = Array{ℜ}(undef,I,J,K)
    @einsum θ_IJK[i,j,k] = θ_R[r]*θ_IR[i,r]*θ_JR[j,r]*θ_KR[k,r]
    θ_flat = reshape(θ_IJK, I*J*K)

    X::Array{ℜ} = reshape(rand(Multinomial(T,θ_flat)),I,J,K)
    return X, (θ_R, θ_IR, θ_JR, θ_KR)
end

function log_marginal(X::Array{ℜ,3}, R::Ƶ; μ::ℜ=3.5, γ::ℜ=0.1) where {ℜ<:Real, Ƶ<:Int}
    I::Ƶ, J::Ƶ, K::Ƶ = size(X)
    X_flat = reshape(Int.(X),I*J*K)

    a::ℜ, b::ℜ = I*J*K*γ, γ/μ
    α_R, α_IR, α_JR, α_KR = fill(a/R,R), fill(a/(I*R),I,R), fill(a/(J*R),J,R), fill(a/(K*R),K,R)

    s, S_R, S_IR, S_JR, S_KR, S⁺ = zeros(ℜ,R), zeros(ℜ,R), zeros(ℜ,I,R), zeros(ℜ,J,R), zeros(ℜ,K,R), sum(X)
    log_PX, log_PS = -Inf, -Inf
    i::Ƶ, j::Ƶ , k::Ƶ = 0, 0, 0

    log_C = a*log(b) - (a + S⁺)*log(b + 1.0) #+ lgamma(a + S⁺) - lgamma(a)
    log_C += 2.0*sum(lgamma.(α_R)) - sum(lgamma.(α_IR)) - sum(lgamma.(α_JR)) - sum(lgamma.(α_KR))

    if R == 1
        S_R .= S⁺
        S_IR[:,1] .= sum(X,dims=(2,3))[:,1,1]
        S_JR[:,1] .= sum(X,dims=(1,3))[1,:,1]
        S_KR[:,1] .= sum(X,dims=(1,2))[1,1,:]
        log_PS = log_C
        log_PS += sum(lgamma.(α_IR .+ S_IR)) + sum(lgamma.(α_JR .+ S_JR)) + sum(lgamma.(α_KR .+ S_KR)) - 2.0*sum(lgamma.(α_R .+ S_R))
        log_PS -= sum(lgamma.(X .+ 1.0))
        return log_PS
    end

    for S_div = product(map(X_ijk -> combinations(1:(X_ijk+R-1),R-1), X_flat)...)
        S_R .= α_R
        S_IR .= α_IR
        S_JR .= α_JR
        S_KR .= α_KR
        log_PS = 0.0
        for (ijk, part) = enumerate(S_div)
            i, j, k = mod(ijk-1,I)+1, mod(div(ijk-1,I),J)+1, div(ijk-1,I*J)+1
            s[1] = part[1]-1
            for r=2:R-1
                s[r] = part[r] - part[r-1] - 1
            end
            s[R] = X[i,j,k] + R-1 - part[R-1]

            S_R .+= s
            S_IR[i,:] .+= s
            S_JR[j,:] .+= s
            S_KR[k,:] .+= s
            log_PS -= sum(lgamma.(s .+ 1.0))
        end
        log_PS += sum(lgamma.(S_IR)) + sum(lgamma.(S_JR)) + sum(lgamma.(S_KR)) - 2.0*sum(lgamma.(S_R)) + log_C
        log_PX = logsumexp([log_PX,log_PS])
    end

    return log_PX
end

end


using Measurements

# Figure 2A-D should be ordered:
#   ccw, lin, cw
# to be consistent with Figure 4.

# N-matrix, manually counted from bins:
# rows: cw, lin, ccw
# cols: in, out-50-bp, out-78-bp
N = [  63  54  48
      125 109  96
       61  55  48 ]

a = [ 0.22  -0.29  -0.545
      0.0   -0.31  -0.572
     -0.22  -0.35  -0.608 ]

σ = [ 0.05  0.03  0.07
      0.05  0.04  0.058
      0.05  0.03  0.059 ]

σ_mean = σ ./ sqrt.(N)

ā = a .± σ_mean

d(a_in, a_out) = (1 - a_in*a_in) / (1 - a_in*a_out) * a_out - a_in
d̃(a_in, a_out) = a_out - a_in

D = d.(ā[:, 1], ā[:, 2:3])
D̃ = d̃.(ā[:, 1], ā[:, 2:3])

Δ = D[2, :] .- (D[1, :] .+ D[3, :]) ./ 2
Δ̃ = D̃[2, :] .- (D̃[1, :] .+ D̃[3, :]) ./ 2


@testset "@map macro(ex)" begin
    n = 3
    A = rand(2, 2);
    b = rand(2)
    identitymap = @map x -> I(n)*x
    @test identitymap == MathematicalSystems.IdentityMap(n)
    linearmap = @map x -> A*x
    @test linearmap == MathematicalSystems.LinearMap(A)
    affinemap = @map x -> A*x + b
    @test affinemap == MathematicalSystems.AffineMap(A, b)
end


@testset "parse single statement" begin
    free = GTP.Free()
    sf = GTP.SemiFree(1)
    dep = GTP.Dependent(1)

    AB = Segment(:A, :B)
    CD = Segment(:C, :D)

    hp = @hp points(A, B)
    @test isempty(hp.constraints)
    @test hp.points == OrderedDict(:A => free, :B => free)

    hp = @hp Points(A, B, C)
    @test isempty(hp.constraints)
    @test hp.points == OrderedDict(:A => free, :B => free, :C => free)

    hp = @hp P := P ∈ Segment(A, B)
    @test hp.points == OrderedDict(:A => free, :B => free, :P => sf)
    @test hp.constraints == [Appartenence(:P, AB)]

    hp = @hp C := Segment(A, B) ⟂ Segment(C, D)
    @test hp.points == Dict(:A => free, :B => free, :C => sf, :D => free)
    @test hp.constraints == [Perpendicularity(AB, CD)]

    hp = @hp C := Segment(A, B) ∥ Segment(C, D)
    @test hp.points == Dict(:A => free, :B => free, :C => sf, :D => free)
    @test hp.constraints == [Parallelism(AB, CD)]

    hp = @hp C := Segment(A, B) ≅ Segment(C, D)
    @test hp.points == Dict(:A => free, :B => free, :C => sf, :D => free)
    @test hp.constraints == [Congruence(AB, CD)]

    hp = @hp P := Segment(A, B) ∩ Segment(C, D)
    @test hp.points == Dict(:A => free, :B => free, :C => free, :P => dep, :D => free)
    @test hp.constraints == [Intersection(:P, AB, CD)]

    hp = @hp H := A ↓ Segment(C, D)
    @test hp.points == Dict(:A => free, :D => free, :C => free, :H => dep)
    @test hp.constraints == [Projection(:H, :A, CD)]

    hp1 = @hp M := Midpoint(A, B)
    hp2 = @hp M := Midpoint(Segment(A, B))
    hp3 = @hp M := midpoint(A, B)
    hp4 = @hp M := Midpoint(Segment(A, B))
    for hp in (hp1, hp2, hp3, hp4)
        @test hp.points == Dict(:A => free, :B => free, :M => dep)
        @test hp.constraints == [Midpoint(:M, AB)]
    end

    hp1 = @hp D := Parallelogram(A, B, C, D)
    hp2 = @hp C := parallelogram(A, B, C, D)

    @test hp1.points == Dict(:A => free, :B => free, :C => free, :D => dep)
    @test hp2.points == Dict(:A => free, :B => free, :C => dep, :D => free)
    @test hp1.constraints == [Parallelogram(:A, :B, :C, :D)]
    @test hp2.constraints == [Parallelogram(:A, :B, :C, :D)]

    # THESIS
    th = @th Segment(A, B) ∥ Segment(C, D)
    @test th.constraints == [Parallelism(AB, CD)]

    th = @th P ∈ Segment(C, D)
    @test th.constraints == [Appartenence(:P, CD)]
    @test string(th) == "THESIS:\n------------\nP ∈ CD\n"
end

@testset begin "test printing"
    hp = @hp begin
        points(A, B, C)
        C := Segment(A, B) ⟂ Segment(A, C)
        M₁ := Midpoint(A, B)
        M₂ := Midpoint(A, C)
        M₃ := Midpoint(B, C)
        H := A ↓ Segment(B, C)
        O := Circle(M₁, M₂, M₃)
    end

    th = @th H ∈ circle(O, M₁)
    @test string(Theorem(hp, th)) == "POINTS:\n------------\nA : free\nB : free\nC : semifree by (1)\nM₁ : dependent by (2)\nM₂ : dependent by (3)\nM₃ : dependent by (4)\nH : dependent by (5)\nO : dependent by (6)\n\nHYPOTHESIS:\n------------\n(1) AB ⟂ AC\n(2) M₁ ∈ AB ∧ AM₁ ≅ M₁B\n(3) M₂ ∈ AC ∧ AM₂ ≅ M₂C\n(4) M₃ ∈ BC ∧ BM₃ ≅ M₃C\n(5) H ∈ BC ∧ AH ⟂ BC\n(6) OM₁ ≅ OM₂ ≅ OM₃\n\nTHESIS:\n------------\nOH ≅ OM₁\n\nPROOF:\n------\nProof missing\n"

end

@testset "parse multiple statements" begin
    free = GTP.Free()
    dep12 = GTP.Dependent(1, 2)
    dep3 = GTP.Dependent(3)
    hp = @hp begin
        points(A, B, C)
        B := Segment(A, B) ⟂ Segment(B, C)
        B := Segment(A, B) ≅ Segment(B, C)
        M := Midpoint(A, B)
    end

    AB = Segment(:A, :B)
    BC = Segment(:B, :C)

    @test hp.points == Dict(:A => free, :B => dep12, :C => free, :M => dep3)
    @test hp.constraints == [Perpendicularity(AB, BC), Congruence(AB, BC), Midpoint(:M, AB)]

    th = @th begin
        Segment(A, B) ⟂ Segment(B, C)
        Segment(A, B) ≅ Segment(B, C)
    end
    @test th.constraints == [Perpendicularity(AB, BC), Congruence(AB, BC)]
end

@testset "point status algebra" begin
    free = GTP.Free()
    sf1 = GTP.SemiFree(1)
    sf2 = GTP.SemiFree(2)
    dep = GTP.Dependent(3)

    for s in (free, sf1, dep)
        @test GTP._update_status(free, s) == s
        @test GTP._update_status(s, free) == s
    end

    @test GTP._update_status(sf1, sf2) == GTP.Dependent(1, 2)

    @test_throws ArgumentError GTP._update_status(sf1, dep)
    @test_throws ArgumentError GTP._update_status(dep, sf1)
    @test_throws ArgumentError GTP._update_status(dep, dep)
end


#------------------------------------------------------------------------------

function truncate_matrix(M::MatElem{<: Generic.AbsSeriesElem}, prec::Int)
    return map(e -> truncate(e, prec), M)
end

#------------------------------------------------------------------------------

function matrix_set_precision!(M::MatElem{<: Generic.AbsSeriesElem}, prec::Int)
    map(e -> set_precision!(e, prec), M)
end

#------------------------------------------------------------------------------

function ps_matrix_const_term(M::MatElem{<: Generic.AbsSeriesElem})
    return map(e -> coeff(e, 0), M)
end

#------------------------------------------------------------------------------

"""
    _matrix_inv_newton_iteration(M, Minv)

Performs a single step of Newton iteration for inverting `M` with `Minv` being a partial result
"""
function _matrix_inv_newton_iteration(M::MatElem{T}, Minv::MatElem{T}) where T <: Generic.AbsSeriesElem{<: Generic.FieldElem}
    return 2 * Minv - Minv * M * Minv
end

#--------------------------------------

"""
    ps_matrix_inv(M, prec)

Input:
- `M` - a square matrix with entries in a univariate power series ring
      it is assumed that M(0) is invertible and all entries having the same precision
- `prec` - an integer, precision, if `-1` then defaults to precision of `M`

Output:
- the inverse of `M` computed up to `prec`
"""
function ps_matrix_inv(M::MatElem{<: Generic.AbsSeriesElem{<: Generic.FieldElem}}, prec::Int=-1)
    const_term = ps_matrix_const_term(M)
    prec = (prec == -1) ? precision(M[1, 1]) : prec
    power_series_ring = base_ring(parent(M))
    result = map(a -> power_series_ring(a), Base.inv(const_term))
    cur_prec = 1
    while cur_prec < prec
        result = _matrix_inv_newton_iteration(M, result)
        cur_prec *= 2
    end
    return result
end

#------------------------------------------------------------------------------

"""
    ps_diff(ps)

Input:
- `ps` - (absolute capped) unvariate power series

Output:
- the derivative of `ps`
"""
function ps_diff(ps::Generic.AbsSeriesElem{<: Generic.RingElem})
    result = zero(parent(ps))
    set_precision!(result, precision(ps))
    for exp in 1:(precision(ps) - 1)
        setcoeff!(result, exp - 1, coeff(ps, exp) * exp)
    end
    return result
end

#------------------------------------------------------------------------------

"""
    ps_integrate(ps)

Input:
- `ps` - (absolute capped) unvariate power series
Output:
- the integral of `ps` without constant term
"""
function ps_integrate(ps::Generic.AbsSeriesElem{<: Generic.FieldElem})
    result = zero(parent(ps))
    set_precision!(result, precision(ps) + 1)
    for exp in 0:(precision(ps) - 1)
        setcoeff!(result, exp + 1, coeff(ps, exp) // (exp + 1))
    end
    return result
end

#------------------------------------------------------------------------------

"""
    ps_matrix_log(M)

Input:
- M - a square matrix with entries in a univariate power series ring
      it is assumed that `M(0)` is the identity
Output:
- the natural log of `M`
"""
function ps_matrix_log(M::MatElem{<: Generic.AbsSeriesElem{<: Generic.FieldElem}})
    const_term = ps_matrix_const_term(M)
    if const_term != one(parent(const_term))
       throw(Base.DomainError("Constant term must be the identity matrix"))
    end
    return map(
        e -> ps_integrate(e),
        map(e -> ps_diff(e), M - one(parent(M))) *
        ps_matrix_inv(M)
    )
end

#------------------------------------------------------------------------------

function _matrix_homlinear_de_newton_iteration(
        A::MatElem{<: Generic.AbsSeriesElem{T}},
        Y::MatElem{<: Generic.AbsSeriesElem{T}},
        Z::MatElem{<: Generic.AbsSeriesElem{T}},
        cur_prec::Int
    ) where T <: Generic.FieldElem
    Yprime = map(ps_diff, Y)
    Z = Z + truncate_matrix(Z * (one(parent(A)) - Y * Z), cur_prec)
    Y = Y - truncate_matrix(Y * map(ps_integrate, Z * (Yprime - truncate_matrix(A, 2 * cur_prec - 1) * Y)), 2 * cur_prec)
    return (Y, Z)
end

#--------------------------------------

"""
    ps_matrix_homlinear_de(A, Y0, prec)

Input:
- `A` - a square matrix with entries in a univariate power series ring
- `Y0` - a square invertible matrix over the base field
Output:
- matrix `Y` such that `Y' = AY` up to precision of `A - 1` and `Y(0) = Y0`
"""
function ps_matrix_homlinear_de(
        A::MatElem{<: Generic.AbsSeriesElem{T}},
        Y0::MatElem{<: T},
        prec::Int=-1
    ) where T <: Generic.FieldElem
    prec = (prec == -1) ? precision(A[1, 1]) : prec
    ps_ring = base_ring(parent(A))
    cur_prec = 1
    Y = (one(parent(A)) + gen(ps_ring) * truncate_matrix(A, cur_prec)) * map(e -> truncate(ps_ring(e), cur_prec), Y0)
    Z = map(e -> truncate(ps_ring(e), cur_prec), Base.inv(Y0))
    while cur_prec < prec
        matrix_set_precision!(Y, 2 * cur_prec)
        matrix_set_precision!(Z, cur_prec)
        Y, Z = _matrix_homlinear_de_newton_iteration(A, Y, Z, cur_prec)
        cur_prec *= 2
    end
    return Y, Z
end

#------------------------------------------------------------------------------

function _variation_of_constants(
        A::MatElem{<: Generic.AbsSeriesElem{T}},
        B::MatElem{<: Generic.AbsSeriesElem{T}},
        Yh::MatElem{<: Generic.AbsSeriesElem{T}},
        Zh::MatElem{<: Generic.AbsSeriesElem{T}},
        Y0::MatElem{<: T},
        prec::Int
    ) where T <: Generic.FieldElem
    Zh += truncate_matrix(Zh * (one(parent(A)) - Yh * Zh), prec)
    Y_particular = truncate_matrix(Yh * map(ps_integrate, Zh * B), prec)
    return Y_particular + Yh * map(e -> parent(A[1, 1])(e), Y0)
end

#--------------------------------------

"""
    ps_matrix_linear_de(A, B, Y0, prec)

Input:
- `A`, `B` - square matrices with entries in a univariate power series ring
- `Y0` - a matrix over the base field with the rows number the same as `A`

Output:
- matrix `Y` such that `Y' = AY + B` up to precision of `A - 1` and `Y(0) = Y0`
"""
function ps_matrix_linear_de(
        A::MatElem{<: Generic.AbsSeriesElem{T}},
        B::MatElem{<: Generic.AbsSeriesElem{T}},
        Y0::MatElem{<: T},
        prec::Int=-1
    ) where T <: Generic.FieldElem
    prec = (prec == -1) ? precision(A[1, 1]) : prec
    n = nrows(A)
    identity = one(AbstractAlgebra.MatrixSpace(base_ring(parent(Y0)), n, n))
    Yh, Zh = ps_matrix_homlinear_de(A, identity, prec)
    matrix_set_precision!(Zh, prec)
    return _variation_of_constants(A, B, Yh, Zh, Y0, prec)
end

#------------------------------------------------------------------------------

"""
    ps_ode_solution(equations, ic, inputs, prec)

Input:
- `equations` - a system of the form ``A(x, u, mu)x' - B(x, u, mu) = 0``,
                where A is a generically nonsingular square matrix. *Assumption*: `A` is nonzero at zero
- `ic` - initial conditions for x's (dictionary)
- `inputs` - power series for inputs represented as arrays (dictionary)
- `prec` - precision of the solution

Output:
- power series solution of the system
"""
function ps_ode_solution(
        equations::Array{P, 1},
        ic::Dict{P, T},
        inputs::Dict{P, Array{T, 1}},
        prec::Int
    ) where {T <: Generic.FieldElem, P <: MPolyElem{T}}
    n = length(equations)
    ring = parent(equations[1])
    S = AbstractAlgebra.MatrixSpace(ring, n, n)
    Sv = AbstractAlgebra.MatrixSpace(ring, n, 1)
    Svconst = AbstractAlgebra.MatrixSpace(base_ring(ring), n, 1)
    eqs = Sv(equations)

    x_vars = filter(v -> ("$(v)_dot" in map(string, gens(ring))), gens(ring))
    x_vars = [x for x in x_vars]
    x_dot_vars = [str_to_var(var_to_str(x) * "_dot", ring) for x in x_vars]

    Jac_dots = S([derivative(p, xd) for p in equations, xd in x_dot_vars])
    Jac_xs = S([derivative(p, x) for p in equations, x in x_vars])

    ps_ring, t = PowerSeriesRing(base_ring(ring), prec, "t"; model=:capped_absolute)
    solution = Dict()
    for (u, coeffs) in inputs
        solution[u] = sum(coeffs[i] * t^(i - 1) for i in 1:length(coeffs))
    end
    for x in x_vars
        solution[x] = ps_ring(ic[x])
    end
    for xd in x_dot_vars
        solution[xd] = ps_ring(0)
        set_precision!(solution[xd], 1)
    end

    cur_prec = 1
    while cur_prec < prec
        @debug "\t Computing power series solution, currently at precision $cur_prec"
        new_prec = min(prec, 2 * cur_prec)
        for i in 1:length(x_vars)
            set_precision!(solution[x_vars[i]], new_prec)
            set_precision!(solution[x_dot_vars[i]], new_prec)
        end
        eval_point = [solution[v] for v in gens(ring)]
        map(ps -> set_precision!(ps, 2 * cur_prec), eval_point)
        eqs_eval = map(p -> evaluate(p, eval_point), eqs)
        J_eval = map(p -> evaluate(p, eval_point), Jac_xs)
        Jd_eval = map(p -> evaluate(p, eval_point), Jac_dots)
        Jd_inv = ps_matrix_inv(Jd_eval, cur_prec)

        X_err = ps_matrix_linear_de(-Jd_inv * J_eval, -Jd_inv * eqs_eval, zero(Svconst))
        for i in 1:length(x_vars)
            solution[x_vars[i]] = solution[x_vars[i]] + X_err[i]
            solution[x_dot_vars[i]] = ps_diff(solution[x_vars[i]])
        end
        cur_prec = new_prec
    end

    return solution
end

#------------------------------------------------------------------------------

function ps_ode_solution(
        equations::Array{P, 1},
        ic::Dict{P, Int},
        inputs::Dict{P, Array{Int, 1}},
        prec::Int
    ) where P <: MPolyElem{<: Generic.FieldElem}
    bring = base_ring(parent(equations[1]))
    ps_ode_solution(
        equations,
        Dict(x => bring(v) for (x, v) in ic),
        Dict(u => map(v -> bring(v), vv) for (u, vv) in inputs),
        prec
    )
end


#-----------------------------------------------------------------------# LinReg
"""
    LinReg()

Linear regression, optionally with element-wise ridge regularization.

# Example

    x = randn(100, 5)
    y = x * (1:5) + randn(100)
    o = fit!(LinReg(), zip(eachrow(x),y))
    coef(o)
    coef(o, .1)
    coef(o, [0,0,0,0,Inf])
"""
mutable struct LinReg{W} <: OnlineStat{XY}
    β::Vector{Float64}
    A::Matrix{Float64}
    weight::W
    n::Int
end
LinReg(p=0;weight=EqualWeight()) = LinReg(zeros(p), zeros(1, 1), weight, 0)

function _fit!(o::LinReg, xy)
    γ = o.weight(o.n += 1)
    x, y = xy
    if o.n == 1
        p = length(x)
        o.β = zeros(p)
        o.A = zeros(p + 1, p + 1)
    end
    for j in 1:(size(o.A, 2)-1)
        xj = x[j]
        o.A[j, end] = smooth(o.A[j, end], xj * y, γ)    # x'y
        for i in 1:j
            o.A[i, j] = smooth(o.A[i,j], x[i] * xj, γ)  # x'x
        end
    end
    o.A[end] = smooth(o.A[end], y * y, γ)  # y'y
end
value(o::LinReg, args...) = coef(o, args...)
function coef(o::LinReg)
    o.β[:] = Symmetric(o.A[1:(end-1), 1:(end-1)]) \ o.A[1:(end-1), end]
end
function coef(o::LinReg, λ::Real)
    o.β[:] = Symmetric(o.A[1:(end-1), 1:(end-1)] + λ*I) \ o.A[1:(end-1), end]
end
function coef(o::LinReg, λ::AbstractVector{<:Real})
    o.β[:] = Symmetric(o.A[1:(end-1), 1:(end-1)] + Diagonal(λ)) \ o.A[1:(end-1), end]
end

predict(o::LinReg, x::AbstractVector) = x'o.β
predict(o::LinReg, x::AbstractMatrix) = x * o.β

function _merge!(o::LinReg, o2::LinReg)
    o.n += o2.n
    smooth!(o.A, o2.A, nobs(o2) / nobs(o))
end

#-----------------------------------------------------------------------# LinRegBuilder
"""
    LinRegBuilder(p)

Create an object from which any variable can be regressed on any other set of variables,
optionally with element-wise ridge regularization.  The main function to use with
`LinRegBuilder` is `coef`:

    coef(o::LinRegBuilder, λ = 0; y=1, x=[2,3,...], bias=true, verbose=false)

Return the coefficients of a regressing column `y` on columns `x` with ridge (`abs2` penalty)
parameter `λ`.  An intercept (`bias`) term is added by default.

# Examples

    x = randn(1000, 10)
    o = fit!(LinRegBuilder(), eachrow(x))

    coef(o; y=3, verbose=true)

    coef(o; y=7, x=[2,5,4])
"""
mutable struct LinRegBuilder{W} <: OnlineStat{VectorOb}
    A::Matrix{Float64}  #  x'x, pretend that x = [x, 1]
    weight::W
    n::Int
end
function LinRegBuilder(p=0; weight = EqualWeight())
    LinRegBuilder(Matrix{Float64}(undef, p + 1, p + 1), weight, 0)
end
function Base.show(io::IO, o::LinRegBuilder)
    print(io, "LinRegBuilder of $(nvars(o)) variables")
end
nvars(o::LinRegBuilder) = size(o.A, 1) - 1

function _fit!(o::LinRegBuilder, x)
    o.n += 1
    if o.n == 1
        o.A = zeros(length(x) + 1, length(x) + 1)
        o.A[end] = 1.0
    end
    smooth_syr!(o.A, BiasVec(x), o.weight(o.n))
end
value(o::LinRegBuilder, args...) = coef(o, args...)

xs(o::LinRegBuilder, y) = setdiff(1:size(o.A, 1) - 1, y)
function add_diag!(S, λ::Number)
    for i in 1:(size(S, 1) - 1)
        S[i, i] += λ
    end
end
function add_diag!(S, λ::AbstractVector)
    for i in 1:(size(S, 1) - 1)
        S[i, i] += λ[i]
    end
end

function coef(o::LinRegBuilder, λ=0.0; y=1, x=xs(o,y), bias=true, verbose=false)
    verbose && @info("Regress $y on $x $(bias ? "with bias" : "")")
    inds = collect(x)
    bias && push!(inds, size(o.A, 1))
    push!(inds, y)
    S = Symmetric(o.A)[inds, inds]
    add_diag!(S, λ)
    p = length(inds) - 1
    return S[1:p, 1:p] \ S[1:p, end]
end

function _merge!(o::LinRegBuilder, o2::LinRegBuilder)
    o.n += o2.n
    smooth!(o.A, o2.A, nobs(o2) / nobs(o))
end


export LogisticBinomial, HalfCauchy, HalfNormal
import Distributions.logpdf
# using SymPy

struct HalfCauchy{T} <: Distribution{Univariate,Continuous}
    σ::T

    function HalfCauchy{T}(σ::T) where T
        new{T}(σ)
    end
end

HalfCauchy(σ::T) where {T<:Real} = HalfCauchy{T}(σ)
HalfCauchy(σ::Integer) = HalfCauchy(Float64(σ))
HalfCauchy() = HalfCauchy(1.0)

Distributions.logpdf(d::HalfCauchy{T} ,x::Real) where {T}  = log(2.0) + logpdf(Cauchy(0.0,d.σ),x)

Distributions.pdf(d::HalfCauchy,x) = 2 * pdf(Cauchy(0,d.σ),x)

Distributions.rand(d::HalfCauchy) = abs(rand(Cauchy(0,d.σ)))

Distributions.quantile(d::HalfCauchy, p) = quantile(Cauchy(0, d.σ), (p+1)/2)

Distributions.support(::HalfCauchy{T} where T) = RealInterval(0.0, Inf)

struct HalfNormal
    σ
end

HalfNormal() = HalfNormal(1)

Distributions.logpdf(d::HalfNormal,x::Real) = log(2) + logpdf(Normal(0,d.σ),x)

Distributions.pdf(d::HalfNormal,x) = 2 * pdf(Normal(0,d.σ),x)

Distributions.rand(d::HalfNormal) = abs(rand(Normal(0,d.σ)))

Distributions.support(::HalfNormal) = RealInterval(0.0, Inf)

# HalfNormal(s) = Truncated(Normal(0,s),0,Inf)
# HalfNormal() = Truncated(Normal(0,1.0),0,Inf)

# Binomial distribution, parameterized by logit(p)
LogisticBinomial(n,x)=Binomial(n,logistic(x))


"""
  Stats.jl File

Classical statistical methods implementation (Module BetaML.Stars)

`?BetaML.Stats` for documentation

- [Importable source code (most up-to-date version)](https://github.com/sylvaticus/BetaML.jl/blob/master/src/Stats.jl) - [Julia Package](https://github.com/sylvaticus/BetaML.jl)
- New to Julia? [A concise Julia tutorial](https://github.com/sylvaticus/juliatutorial) - [Julia Quick Syntax Reference book](https://julia-book.com)

"""

"""
    BetaML.Stats module

Implement classical statistical methods. EXPERIMENTAL !

The module provide the following functions. Use `?[type or function]` to access their full signature and detailed documentation:

# Hyphothesis testing

- ``: Build a single Decision Tree

Acknowlegdments: most code is based on the MITx MOOC [Fundamentals of Statistics](https://www.edx.org/course/fundamentals-of-statistics)
"""
module Stats

using LinearAlgebra, Random, Distributions

using  ForceImport
@force using ..Api
@force using ..Utils

export welchSatterthwaite, huberLoss, check, mEstimationBruteForce, findQuantile, goodnessOfFitDiscrete, ksTest, computeDensity, computeKSTableValue

welchSatterthwaite(σx, σy,n,m) = Int(floor(((σx^2/n) + (σy^2/m))^2 / ( (σx^4/(n^2*(n-1)) + (σy^4/(m^2*(m-1)) ) ))))
huberLoss(x,δ=0.01) = abs(x) < δ ? x^2/2 : δ*(abs(x)-δ/2)
check(x,α) = x >=0 ? α * x : - (1- α) * x

"""
    mEstimationBruteForce(obs,candidates,lossFunction=abs)

"Solve" m-estimation in 1-D by "brute-force", i.e. by trying all the candidates provided to the function.

"""
function mEstimationBruteForce(obs,candidates,lossFunction=abs)
    score  = +Inf
    θstar = 0
    for c in candidates
      candidateScore = mean(lossFunction.(obs .- c))
      if candidateScore < score
        score = candidateScore
        θstar = c
      end
    end
    return θstar
  end

function findQuantile(obs,α;precision=0.001)
    score  = +Inf
    quantile = 0
    candidates = minimum(obs):precision:maximum(obs)
    for c in candidates
        candidateScore = mean(check.(obs .- c,α))
        if candidateScore < score
            score = candidateScore
            quantile = c
        end
    end
    return quantile
end

function goodnessOfFitDiscrete(data,p0=[1/length(data) for i in 1:length(data)];α=0.05)
    K = length(p0)
    N = sum(data)
    if length(data) != K
      @error "p0 and data must have the same number of categories!"
    end
    p̂ = data ./ N
    T = N * sum((p̂[k] - p0[k])^2/p0[k] for k in 1:K)
    χDist = Chisq(K-1)
    rejectedH₀ = T > quantile(χDist,1-α)
    p_value = 1 - cdf(χDist,T)
    return (testValue=T, threshold=quantile(χDist,1-α),rejectedH₀=rejectedH₀, p_value=p_value)
  end

function computeDensity(data,support)
    counts =  [count(i -> i==s,data) for s in support]
    if length(data) > sum(counts)
        shareDataNotInSupport = (length(data) -  sum(counts)) / length(data)
        if shareDataNotInSupport >= 0.0001
            @warn "$shareDataNotInSupport of the data is not in the support"
        end
    end
    return counts
end

"""
    goodnessOfFitDiscrete(data,support,f₀;compressedData=true,α=0.05,d=0)

Perform a goodness to fit chi-squared test to check for a particular MMF.

The passed distribution must support the method `pdf(dist,x)` for the provided support.
H₀ can be either the PDF with a specified set of parameters or the PDF in general. In this case the distribution object should be passed to this function with the MLE estimators that best fit the data (it is NOT done inside this function). In such case  the `d` parameter should be set to the number of estimated parameters in order to remove the `d` degree of freedom from the chi-square test.
"""
function goodnessOfFitDiscrete(data,support,f₀;compressedData=true,α=0.05,d=0)
    if !compressedData
        data   = computeDensity(data,support)
    end
    K          = length(support)
    N          = sum(data)
    p̂          = data ./ N
    df         = K - d - 1
    p0         = pdf.(f₀,support)
    T          = N * sum((p̂[k] - p0[k])^2/p0[k] for k in 1:K)
    χDist      = Chisq(df)
    rejectedH₀ = T > quantile(χDist,1-α)
    p_value    = 1 - cdf(χDist,T)
    return (testValue=T, threshold=quantile(χDist,1-α),rejectedH₀=rejectedH₀, p_value=p_value)
end

"""
   ksTest(data,f₀;α=0.05,asymptoticThreshold)

Perform the Kolmogorov-Smirnov goodness-of-fits table using the asymptotic Kolmogorov distribution (for N > 30, as it is faster) or the non-asymptotic KS Table for N < 30.

Note that as n → ∞, Distributions.quantile_bisect(distr,1-α) * sqrt(N) → quantile(distr,1-α)
For a three-digit precision use a asymptoticThreshold >= 1000, but slower!
"""
function ksTest(data,f₀,;α=0.05,asymptoticThreshold=100)
    data       = sort(data)
    N          = length(data)
    cdfhat     = collect(0:N) ./ N
    maxDist    = 0.0
    for (n,x) in enumerate(data)
        dist = max(abs(cdfhat[n]-cdf(f₀,x)), abs(cdfhat[n+1]-cdf(f₀,x)))
        if dist > maxDist
            maxDist = dist
        end
    end
    T          = sqrt(N) * maxDist
    distr      = N > asymptoticThreshold ? Kolmogorov() : KSDist(N)
    q          = N > asymptoticThreshold ? quantile(distr,1-α) : Distributions.quantile_bisect(distr,1-α) * sqrt(N)
    rejectedH₀ = T > q
    p_value    = 1 - cdf(distr,T)
    return (testValue=T, threshold=q,rejectedH₀=rejectedH₀, p_value=p_value)
end

"""
   computeKSTableValue(f₀,N,α,repetitions=1000)

Compute the values of the Kolmogorov-Smirnov table by numerical simulation

"""
function computeKSTableValue(f₀,N,α,repetitions=1000)
    Ts = Array{Float64,1}(undef,repetitions)
    for rep in 1:repetitions
        data       = sort(rand(f₀,N))
        N          = length(data)
        cdfhat     = collect(0:N) ./ N
        maxDist    = 0.0
        for (n,x) in enumerate(data)
            dist = max(abs(cdfhat[n]-cdf(f₀,x)), abs(cdfhat[n+1]-cdf(f₀,x)))
            if dist > maxDist
                maxDist = dist
            end
        end
        T          = sqrt(N) * maxDist
        Ts[rep]    = T
    end
    Ts = sort(Ts)
    return Ts[Int(ceil((1-α)*repetitions))]/sqrt(N)
end

end # end module


#=
Lightweight implementation of forward-mode second-degree autodiff.
=#
module Jets

import SpecialFunctions: erf, erfc, erfcinv, dawson
import Base: convert, promote_rule, +, -, *, /, inv, cos, sin, tan, cot, sec, csc, exp, cosh, sinh, tanh, coth, sech, csch, log, sqrt, one, zero, isnan, isinf, conj, real, imag

const erfinf=2/sqrt(pi)
const ierfinf=sqrt(pi)/2

#a+be+ce^2 with e^3=0
struct Jet{T<:Number} <: Number
  a::T
  b::T
  c::T
end

Standard=Union{Real,Complex{<:Real}}

Jet(x::T) where T<:Number = Jet{T}(x, zero(T), zero(T))

function Jet(x::T1,y::T2) where {T1<:Number,T2<:Number}
    T = promote_type(T1,T2)
    Jet{T}(promote(x,y)..., zero(T))
end

function Jet(x::T1,y::T2,z::T3) where {T1<:Number,T2<:Number,T3<:Number}
    T = promote_type(T1,T2,T3)
    Jet{T}(promote(x,y,z)...)
end

convert(::Type{Jet},x::Standard)=Jet(x)
convert(::Type{Jet{T}},x::Standard) where T<:Number = Jet(convert(T,x))
convert(T::Type{<:Standard},x::Jet)=convert(T,x.a)

promote_rule(::Type{Jet{T1}}, ::Type{T2}) where {T1<:Number,T2<:Standard} = Jet{promote_type(T1,T2)}
promote_rule(::Type{Jet{T1}}, ::Type{Jet{T2}}) where {T1<:Number,T2<:Number} = Jet{promote_type(T1,T2)}

function (+)(x::Jet,y::Jet)
  Jet(x.a+y.a,x.b+y.b,x.c+y.c)
end

function (-)(x::Jet,y::Jet)
  Jet(x.a-y.a,x.b-y.b,x.c-y.c)
end

function (-)(y::Jet)
  Jet(-y.a,-y.b,-y.c)
end

function (+)(x::Jet,y::Standard)
  Jet(x.a+y,x.b,x.c)
end

function (-)(x::Jet,y::Standard)
  Jet(x.a-y,x.b,x.c)
end

function (+)(x::Standard,y::Jet)
  Jet(x+y.a,y.b,y.c)
end

function (-)(x::Standard,y::Jet)
  Jet(x-y.a,-y.b,-y.c)
end

function (*)(x::Standard,y::Jet)
  Jet(x*y.a,x*y.b,x*y.c)
end

function (*)(y::Jet,x::Standard)
  Jet(y.a*x,y.b*x,y.c*x)
end

function (/)(x::Jet,y::Standard)
  x*inv(y)
end

function (*)(x::Jet,y::Jet)
  Jet(x.a*y.a,x.a*y.b+x.b*y.a,x.a*y.c+x.b*y.b+x.c*y.a)
end

#x=z/y only if x*y=z
function (/)(z::Jet,y::Jet)
  r=inv(y.a)
  xa=r*z.a
  xb=r*(z.b-xa*y.b)
  xc=r*(z.c-xa*y.c-xb*y.b)
  Jet(xa,xb,xc)
end

function inv(y::Jet)
  xa=inv(y.a)
  xb=-xa*xa*y.b
  xc=-xa*(xa*y.c + xb*y.b)
  Jet(xa,xb,xc)
end

function (/)(z::Standard,y::Jet)
  r=inv(y.a)
  xa=r*z
  xb=-r*xa*y.b
  xc=-r*(xa*y.c + xb*y.b)
  Jet(xa,xb,xc)
end

#f(a+e)=f(a)+ef'(a)+e^2f''(a)/2
function taylorcoeffs(func, pt::Standard)
  func(Jet(pt,one(pt)))
end

function taylorapprox(taylor::Jet,x::Standard)
  taylor.a+x*(taylor.b+x*taylor.c)
end

#computes f(x::Jet) from x and
#f,df,ddf=f(x.a),f'(x.a),f''(x.a)
function derivschain(x::Jet,
                     f::Standard,
                     df::Standard,
                     ddf::Standard)
  #f(a+be+ce^2)=f(a)+bef'(a)+e^2(cf'(a)+b^2f''(a)/2)
  Jet(f, x.b*df, x.c*df + x.b*x.b*ddf/2)
end

cos(x::Jet,si::Standard,co::Standard) = Jet(co, -x.b*si, -x.c*si - x.b*x.b*co/2)
sin(x::Jet,si::Standard,co::Standard) = Jet(si, x.b*co, x.c*co - x.b*x.b*si/2)
tan(x::Jet,si::Standard,co::Standard) = sin(x,si,co) / cos(x,si,co)
cot(x::Jet,si::Standard,co::Standard) = cos(x,si,co) / sin(x,si,co)
sec(x::Jet,si::Standard,co::Standard) = inv(cos(x,si,co))
csc(x::Jet,si::Standard,co::Standard) = inv(sin(x,si,co))

cos(x::Jet)=cos(x,sincos(x.a)...)
sin(x::Jet)=sin(x,sincos(x.a)...)
tan(x::Jet)=tan(x,sincos(x.a)...)
cot(x::Jet)=cot(x,sincos(x.a)...)
sec(x::Jet)=sec(x,sincos(x.a)...)
csc(x::Jet)=csc(x,sincos(x.a)...)

function exp(x::Jet)
  ex=exp(x.a)
  Jet(ex, ex*x.b, ex*(x.c + x.b*x.b/2))
end

function log(x::Jet)
    f=log(x.a)
    df=inv(x.a)
    ddf=-df^2
    derivschain(x,f,df,ddf)
end

function sqrt(x::Jet)
  f=sqrt(x.a)
  r=inv(2x.a)
  df=f*r
  ddf=-df*r
  derivschain(x,f,df,ddf)
end

function dawson(x::Jet)
  f=dawson(x.a)
  df=1-2*x.a*f
  ddf=2*(f*(2*x.a*x.a-1)-x.a)
  derivschain(x,f,df,ddf)
end

function erf(x::Jet)
  f=erf(x.a)
  df=erfinf*exp(-x.a*x.a)
  ddf=-2*x.a*df
  derivschain(x,f,df,ddf)
end

function erfc(x::Jet)
  f=erfc(x.a)
  df=-erfinf*exp(-x.a*x.a)
  ddf=-2*x.a*df
  derivschain(x,f,df,ddf)
end

function erfcinv(x::Jet)
  #erfcinv only defined on (0,2)
  f=erfcinv(real(x.a))
  df=-ierfinf*exp(f*f)
  ddf=2*f*df*df
  derivschain(x,f,df,ddf)
end

function cosh(x::Jet)
    p=exp(x)
    m=inv(p)
    (p+m)/2
end
function sinh(x::Jet)
    p=exp(x)
    m=inv(p)
    (p-m)/2
end
function tanh(x::Jet)
    p=exp(x)
    m=inv(p)
    (p-m)/(p+m)
end
function coth(x::Jet)
    p=exp(x)
    m=inv(p)
    (p+m)/(p-m)
end
function sech(x::Jet)
    p=exp(x)
    m=inv(p)
    2/(p+m)
end
function csch(x::Jet)
    p=exp(x)
    m=inv(p)
    2/(p-m)
end

function real(x::Jet)
  Jet(real(x.a),real(x.b),real(x.c))
end

function imag(x::Jet)
  Jet(imag(x.a),imag(x.b),imag(x.c))
end

function conj(x::Jet)
  Jet(conj(x.a),conj(x.b),conj(x.c))
end

one(::Jet{T}) where T<:Number = Jet(one(T))
zero(::Jet{T}) where T<:Number = Jet(zero(T))

isnan(x::Jet)=isnan(x.a) || isnan(x.b) || isnan(x.c)
isinf(x::Jet)=isinf(x.a) || isinf(x.b) || isinf(x.c)

#input: 2nd-order Taylor polynomial of function f at some point
#output: quadratic and linear coefficients of Taylor polynomial of arg(f) at that point
phasepoly(z::Jet)=complex(0.,phase_acceleration(z)),complex(0.,phase_velocity(z))

phase_velocity(z::Jet)=imag(z.b/z.a)
phase_acceleration(z::Jet)=imag((z.c*z.a-0.5*z.b*z.b)/(z.a*z.a))

end #module


using WiltonInts84
using StaticArrays
using Test

#include("num_quad.jl")

if !@isdefined record
    record = false
end

v1p = SVector(4.12, 1.74, 1.8);
v2p = SVector(2.04, 3.47, -3.5);
v3p = SVector(-4.21, 3.2, 0.08);
lvp = v2p-v1p; # an edge vector
observationPointp = v1p + 2.84*lvp;
I, J = wiltonints(v1p,v2p,v3p,observationPointp,Val{0})

using JLD2
fn = joinpath(dirname(@__FILE__),"issue1.jld2")

if record == true
    K, L = dblquadints1(v1p, v2p, v3p, observationPointp, Val{0})
    K = [k for k in K]
    L = [L[d][i] for i in 1:3, d in eachindex(L)]
    jldopen(fn, "w") do file
        write(file, "K", K)
        write(file, "L", L)
    end
else
    K, L = jldopen(fn,"r") do file
        read(file, "K"),
        read(file, "L")
    end
end

for d in eachindex(K)
    @eval @test nearlyequal(I[$d], K[$d], 10e-8)
    for i in 1:3
        @eval @test nearlyequal(J[$d][$i], L[$i,$d], 10e-8)
    end
end


# try include("sparse_eigen_test.jl") catch end
# try include("./test/sparse_eigen_test.jl") catch end

# test_native_eigen()

import LinearAlgebra
using MatrixCompletion.Utilities.FastEigen
using Test

function create_symmetric_matrix(n)
    a = rand(n,n) * 5
    return a+a'
end

function correct_output_sparseeigen(input,k)
    eigen_dcp = LinearAlgebra.eigen(input);
    eigen_val = eigen_dcp.values;
    eigen_vec = eigen_dcp.vectors;
    first_k_idx = Base.sortperm(eigen_val,rev=true)[1:k];
    return eigen_val[first_k_idx],eigen_vec[:,first_k_idx];
end

function project(v,e)
    return e * LinearAlgebra.Diagonal(v) * e';
end

function test_native_eigen(;n = 200, nev = 20)
  input = create_symmetric_matrix(n);
  @time λ, X = eigs(NativeEigen(), input ;nev = nev);
  λ₀, X₀ = correct_output_sparseeigen(input, nev);
  p = project(λ, X)
  p₀ = project(λ₀, X₀)
  @test LinearAlgebra.norm(p - p₀) < 0.01
end

function test_krylov(;n = 200, nev = 20)
  a = create_symmetric_matrix(n) * 5
  @info("doing krylov eigen")
  @time λ, X = eigs(KrylovMethods(), a, nev = nev)
  @info("doing full eigen")
  @time λ₀, X₀ = correct_output_sparseeigen(a, nev)
  p = project(λ, X)
  p₀ = project(λ₀, X₀)
  @test LinearAlgebra.norm(p - p₀)^2 / LinearAlgebra.norm(p₀)^2 < 0.01
end

function test_arpack(;n = 200, nev = 20)
  a = create_symmetric_matrix(n) * 5
  @info("doing Arpack eigen")
  # @time λ, X = eigs(ARPACK(), a, nev = nev)
  @info("doing full eigen")
  @time λ₀, X₀ = correct_output_sparseeigen(a, nev)
  p = project(λ, X)
  p₀ = project(λ₀, X₀)
  @test LinearAlgebra.norm(p - p₀)^2 / LinearAlgebra.norm(p₀)^2 < 0.01
end

test_arpack(n = 4000, nev = 1000)

function test_lobpcg(;dim= 2000,nev=20,repeat=5)
  input = create_symmetric_matrix(dim);
  for i = 1:repeat
    @time begin
      λ, X = eigs(NativeLOBPCG(), input; nev=nev)
      p1 = get_projection(λ, X)
    end
    @time begin
      v0, e0 =  correct_output_sparseeigen(input, 20)
      p2 = get_projection(v0, e0)
    end
    @test LinearAlgebra.norm(p1 - p2) < 0.01
  end
end

@testset "$(format("Sparse Eigen: KrylovKit Wrapper"))" begin
  let
    for i in 1:10
      test_krylov(n = 2000, nev = 20)
    end
  end
end

# @testset "$(format("Sparse Eigen: NativeEigen Wrapper"))" begin
#   let
#     for i in 1:10
#       test_native_eigen(n = 2000, nev = 20)
#     end
#   end
# end


export rand_eg, empty_eg, chain_eg, ring_eg, K_eg, disconnected_cliques_eg
export petersen_graph, c60_graph, get_petersen_graph, get_c60_graph
export inverse_eg, smallworld_eg, random_regular_eg

"""
    K_eg(nv::Int, [nw]) -> EliminateGraph

fully connected graph, if `nw` provided, compute `K(nv,nw)` graph.
"""
K_eg(nv::Int) = rand_eg(nv, 1.0)
K_eg(m::Int, n::Int) = EliminateGraph(m+n,vcat([vi=>vj for vi=1:m, vj=m+1:m+n]...))

"""
Inverse graph, arXiv: 1807.03739
"""
function inverse_eg(n::Int)
    EliminateGraph(n, vcat([x=>mod1(x+1, n) for x=1:n], [x=>mod(-mod_inverse(x-1, n), n)+1 for x=2:n]))
end

"""

"""
function smallworld_eg(n::Int; ρ::Real)
    longlinks = Pair{Int,Int}[]
    for x=1:n, y=1:n
        if abs(mod(x-y, n)) > 1 && rand() < ρ
            push!(longlinks, x=>y)
        end
    end
    EliminateGraph(n, vcat([x=>mod(x, n)+1 for x=1:n], longlinks))
end

function random_regular_eg(nv::Int, nreg::Int)
    eg = empty_eg(nv)
    g = random_regular_graph(nv, nreg)
    for i in 1:nv
        for j=g.fadjlist[i]
            unsafe_connect!(eg,i,j)
        end
    end
    return eg
end

"""
    mod_inverse(x::Int, N::Int) -> Int
Return `y` that `(x*y)%N == 1`, notice the `(x*y)%N` operations in Z* forms a group and this is the definition of inverse.
"""
function mod_inverse(x::Int, N::Int)
    for i=1:N
        (x*i)%N == 1 && return i
    end
    throw(ArgumentError("Can not find the inverse, $x is probably not in Z*($N)!"))
end

"""
A graph consists of disconnected cliques.
"""
function disconnected_cliques_eg(nvs::Int...)
    N = sum(nvs)
    tbl = zeros(Bool, N, N)
    offset = 0
    for nv in nvs
        for j=offset+1:offset+nv
            for i=offset+1:offset+nv
                i!=j && (@inbounds tbl[i,j] = true)
            end
        end
        offset += nv
    end
    return EliminateGraph(tbl)
end

"""
    empty_eg(nv::Int) -> EliminateGraph

fully disconnected graph
"""
empty_eg(nv::Int) = EliminateGraph(zeros(Bool,nv,nv))

"""
    rand_eg(nv::Int, density::Real) -> EliminateGraph

Generate a random `EliminateGraph`.
"""
function rand_eg(nv::Int, density::Real)
    tbl = rand(nv, nv) .< density
    copyltu!(tbl)
    for i=1:nv
        tbl[i,i] = false
    end
    EliminateGraph(tbl)
end

"""chain graph"""
chain_eg(nv::Int) = EliminateGraph(nv,[vi=>vi+1 for vi=1:nv-1])

"""ring graph"""
ring_eg(nv::Int) = EliminateGraph(nv,[vi=>mod1(vi+1,nv) for vi=1:nv])

# Special Graphs
get_petersen_graph() = EliminateGraph(10,
                    [1=>2, 2=>3, 3=>4, 4=>5, 5=>1,
                    1=>6, 2=>7, 3=>8, 4=>9, 5=>10,
                    6=>8, 7=>9, 8=>10, 9=>1, 10=>2])
const petersen_graph = get_petersen_graph()

get_c60_graph() = EliminateGraph(60, [1=>10, 1=>41, 1=>59, 2=>12, 2=>42, 2=>60, 3=>6, 3=>
        43, 3=>57, 4=>8, 4=>44, 4=>58, 5=>13, 5=>56, 5=>
        57, 6=>10, 6=>31, 7=>14, 7=>56, 7=>58, 8=>12, 8=>
        32, 9=>23, 9=>53, 9=>59, 10=>15, 11=>24, 11=>53, 11=>
        60, 12=>16, 13=>14, 13=>25, 14=>26, 15=>27, 15=>
        49, 16=>28, 16=>50, 17=>18, 17=>19, 17=>54, 18=>
        20, 18=>55, 19=>23, 19=>41, 20=>24, 20=>42, 21=>
        31, 21=>33, 21=>57, 22=>32, 22=>34, 22=>58, 23=>
        24, 25=>35, 25=>43, 26=>36, 26=>44, 27=>51, 27=>
        59, 28=>52, 28=>60, 29=>33, 29=>34, 29=>56, 30=>
        51, 30=>52, 30=>53, 31=>47, 32=>48, 33=>45, 34=>
        46, 35=>36, 35=>37, 36=>38, 37=>39, 37=>49, 38=>
        40, 38=>50, 39=>40, 39=>51, 40=>52, 41=>47, 42=>
        48, 43=>49, 44=>50, 45=>46, 45=>54, 46=>55, 47=>
        54, 48=>55])
const c60_graph = get_c60_graph()


using XDiff

ex = quote
    y = exp(W * x)
    sum((x - y).^2)
end
inputs = [:W => rand(5, 5), :x => rand(5)]
ctx = Dict()
@time dexs = rdiff(ex; ctx=ctx, inputs...)


function nchoosek(n, k; with_replacement=true)
    perms = []
    all_branches = collect(1:n)
    for i in 2:k
        next_branches = collect(1:n)
        if !with_replacement
            deleteat!(next_branches, i)
        end
        all_branches = add_branches(all_branches, next_branches)
    end
    return all_branches
end

function add_branches(roots, options)
    new_branches = []
    for r in roots
        for o in options
            push!(new_branches, [r o])
        end
    end
    return new_branches
end

function calc_permutations(n, k)
    perms = []
    current_branches = collect(1:n)
    if k==1; return current_branches; end

    all_branches = nothing
    for i in 2:k
        tmp = copy(current_branches)
        all_branches = nothing
        for b in tmp
            next_branches = collect(b[end]:n)
            b = add_branches([b], next_branches)
            if all_branches == nothing
                all_branches = b
            else
                all_branches = vcat(all_branches, b)
            end
        end
        current_branches = all_branches
    end
    return all_branches
end

export nchoosek, calc_permutations


using PtFEM

ProjDir = dirname(@__FILE__)
exname = split(@__FILE__)[end][1:end-3]

data = Dict(
  # Plane(ndim, nst, nxe, nye, nip, direction, finite_element(nod, nodof), axisymmetric)
  :struc_el => Plane(2, 3, 2, 2, 16, :x, Quadrilateral(4, 4), false),
  :properties => [10.92 0.3;],
  :x_coords => collect(range(0.0, stop=0.5, length=3)),
  :y_coords => collect(range(0.0, stop=0.5, length=3)),
  :thickness => 1.0,
  :support => [
    (1, [0 0 0 1]),
    (2, [0 0 1 1]),
    (3, [0 0 1 0]),
    (4, [0 1 0 1]),
    (6, [1 0 1 0]),
    (7, [0 1 0 0]),
    (8, [1 1 0 0]),
    (9, [1 0 0 0])
    ],
  :loaded_nodes => [
    (9, [0.25 0.0 0.0 0.0])
    ]
)

data |> display
println()

@time dis_df, sigma_df = p47(data)
println()

#PtFEM.vtk(data, dis_df, sigma_df, ProjDir, "Ex47.1")
#println()

dis_df |> display
println()
println("\nThe integration point (nip = $(data[:struc_el].nip)) stresses are:")
sigma_df |> display


using CMIM
using Base.Test

# Testing data
X = Bool[true true true false false false true; true false true false false false true; false false false true false false true; false true true false true false true; true false false true false false true; false true true true true false true; true false false false false false true; true true true true true false false; false true false true false false false; true true true true false false false; true false false false true false true; false false false false false true true; true true true false false true true; true true false true false true false; false false true true false true true; false true false false true false true; true false false false false true true]
y = Bool[true, true, true, false, true, false, true, true, false, false, false, true, true, true, false, true, true]

# Assign a score to all features
features = cmim(size(X, 2), X, y)

# Compare the results against R implementation of CMIM algorithm
# https://www.rdocumentation.org/packages/praznik/versions/5.0.0/topics/CMIM
@test first.(features) == [1, 3, 5, 4, 2, 6, 7]
@test ≈(last.(features), [0.0736965, 0.0426288, 0.041282, 0.0395759, 0.00908506, 0.00341897, 0.0], atol=0.00001)


using Test
using MortalityTables

const mt = MortalityTables

tables = mt.Tables()
cso2001 = tables["2001 CSO Super Preferred Select and Ultimate - Male Nonsmoker, ANB"]
vbt2001 = tables["2001 VBT Residual Standard Select and Ultimate - Male Nonsmoker, ANB"]
cso1980 = tables["1980 CSO Basic Table – Male, ANB"]

@test mt.qx(cso1980,35,1) == .00118
@test mt.qx(cso1980,35,61) == .27302
@test mt.qx(cso1980,95) == .27302
@test ismissing(mt.qx(cso1980,35,95))
@test ismissing(mt.qx(cso1980,101))

@test mt.qx(cso2001,35,1) == .00037
@test mt.qx(cso2001,35,61) == .26719
@test mt.qx(cso2001,16) == .00041
@test mt.qx(cso2001,95) == .26719
@test ismissing(mt.qx(cso2001,15))
@test ismissing(mt.qx(cso2001,150))
@test ismissing(mt.qx(cso2001,35,95))

@test mt.qx(vbt2001,35,1) == .00036
@test mt.qx(vbt2001,35,61) == .24298
@test mt.qx(vbt2001,95) == .24298
@test ismissing(mt.qx(vbt2001,35,95))
@test ismissing(mt.qx(vbt2001,150))

#this is to check trailing whitespace, as the source file has trailing whitespace in it
vbt2001su = tables["2001 VBT Select and Ultimate - Male Nonsmoker, ANB"]
vbt2001su == tables["2001 VBT Select and Ultimate - Male Nonsmoker, ANB"]


"""
Tools to compute partition functions.
"""

"""
    partition_function(
        g::Array{<: Real}, χ::Array{<: Unitful.Energy, 1}, temp::Unitful.Temperature
    )
    partition_function(data::AtomicStage, temp::Unitful.Temperature)

Calculate partition function from a set of levels defined by arrays
of statistical weights and energies (or an AtomicStage struct),
for a given temperature.
"""
function partition_function(
    g::Array{<: Real}, χ::Array{<: Unitful.Energy, 1}, temp::Unitful.Temperature,
)
    return sum(g .* exp.(-χ ./ (k_B * temp)))
end

function partition_function(atom::AtomicStage, temp::Unitful.Temperature)
    return partition_function(atom.g, atom.χ, temp)
end

"""
    partition_function_interpolator(
        atom::AtomicStage, temperatures::Array{<: Unitful.Temperature},
    )
    partition_function_interpolator(
        element::String, stage, temperatures::Array{<: Unitful.Temperature};
        source="NIST"
    )

Returns an interpolator for the partition function of a given atomic stage.

# Arguments
- `atom`: an `AtomicStage` structure with the data for the given stage
- `temperatures`: array with temperatures used to build the interpolation table

# Returns
- `interpolator`: linear interpolator object that takes temperature to give
  partition function
"""
function partition_function_interpolator(
    atom::AtomicStage, temperatures::Array{<: Unitful.Temperature},
)
    pfunc = partition_function.(Ref(atom), temperatures)
    return LinearInterpolation(temperatures, pfunc, extrapolation_bc=Line())
end

function partition_function_interpolator(
    element::String, stage, temperatures::Array{<: Unitful.Temperature};
    source="NIST"
)
    if source == "NIST"
        atom = read_NIST(element, stage)
    else
        error("NotImplemented: Atomic data source $source not supported")
    end
    return partition_function_interpolator(atom, temperatures)
end


@testset for llog in (Libm.log_tang,)
    for T in (Float32, Float64)
        @test isnan(llog(T(NaN)))
        @test llog(T(Inf)) == Inf
        @test llog(T(0)) == -Inf
        s = logspace(T(-10),T(10),1000)
        @test Base.log.(s) ≈ llog.(s)
        s = linspace(T(0),realmin(T),1000)
        @test Base.log.(s) ≈ llog.(s)
        @test_throws DomainError llog(T(-1))

        @test Base.log(prevfloat(T(Inf))) == llog(prevfloat(T(Inf)))
        @test Base.log(nextfloat(T(0))) == llog(nextfloat(T(0)))
    end
end

@testset for llog1p in (Libm.log1p_tang,)
    for T in (Float32, Float64)
        @test isnan(llog1p(T(NaN)))
        @test llog1p(T(Inf)) == Inf
        @test llog1p(T(0)) == 0
        @test llog1p(T(-1)) == -Inf
        s = logspace(T(-10),T(10),1000)
        @test Base.log1p.(s) ≈ llog1p.(s)
        s = -logspace(T(-10),T(0),1000)
        @test Base.log1p.(s) ≈ llog1p.(s)
        @test_throws DomainError llog1p(T(-2))
        @test Base.log1p(prevfloat(T(Inf))) == llog1p(prevfloat(T(Inf)))
        @test Base.log1p(nextfloat(T(-1))) == llog1p(nextfloat(T(-1)))
    end
end


# Rijke tube model
# Reference: Huhn and Magri 2020
using LinearAlgebra
#using DiffEqSensitivity, OrdinaryDiffEq, Zygote
include("cheb.jl")
# s = [beta, tau]
c1 = 0.1
c2 = 0.06
xf = 0.2
Ng = 10
tNg = 2*Ng + 1
Nc = 10
N = 2*Ng + Nc
j = LinRange(1,Ng,Ng)
jpi = @. pi.*j
cjpixf = @. cos.(xf.*jpi)
sjpixf = @. sin.(xf.*jpi)
zetaj = @. c1.*j.*j .+ c2.*
		sqrt.(j)
coeffs = [-1.0, 0.0, 1.75e3, 6.2e-12, -7.5e6]
tau = 0.2
dt = 5.e-1/Nc*tau
function qfun(t)
	if abs(t + 1.0) > 0.005
		return sqrt(abs(1.0 + t)) - 1.0
	end
	q = 0.
	for i = 1:5
		q += coeffs[i]*((1+t)^(i-1))
	end
	return q
end

D = cheb_diff_matrix(Nc)
function Rijke(u0::Array{Float64,1},
			   s::Array{Float64,1}=[7.0, 0.2],
			   n::Int64=1)
	uDot1 = zeros(N)
	uDot2 = zeros(N)
	uDot3 = zeros(N)
	beta, tau = s
	#ufMean = zeros(n)
	u = copy(u0)
	for i = 1:n
		#ufMean[i] = dot(u0[1:Ng],cjpixf)
		un = copy(u)
		f!(uDot1, u, s, i)
		@. u = u + (1/2)*dt*uDot1
		f!(uDot2, u, s, i)
		@. u = u - dt*uDot1 +
			2*dt*uDot2
		f!(uDot3, u, s, i)
		@. u = un + dt*((1/6).*uDot1 +
					 (2/3)*uDot2 + (1/6)*uDot3)
	end
	return u
end
function Rijke_ODE(u0::Array{Float64,1},
			   s::Array{Float64,1}=[7.0, 0.2],
			   n::Int64=1)
	t = n*dt
	prob = ODEProblem(f!, u0, (0.,t), s)
	sol = Array(solve(prob, Tsit5(),saveat=dt))
	return sol[:,end]
end
function f!(uDot, u, s, t)
	eta = view(u, 1:Ng)
	mu = view(u, Ng+1:2*Ng)
	v = view(u, tNg:N)
	beta, tau = s
	heat = beta*qfun(v[1])
	@. uDot[1:Ng] = jpi*mu
	@. uDot[Ng+1:2*Ng] = -jpi*eta - zetaj*mu - 2.0*heat*
						sjpixf
	uDot[tNg:N] .= -2.0/tau.*(D*[v;
					dot(eta, cjpixf)])[1:end-1]
end
function dfdbeta(u,s)
	n, d = size(u)
	beta, tau = s
	v1 = u[:,2*Ng + 1]
	dheat = qfun.(v1)
	return reshape([zeros(Ng*n);
					kron(-2.0.*ones(n).*dheat, sjpixf);
					zeros(Nc*n)], d, n)
end
function perturbation(u, s, eps=1.e-6)
	d, = size(u)
	beta, tau = s
	return (Rijke(u, [beta + eps, tau], 1) -
			Rijke(u, [beta - eps, tau], 1))/(2*eps)
end
function tau_perturbation(u, s, eps=1.e-6)
	d, = size(u)
	beta, tau = s
	return (Rijke(u, [beta, tau + eps], 1) -
			Rijke(u, [beta, tau - eps], 1))/(2*eps)
end

function dRijke(u, s, eps)
	d,  = size(u)
	dTu = zeros(d,d)
	u_p = zeros(d)
	u_m = zeros(d)
	v0 = zeros(d)
	for j = 1:d
		v0 .= zeros(d)
		v0[j] = 1.0
		u_p .= u .+ eps*v0
		u_m .= u .- eps*v0

		dTu[:,j] = (Rijke_ODE(u_p, s, 1) -
					Rijke_ODE(u_m, s, 1))/(2*eps)
	end
	return dTu
end
function dRijke_AD(u, s, eps=0.)
    du_ad = zeros(d,d)
    for j = 1:d
        v = zeros(d)
        du_ad[:,j] = Zygote.gradient(v->
                Rijke_ODE(u .+ v, s, 1)[j],v)[1]
    end
    return du_ad'
end


using DiffEqSensitivity, OrdinaryDiffEq, Zygote, Test
function lv!(du, u, p, t)
    x,y = u
    a, b, c, d = p
    du[1] = a*x - b*x*y
    du[2] = -c*y + d*x*y
end
function test(u0,p)
    tspan = [0.,1.]
    prob = ODEProblem(lv!, u0, tspan, p)
    sol = solve(prob,Tsit5())
    return sol.u[end][1]
end
function test2(u0,p)
    tspan = [0.,1.]
    prob = ODEProblem(lv!, u0, tspan, p)
    sol = solve(prob,Tsit5())
    return Array(sol)[1,end]
end

u0 = [1.,1.]
p = [1.,1.,1.,1.]
@test Zygote.gradient(test,u0,p) == Zygote.gradient(test2,u0,p)


@testset "Spherical tensors" begin
    @test system(TensorComponent(SphericalTensor(1), 1)) == OrbitalAngularMomentumSubSystem()
    @testset "Reduced matrix elements" begin
        for k = 0:6
            𝐂ᵏ = SphericalTensor(k)
            for (a,b) in [(0,k), (k,0), (1,k+1), (k+1,1)]
                @test !iszero(a, 𝐂ᵏ, b)
                # Since this is verbatim the definition of the reduced
                # matrix element, this is more a test of the tensor
                # DSL producing the correct code.
                @test rme(a, 𝐂ᵏ, b) ≈ AngularMomentumAlgebra.∏(b)*clebschgordan(b, 0, k, 0, a)
            end
        end
    end
    @testset "Components" begin
        𝐧 = SphericalTensor(1)
        for ℓ = 0:10
            for m = -ℓ:ℓ
                b = SpinOrbital(Orbital(:k, ℓ), m, half(1))
                for (±,∓) in [(+,-),(-,+)]
                    m′ = m ± 1
                    a = SpinOrbital(Orbital(:k, ℓ+1), m′, half(1))
                    @test dot(a, TensorComponent(𝐧, 0 ± 1), b) ≈ √((ℓ ± m + 1)*(ℓ ± m + 2)/
                                                                   (2*(2ℓ+1)*(2ℓ+3)))

                    m′ = m
                    a = SpinOrbital(Orbital(:k, ℓ+1), m′, half(1))
                    @test dot(a, TensorComponent(𝐧, 0), b) ≈ √((ℓ - m + 1)*(ℓ + m + 1)/
                                                               ((2ℓ+1)*(2ℓ+3)))

                    m′ = m ± 1
                    if abs(m′) ≤ ℓ-1
                        a = SpinOrbital(Orbital(:k, ℓ-1), m′, half(1))
                        @test dot(a, TensorComponent(𝐧, 0 ± 1), b) ≈ -√((ℓ ∓ m - 1)*(ℓ ∓ m)/
                                                                        (2*(2ℓ+1)*(2ℓ-1)))
                    end

                    m′ = m
                    if abs(m′) ≤ ℓ-1
                        a = SpinOrbital(Orbital(:k, ℓ-1), m′, half(1))
                        @test dot(a, TensorComponent(𝐧, 0), b) ≈ √((ℓ - m)*(ℓ + m)/
                                                                   ((2ℓ+1)*(2ℓ-1)))
                    end
                end
            end
        end
    end
end


using Lehmann
using FastGaussQuadrature, Printf

function kernelMatrix(ktype, x, ω, β) # both x and \omege are vectors
    if ktype == 0
        ker = zeros(Float64, (length(x), length(ω)))
    else
        ker = zeros(ComplexF64, (length(x), length(ω)))
    end
    for (ei, e) in enumerate(x)
        for (wi, w) in enumerate(ω)
            ker[ei, wi] = kernel(ktype, e, w, β)
        end
    end
    return ker
end

function kernel(ktype, x, ω, β)
    if ktype == 0
        ker = Spectral.kernelT(type, x, ω, β)
    elseif ktype == 1
        ker = Spectral.kernelΩ(type, x, ω, β)
    else
        ωn = (2x + 1) * π / β
        ker = 1im / (1im * ωn - ω)^2
    end
    return ker
end

function SemiCircle(type, Grid, β, Euv, ktype)
    # calculate Green's function defined by the spectral density
    # S(ω) = sqrt(1 - (ω / Euv)^2) / Euv # semicircle -1<ω<1

    ##### Panels endpoints for composite quadrature rule ###
    npo = Int(ceil(log(β * Euv) / log(2.0)))
    pbp = zeros(Float64, 2npo + 1)
    pbp[npo + 1] = 0.0
    for i in 1:npo
        pbp[npo + i + 1] = 1.0 / 2^(npo - i)
    end
    pbp[1:npo] = -pbp[2npo + 1:-1:npo + 2]

    function Green(n, ktype)
        # n: polynomial order
        xl, wl = gausslegendre(n)
        xj, wj = gaussjacobi(n, 1 / 2, 0.0)
        if ktype == 0
            G = zeros(Float64, length(Grid))
        elseif ktype == 1 || ktype == 2
            G = zeros(ComplexF64, length(Grid))
        end
        err = zeros(Float64, length(Grid))
        for (τi, τ) in enumerate(Grid)
            for ii in 2:2npo - 1
                a, b = pbp[ii], pbp[ii + 1]
                for jj in 1:n
                    x = (a + b) / 2 + (b - a) / 2 * xl[jj]
                    if type == :corr && x < 0.0
                        # spectral density is defined for positivie frequency only for correlation functions
                        continue
                    end
                    ker = kernel(ktype, τ, x * Euv, β)
                    G[τi] += (b - a) / 2 * wl[jj] * ker * sqrt(1 - x^2)
                end
            end

            a, b = 1.0 / 2, 1.0
            for jj in 1:n
                x = (a + b) / 2 + (b - a) / 2 * xj[jj]
                ker = kernel(ktype, τ, x * Euv, β)
                # ker = IsMatFreq ? Spectral.kernelΩ(type, τ, Euv * x, β) : Spectral.kernelT(type, τ, Euv * x, β)
                G[τi] += ((b - a) / 2)^1.5 * wj[jj] * ker * sqrt(1 + x)
            end

            if type != :corr
                # spectral density is defined for positivie frequency only for correlation functions
                a, b = -1.0, -1.0 / 2
                for jj in 1:n
                    x = (a + b) / 2 + (b - a) / 2 * (-xj[n - jj + 1])
                    ker = kernel(ktype, τ, x * Euv, β)
                    # ker = IsMatFreq ? Spectral.kernelΩ(type, τ, Euv * x, β) : Spectral.kernelT(type, τ, Euv * x, β)
                    G[τi] += ((b - a) / 2)^1.5 * wj[n - jj + 1] * ker * sqrt(1 - x)
                end
            end
        end
        return G
    end

    g1 = Green(24, ktype)
    g2 = Green(48, ktype)
    err = abs.(g1 - g2)

    println("Semi-circle case integration error = ", maximum(err))
    return g2, err
end

function dlr2matfreqD(dlrcoeff, dlrGrid::DLR.DLRGrid, nGrid; axis=1)
    @assert length(size(dlrcoeff)) >= axis "dimension of the dlr coefficients should be larger than axis!"
    ωGrid = dlrGrid.ω

    kernel = kernelMatrix(2, nGrid, ωGrid, dlrGrid.β)

    coeff, partialsize = DLR._tensor2matrix(dlrcoeff, axis)

    G = kernel * coeff # tensor dot product: \sum_i kernel[..., i]*coeff[i, ...]

    return DLR._matrix2tensor(G, partialsize, axis)
end

rtol(x, y) = maximum(abs.(x - y)) / maximum(abs.(x))

β = 10.0
Euv = 1.0
eps = 1.0e-8
type = :fermi

dlr = DLR.DLRGrid(type, 1Euv, β, eps) # construct dlr basis

Gt0 = SemiCircle(type, dlr.τ, β, Euv, 0)[1] # Gτ in dlr τ grid

coeff = DLR.tau2dlr(type, Gt0, dlr)

Gtfitted = DLR.dlr2tau(type, coeff, dlr, dlr.τ)

println("τ fit error: ", rtol(Gt0, Gtfitted))

ngrid = collect(-10:10)
Gwn = SemiCircle(type, ngrid, β, Euv, 1)[1]  # G(wn) in user-defined wn grid

Gwnfitted = DLR.dlr2matfreq(type, coeff, dlr, ngrid)
println("ωn fit error: ", rtol(Gwn, Gwnfitted))

GwnD = SemiCircle(type, ngrid, β, Euv, 2)[1]

GwnDfitted = dlr2matfreqD(coeff, dlr, ngrid)
println("derivative in ωn fit error: ", rtol(GwnD, GwnDfitted))

for (wi, w) in enumerate(ngrid)
    @printf("%3i   %10.6f + %10.6fim   %10.6f + %10.6fim   %12.6f\n", w, real(GwnD[wi]), imag(GwnD[wi]), real(GwnDfitted[wi]), imag(GwnDfitted[wi]), abs(GwnDfitted[wi] - GwnD[wi]))
end


include("../src/gmm.jl")

# 2D Gaussian mixed model on random data with heatmap.

using Plots

K = 3 # cluster numbers
num_samples = 50
ps, gmm_model, gm_model = create_gmm(K, 2) # prepare model

# prepare data
tmp=ones(2, num_samples)
tmp1=ones(2, num_samples)
tmp1[1, :] .= 0
X = hcat(randn(2, num_samples)/4+tmp1*1.6, randn(2, num_samples)/2, randn(2, num_samples)/4+tmp*1.6)

# learn model params
EM!(ps, X, K, gmm_model, gm_model, 100)

# plot results
x = y = LinRange(minimum(X), maximum(X), 100)
z = Float64[gmm_model(ps, [yi, xi]) for xi = x, yi = y]
p = heatmap(x, y, z, colorbar_title="Likelihood", title = "Gaussian mixture model heatmap")
scatter!(p, X[1, :], X[2, :], label="Data", xlabel="x1", ylabel="x2")


######### CmdStan program example  ###########

using CmdStan, Test, Statistics

ProjDir = dirname(@__FILE__)
cd(ProjDir) do

  bernoullimodel = "
  data {
    int<lower=1> N;
    int<lower=0,upper=1> y[N];
  }
  parameters {
    real<lower=0,upper=1> theta;
  }
  model {
    theta ~ beta(1,1);
    y ~ bernoulli(theta);
  }
  "

  bernoullidata = Dict("N" => 10, "y" => [0, 1, 0, 1, 0, 0, 0, 0, 0, 1])
  inittheta = Dict("theta" => 0.60)

  global stanmodel, sdf
  stanmodel = Stanmodel(name="bernoulli", model=bernoullimodel,
    random=CmdStan.Random(seed=-1),
    num_warmup=1000, printsummary=false);

  global rc, sim
  rc, a3d, cnames = stan(stanmodel, bernoullidata, ProjDir,
    init=inittheta, CmdStanDir=CMDSTAN_HOME)

  if rc == 0
    sdf  = read_summary(stanmodel)
    @test sdf[sdf.parameters .== :theta, :mean][1] ≈ 0.34 rtol=0.1
  end

end # cd


function energy_level(mol_idx, v, J)
    E = 0.0
    for k = 0:4
        tmpE1 = pow(v+0.5, k)
        for l =0:4
            tmpE2 = tmpE1 * Y[k,l] * pow(J*(J+1),l)
            E = E + tmpE2
        end
    end
    return E; #in cm^{-1}
end


using Test
using TestSetExtensions
using LinearAlgebra
using Qaintessent
using Qaintmodels

function isunitary(cb::Vector{CircuitGate})
    sparse_matrix(cb) * sparse_matrix(Base.adjoint(cb)) ≈ I
end

@testset ExtendedTestSet "quantum Fourier transform" begin
    for N in 1:4
        c = qft_circuit(N)

        Fmat = [exp(2*π*im*j*k/2^N)/sqrt(2^N) for k in 0:(2^N-1), j in 0:(2^N-1)]

        @test Qaintessent.sparse_matrix(c) ≈ Fmat
        @test isunitary(c)
    end
end

@testset ExtendedTestSet "toffoli circuit test" begin
    N = 3
    cgc = toffoli_circuit(1, (3, 2))

    # reference
    toffoli = circuit_gate(1, X, 3, 2)

    @test Qaintessent.sparse_matrix(toffoli) ≈ Qaintessent.sparse_matrix(cgc)
end

@testset ExtendedTestSet "vbe adder test" begin
    for N in 1:4
        M = 3N + 1
        adder = vbe_adder_circuit(N)
        ψ = fill(0.0 + 0.0im, 2^M)
        a = rand(0:2^(N-1))
        b = rand(0:2^(N-1))

        index = b << N + a
        ψ[index+1] = 1.0
        ψ = apply(ψ, adder)
        answer = ((findall(x -> x == 1, ψ)[1] - 1) % (2^2N)) >> N
        @test answer == (a + b) % (2^N)
    end
end

@testset ExtendedTestSet "qcla out of place adder test" begin
    for N in 1:4
        anc = N - count_ones(N) - floor(Int, log(N))
        M = 3N + anc + 1

        cgc = qcla_out_adder_circuit(N)
        ψ = fill(0.0 + 0.0 * im, 2^M)
        a = rand(0:2^(N-1))
        b = rand(0:2^(N-1))

        index = b << N + a

        ψ[index+1] = 1.0

        ψ = apply(ψ, cgc)
        answer = (findall(x -> x == 1, ψ)[1] - 1) >> 2N
        @test answer == a + b
    end
end

@testset ExtendedTestSet "qcla in place adder test" begin
    for N in 1:4
        n = 1
        anc = 0
        while 2^n < N
            anc += N ÷ 2^n -1
            n += 1
        end
        M = 3N + anc
        cgc = qcla_inplace_adder_circuit(N)
        a = rand(0:2^(N-1))
        b = rand(0:2^(N-1))

        index = b << N + a
        ψ = fill(0.0 + 0.0 * im, 2^M)

        ψ[index+1] = 1.0

        ψ = apply(ψ, cgc)
        answer = ((findall(x -> x == 1, ψ)[1] - 1) >> N)
        @test answer == a + b
    end
end


# Physical parameter monitoring.

"""
    check_plasma_characteristics(n, v, T, B)

Display characteristic plasma parameters given density `n`, bulk velocity `v`, temperature
`T`, and magnetic field strength `B` in SI units.
"""
function check_plasma_characteristics(n, v, T, B)
   γ = 5/3    # adiabatic index

   p = n*kB*T # [Pa]

   # Characteristic parameters
   di  = √(mᵢ*ϵ₀/n)*c/qᵢ           # ion inertial length, [m]
   fci = qᵢ * B / (mᵢ * 2π)        # [/s]
   vA  = B / √(μ₀ * n * mᵢ)        # Alfvén speed, [m/s]
   vS  = √(γ * p / (n * mᵢ))       # sonic speed, [m/s]
   vT  = √(kB * T / mᵢ)            # thermal speed, [m/s]
   rᵢ  = mᵢ * vT / (qᵢ * B)        # gyroradius, [m]

   println("--------------------------------------------------")
   println("* Characteristic plasma properties")
   println("Density             : ", rpad(round(n/1e6; digits=2), 9), "amu/cc")
   println("Velocity            : ", rpad(round(v/1e3; digits=2), 9), "km/s")
   println("Pressure            : ", rpad(round(p*1e9; digits=3), 9), "nPa")
   println("Temperature         : ", rpad(round(T; digits=3), 9), "K")
   println("Magnetic field      : ", rpad(round(B*1e9; digits=2), 9), "nT")
   println("Ion inertial length : ", rpad(round(di/1e3; digits=2),9), "km")
   println("Ion gyroradius      : ", rpad(round(rᵢ/1e3; digits=2),9), "km")
   println("Gyrofrequency       : ", rpad(round(fci; digits=2),   9), "Hz")
   println("Alfvén speed        : ", rpad(round(vA/1e3; digits=2),9), "km/s")
   println("Sonic speed         : ", rpad(round(vS/1e3; digits=2),9), "km/s")
   println("--------------------------------------------------")
end


using LinearAlgebra

function solve()
    n = parse(Int, readline())
    a = [parse(Int, x) for x in split(readline())]

    sort!(a, rev=true)

    c = zeros(Int, n)
    for i=1:n
        c[i] = n + 1 - 2i
    end

    a⋅c
end

println(solve())


struct SimpleCurve
    start::Tuple{Number, Number}
    stop::Tuple{Number, Number}
    control::Tuple{Number, Number}

    SimpleCurve(start::Tuple{Number, Number}, stop::Tuple{Number, Number}, control::Tuple{Number, Number}=(start .+ stop) ./ 2) = new(start, stop, control)
end

function getPoint(curve::SimpleCurve, percent::Number)
    return (1 - percent)^2 .* curve.start .+ 2 .* (1 - percent) * percent .* curve.control .+ percent^2 .* curve.stop
end


@inline function skycolor(ray::Ray{T}) where T
	white = SA[1.0, 1.0, 1.0]
	skyblue = SA[0.5, 0.7, 1.0]
	t = T(0.5)*(ray.dir.y + one(T))
    (one(T)-t)*white + t*skyblue
end

@inline color_vec3_in_rgb(v::Vec3{T}) where T = 0.5normalize(v) + SA{T}[0.5,0.5,0.5]

"""Compute color for a ray, recursively

	Args:
		depth: how many more levels of recursive ray bounces can we still compute?"""
@inline @fastmath function ray_color(r::Ray{T}, world::HittableList, depth=16) where T
    if depth <= 0
		return SA{T}[0,0,0]
	end

	rec::Union{HitRecord,Nothing} = hit(world, r, T(1e-4), typemax(T))
    if rec !== nothing
		# For debugging, represent vectors as RGB:
		# claforte TODO: adapt to latest code!
		# return color_vec3_in_rgb(rec.p) # show the normalized hit point
		# return color_vec3_in_rgb(rec.n⃗) # show the normal in RGB
		# return color_vec3_in_rgb(rec.p + rec.n⃗)
		# return color_vec3_in_rgb(random_vec3_in_sphere())
		#return color_vec3_in_rgb(rec.n⃗ + random_vec3_in_sphere())

        s = scatter(rec.mat, r, rec)
		if s.reflected
			return s.attenuation .* ray_color(s.r, world, depth-1)
		else
			return SA{T}[0,0,0]
		end
    else
        skycolor(r)
    end
end


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

		B_Switch Code

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

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

    Now we are going to construct a matrix summarizing the evolution of V_z

    This comes from the following discretized Bellman equation:

    ρv_ij = u(c_ij) + v_k(zF(k_{i}) -δk-c) + v_z⋅μ(z)
                                   + 1/2(v_{zz})σ^2(z)

                                   or

    ρv_ij = u(c_ij) + v_k(zF(k_{i}) -δk-c) + ((v_{i,j+1}-v_{i,j})/Δz)μ(z)
                            + 1/2((v_{i,j+1}-2v_{i,j}+v_{ij-1})/Δz^2)σ^2(z)

    Assume forward difference because of boundary conditions

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

function B_switch(μ, Σ_sq, dz, H)
	 dz_sq = dz^2
	 yy = (-Σ_sq/dz_sq .- μ/dz)
	 χ = Σ_sq/(2*dz_sq)
	 ζ = μ/dz .+ Σ_sq/(2*dz_sq)

	 # Define the Diagonals of this matrix
	 updiag_z = zeros(H,1)
	 	for j = 1:J
			updiag_z =[updiag_z; repeat([ζ[j]], H, 1)]
		end
	 updiag_z =(updiag_z[:])

	 centerdiag_z=repeat([χ[1]+yy[1]],H,1)
		for j = 2:J-1
			centerdiag_z = [centerdiag_z; repeat([yy[j]], H, 1)]
		end
	 centerdiag_z=[centerdiag_z; repeat([yy[J]+ζ[J]], H, 1)]
	  centerdiag_z = centerdiag_z[:]

	lowdiag_z = repeat([χ[2]], H, 1)
		for j=3:J
			lowdiag_z = [lowdiag_z; repeat([χ[j]],H,1)]
		end
	lowdiag_z=lowdiag_z[:]

	# spdiags in Matlab allows for automatic trimming/adding of zeros
	    # spdiagm does not do this

	B_switch = sparse(Diagonal(centerdiag_z))+ [zeros(H,H*J);  sparse(Diagonal(lowdiag_z)) zeros(H*(J-1), H)]+ sparse(Diagonal(updiag_z))[(H+1):end,1:(H*J)] # trim off rows of zeros

return B_switch

end


#=
Module with functions for matrices in finite fields.

Author: Ivan A. Moreno Soto
Last updated: 2019/July/15
=#

module FiniteFieldMatrix

using Random
using Nemo
using LinearAlgebra

include("./ExtensionField.jl")
using .ExtensionField

export
    multiplyPolyMatrices!,
    gaussianEliminationColumnPivoting!,
    GeneratorInverse!,
    getPermutation,
    getScrambler

"""
    multiplyPolyMatrices!(A, B, g)

Multiplies two matrices with coefficients in GF(2^m).
"""
function multiplyPolyMatrices!(A, B, g)
    C = zeros(UInt32, (size(A)[1], size(B)[2]))

    for i in 1:size(A)[1]
        for j in 1:size(A)[2]
            for k in 1:size(B)[2]
                temp = multiply(A[i, j], B[j, k])
                C[i, k] = add(C[i, k], temp)
            end
        end
    end

    # Applying modulo to every entry.
    C = modulo.(C, g)

    return C
end

"""
    searchPivot!(H, i, n)

Searches for the ith pivot in H in the n-i columns left.
"""
function searchPivot!(H, i, n)
    pivot = i
    for j in (i+1):n
        if H[i, j] == 1
            pivot = j
            break
        end
    end

    return pivot
end

"""
    permuteColumns!(H, i, pivot, k)

Permutes columns i and pivot with k entries each one in H.
"""
function permuteColumns!(H, i, pivot, k)
    for j in 1:k
        temp = H[j, i]
        H[j, i] = H[j, pivot]
        H[j, pivot] = temp
    end
end

"""
    pivotColumns!(H, i, n)

Pivots column i of matrix H. The function can search up to n-i columns
right.
"""
function pivotColumns!(H, i, n)
    k = size(H)[1]
    pivot = searchPivot!(H, i, n)

    if i == pivot
        error("Pivoting failed.")
    end

    permuteColumns!(H, i, pivot, k)

    return pivot
end

"""
    swapCodeSupport!(codeSupport, i, j)

Swaps two elements of a code support.
"""
function swapCodeSupport!(codeSupport, i, j)
    t = codeSupport[i]
    codeSupport[i] = codeSupport[j]
    codeSupport[j] = t
end

"""
    gaussianEliminationColumnPivoting!(H, n, codeSupport)

Applies Gauss-Jordan elimination to matrix H of dimension nxk. It also permutes
a given code support to keep a coherent goppa code.
"""
function gaussianEliminationColumnPivoting!(H, n, codeSupport)
    k = size(H)[2]
    # In GF(2)
    for i in 1:n
        # Pivoting the matrix to ensure that it can be reduced.
        if H[i, i] != 1
            pivot = pivotColumns!(H, i, k)
            swapCodeSupport!(codeSupport, i, pivot)
        end

        if H[i, i] != 1
            error("The matrix is not full rank.")
        end

        for j in (i+1):n
            if H[j, i] != 0
                for l in i:k
                    H[j, l] = (H[j, l] + H[i, l]) % 2
                end
            end
        end
    end

    for i in 2:n
        for j in 1:(i-1)
            if H[j, i] != 0
                for l in i:k
                    H[j, l] = (H[j, l] + H[i, l]) % 2
                end
            end
        end
    end
end

"""
    GeneratorInverse!(G)

Finds the right inverse of a generator matrix.
"""
function GeneratorInverse!(G)
    A = (G * transpose(G)) .% 2
    AInverse = BinaryInverse!(A)
    return (transpose(G) * AInverse) .% 2
end

"""
    BinaryInverse!(A)

Finds the inverse of a binary matrix A. Can throw an exception
if the inverse doesn't exist.
"""
function BinaryInverse!(A)
    B = Array{Int}(A)
    n = size(B)[1]
    R = ResidueRing(ZZ, 2)
    S = MatrixSpace(R, n, n)
    NemoInverse = inv(S(B))
    Inverse = zeros(UInt32, (n, n))
    for i in 1:n
        for j in 1:n
            if NemoInverse[i, j] == 1
                Inverse[i, j] = 1
            end
        end
    end
    return Inverse
end

"""
    getPermutation(n)

Returns a random permutation matrix of dimension nxn.
"""
function getPermutation(n)
    P = Matrix{UInt32}(I, n, n)
    return P[randperm(n), :]
end

"""
    fillBinaryMatrixRandomly!(M)

Fills randomly the entries of a matrix M with 0s and 1s.
"""
function fillBinaryMatrixRandomly!(M, k)
    for i in 1:k
        M[i, i] = 1
    end

    for i in 1:k
        for j in i:k
            if rand() <= 0.5
                M[i, j] = 1
            end
        end
    end

    for i in 2:k
        for j in 1:k
            M[i, j] = (M[i, j] + M[1, j]) % 2
        end
    end
end

"""
    getScrambler(k)

Returns a random binary non-singular matrix of dimension kxk.
"""
function getScrambler(k)
    S = SInverse = zeros(UInt32, (k, k))
    fillBinaryMatrixRandomly!(S, k)
    SInverse = BinaryInverse!(S)

    return S, SInverse
end

end # FiniteFieldMatrix


# QCP example
#    maximize x + y
#
#    s.t.  x, y >= 0
#          x^2 + y^2 <= 1
#
#    solution: (0.71, 0.71) objv = 1.414

using CPLEX

env = CPLEX.Env()

model = CPLEX.Model(env, "qcqp_01")
CPLEX.set_sense!(model, :Max)

CPLEX.add_vars!(model, [1., 1.], 0., Inf)

 # add_qpterms!(model, linearindices, linearcoeffs, qrowinds, qcolinds, qcoeffs, sense, rhs)
CPLEX.add_qconstr!(model, [], [], [1, 2], [1, 2], [1, 1.], '<', 1.0)

CPLEX.optimize!(model)

println("sol = $(CPLEX.get_solution(model))")
println("obj = $(CPLEX.get_objval(model))")


using Distributions, Plots

function visualize_gamma()
    a = 0.01:0.01:4
    b = 0.01:0.01:4
    A = repeat(a', length(b), 1)
    B = repeat(b, 1, length(a))
    Z = Gamma.(A, B)

    xx = range(0.01, 4, length=100)
    p = contour(a, b, pdf.(Z,0.01), title="Gamma PDF for x=0.01")

    anim = @animate for x in xx
        contour(a, b, pdf.(Z,x), title="Gamma PDF for x=$(round(x,digits=4))")
    end
    gif(anim, "tmp.gif", fps=10)
    try
        if "BROWSER" in keys(ENV)
            browser = ENV["BROWSER"]
            cmd = `open tmp.gif -a $browser`
            run(cmd)
        else
            browser = "$(ENV["HOME"])/Applications/Google Chrome.app/"
            cmd = `open tmp.gif -a $browser`
            run(cmd)
        end
    finally
        return anim
    end
end
visualize_gamma()


flatten = A -> collect(Iterators.flatten(A));

function volumesphere(n)
    V = 1;
    if n==1
        V = 2;
    elseif n>1
        V = (2*π/n)*volumesphere(n-2);
    elseif n < 0
        throw(DomainError(n,"dimension argument must be non-negative"));
    end
    return V
end

function surfaceareasphere(n)
    return (n+1)*volumesphere(n+1)
end

function sphericalquadrature(n,deg)
    x,wx = Float64[1,-1],Float64[1,1];
    if n > 1
        z,wz = gaussjacobi(deg,(n-3)/2,(n-3)/2);
        y,wy = sphericalquadrature(n-1,deg);
        xA = map(x->flatten([x[1],sqrt(1-x[1]^2).*x[2]]),Base.product(z,y))
        x = xA[:];
        wxA = map(prod,Base.product(wz,wy))
        wx = wxA[:];
    elseif n < 1
        throw(DomainError(n,"dimension argument must be positive"));
    end
    return x,wx
end


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

# %%
using Distributions, StatsPlots, Roots

tstat(μ, X) = (mean(X) - μ)/√(var(X) / length(X))
pvalue(μ, X) = 2ccdf(TDist(length(X) - 1), abs(tstat(μ, X)))

function ci(X, α)
    X̄ = mean(X)
    c = quantile(TDist(length(X) - 1), 1 - α/2)
    d = √(var(X) / length(X))
    X̄ - c*d, X̄ + c*d
end

isininterval(x, int) = first(int) ≤ x ≤ last(int)
isinci(μ, X, α) = isininterval(μ, ci(X, α))
prob_trueisinci(dist, n, α; L=10^6) = mean(isinci(mean(dist), rand(dist, n), α) for _ in 1:L)

# %%
ci_roots(X, α) = find_zeros(μ -> log(pvalue(μ, X)) - log(α), -1e2, 1e2)

# %%
@show X = rand(10)
@show ci(X, 0.05)
@show ci_roots(X, 0.05);

# %%
isinci_roots(μ, X, α) = isininterval(μ, ci_roots(X, α))
prob_trueisinci_roots(dist, n, α; L=10^4) = mean(isinci_roots(mean(dist), rand(dist, n), α) for _ in 1:L)

# %%
@time prob_trueisinci_roots(Normal(), 20, 0.05)

# %%

0013/probability of true value being in CI (2).jl