# test basic manifold product behaviour

using ApproxManifoldProducts
using KernelDensityEstimate
using Test

# # 2D
#
# pts1 = [0.05*randn(100)'; 0.5*randn(100)']
# pts2 = [0.05*randn(100)'; 0.5*randn(100)']
#
# P1 = manikde!(pts1, (:Euclid, :Circular))
# P2 = manikde!(pts2, (:Euclid, :Circular))
#
# P12 = P1*P2
#
#
#
# plotKDE([P1;P2;P12], c=["red";"blue";"magenta"],levels=1)
#

# 3D

@testset "Basic 3D multiply and cross dimension covariance test..." begin

N = 100

pts1 = [0.05*randn(N)'; 0.05*randn(N)'; 0.75*randn(N)']
pts2 = [0.05*randn(N)'; 0.05*randn(N)'; 0.75*randn(N)']

# P1 = manikde!(pts1, (:Euclid,:Euclid,:Circular))
# P2 = manikde!(pts2, (:Euclid,:Euclid,:Circular))

# P1 = manikde!(pts1, (:Euclid,:Euclid,:Euclid))
# P2 = manikde!(pts2, (:Euclid,:Euclid,:Euclid))

P2 = kde!(pts2)
P1 = kde!(pts1)

P12 = P1*P2

pts = getPoints(P12)

@test 0.8*N < sum(abs.(pts[1,:]) .< 0.1)
@test 0.8*N < sum(abs.(pts[2,:]) .< 0.1)
@test 0.8*N < sum(abs.(pts[3,:]) .< 2.0)

P1 = manikde!(pts1, (:Euclid,:Euclid,:Euclid))
P2 = manikde!(pts2, (:Euclid,:Euclid,:Euclid))

P12 = P1*P2

pts = getPoints(P12)

@test 0.8*N < sum(abs.(pts[1,:]) .< 0.1)
@test 0.8*N < sum(abs.(pts[2,:]) .< 0.1)
@test 0.8*N < sum(abs.(pts[3,:]) .< 2.0)

P1 = manikde!(pts1, (:Euclid,:Euclid,:Circular))
P2 = manikde!(pts2, (:Euclid,:Euclid,:Circular))

P12 = P1*P2

pts = getPoints(P12)

@test 0.8*N < sum(abs.(pts[1,:]) .< 0.1)
@test 0.8*N < sum(abs.(pts[2,:]) .< 0.1)
@test 0.8*N < sum(abs.(pts[3,:]) .< 2.0)

# using KernelDensityEstimatePlotting
# using Gadfly
# Gadfly.set_default_plot_size(35cm,25cm)
#
# plotKDE([P1;P2;P12], c=["red";"blue";"magenta"],levels=1) |> PDF("/tmp/test.pdf")
# run(`evince /tmp/test.pdf`)

end

#


# This file is pat of RLinearAlgebra.jl
# 1. Specifies type
# 2. Implements sample function
# 3. Exports Type

# using Random, LinearAlgebra

"""
    LinSysVecColDetermCyclic <: LinSysVecColSelect

An immutable structure without any fields. Specifies deterministic cycling through the
columns of a linear system.
"""
struct LinSysVecColDetermCyclic <: LinSysVecColSelect end

# Common sample interface for linear systems
function sample(
    type::LinSysVecColDetermCyclic,
    A::AbstractArray,
    b::AbstractVector,
    x::AbstractVector,
    iter::Int64
)

    col_ind = mod(iter, 1:length(x))

    # Search direction
    v = zeros(length(x))
    v[col_ind] = 1.0

    # Normal equation residual
    res = dot(A[:,col_ind], A * x - b)

    return v, A, res
end
#export LinSysVecColDetermCyclic


#
###### Data transformations
#

# Lag data takes in a matrix. If we had columns a b c, then we will have a matrix with
# a(-lags) b(-lags) c(-lags) a(-lags + 1) b(-lags + 1) c(-lags + 1) ... ... ... a(-1) b(-1) c(-1).
# Maybe the columns should be reordered to have the closest lags at the front? changed!

function lagData(data, lags::Int64, obs::Int64)
	VARdata = data[lags:(obs-1),:]
	for i=(lags-1):-1:1
		VARdata = [VARdata data[i:(obs-lags+i-1),:]]
	end
	return VARdata
end

# dataMatrix is a function that creates a data matrix that goes into the estimation
# args:
#	- data: matrix of numbers
# 	- lags: number of lags in the estimation
# 	- typ: ASCIIString describing the type of VAR to be fitted, can be either of the following
# 			- None, do not include neither constant nor trend
# 			- Const, do include a constant
# 			- Trend, do include a trend
# 			- Const and trend, do include both constant and trend
# Returns a data matrix as from lagData with first columns corresponding to type.

function dataMatrix(data, lags, typ)
	obs = size(data)[1]
	data = lagData(data, lags, obs)

	typ=="None" && return(data)
	typ=="Const" && return([ones(obs-lags) data])
	typ=="Const and trend" && return([ones(obs-lags) 1:(obs-lags) data])
	typ=="Trend" && return([1:(obs-lags) data])
end

#
######
#


function _convex_combination(Q::AbstractMatrix, κ::Real)::AbstractMatrix
    """
    src: https://arxiv.org/pdf/1310.3396.pdf
    ---------
    Parameters:
    Q: Covariance matrix

    """
    Q_prim = κ * Q + (1-κ) * I(size(Q)[1])
    return Q_prim
end


# Tests on Multivariate Normal distributions

import NumericExtensions
import PDMats: ScalMat, PDiagMat, PDMat

using Distributions
using Base.Test

##### construction, basic properties, and evaluation

mu = [1., 2., 3.]
va = [1.2, 3.4, 2.6]
C = [4. -2. -1.; -2. 5. -1.; -1. -1. 6.]

x1 = [3.2, 1.8, 2.4]
x = rand(3, 100)

# SGauss

gs = gmvnormal(mu, sqrt(2.0))
@test isa(gs, IsoNormal)
@test dim(gs) == 3
@test mean(gs) == mode(gs) == mu
@test_approx_eq cov(gs) diagm(fill(2.0, 3))
@test var(gs) == diag(cov(gs))
@test_approx_eq entropy(gs) 0.5 * logdet(2π * e * cov(gs))

gsz = gmvnormal(3, sqrt(2.0))
@test isa(gsz, IsoNormal)
@test dim(gsz) == 3
@test mean(gsz) == zeros(3)
@test gsz.zeromean

# DGauss

gd = gmvnormal(mu, sqrt(va))
@test isa(gd, DiagNormal)
@test dim(gd) == 3
@test mean(gd) == mode(gd) == mu
@test_approx_eq cov(gd) diagm(va)
@test var(gd) == diag(cov(gd))
@test_approx_eq entropy(gd) 0.5 * logdet(2π * e * cov(gd))

gdz = gmvnormal(PDiagMat(va))
@test isa(gdz, DiagNormal)
@test dim(gdz) == 3
@test mean(gdz) == zeros(3)
@test gdz.zeromean

# Gauss

gf = MvNormal(mu, C)
@test isa(gf, MvNormal)
@test dim(gf) == 3
@test mean(gf) == mode(gf) == mu
@test cov(gf) == C
@test var(gf) == diag(cov(gf))
@test_approx_eq entropy(gf) 0.5 * logdet(2π * e * cov(gf))

gfz = MvNormal(C)
@test isa(gfz, MvNormal)
@test dim(gfz) == 3
@test mean(gfz) == zeros(3)
@test gfz.zeromean

##### LogPDF/PDF evaluation

@test_approx_eq_eps logpdf(gs, x1) -5.106536370454 1.0e-8
@test_approx_eq_eps logpdf(gd, x1) -6.029399605174 1.0e-8
@test_approx_eq_eps logpdf(gf, x1) -5.680452770982 1.0e-8

@test_approx_eq_eps logpdf(gsz, x1) -8.606536370454 1.0e-8
@test_approx_eq_eps logpdf(gdz, x1) -9.788449378930 1.0e-8
@test_approx_eq_eps logpdf(gfz, x1) -9.621416626404 1.0e-8

n = size(x, 2)
r = zeros(n)

for i = 1:n; r[i] = logpdf(gs, x[:,i]); end
@test_approx_eq logpdf(gs, x) r
@test_approx_eq pdf(gs, x) exp(r)

for i = 1:n; r[i] = logpdf(gsz, x[:,i]); end
@test_approx_eq logpdf(gsz, x) r
@test_approx_eq pdf(gsz, x) exp(r)

for i = 1:n; r[i] = logpdf(gd, x[:,i]); end
@test_approx_eq logpdf(gd, x) r
@test_approx_eq pdf(gd, x) exp(r)

for i = 1:n; r[i] = logpdf(gdz, x[:,i]); end
@test_approx_eq logpdf(gdz, x) r
@test_approx_eq pdf(gdz, x) exp(r)

for i = 1:n; r[i] = logpdf(gf, x[:,i]); end
@test_approx_eq logpdf(gf, x) r
@test_approx_eq pdf(gf, x) exp(r)

for i = 1:n; r[i] = logpdf(gfz, x[:,i]); end
@test_approx_eq logpdf(gfz, x) r
@test_approx_eq pdf(gfz, x) exp(r)

##### Sampling

x = rand(gs)
@test isa(x, Vector{Float64})
@test length(x) == dim(gs)

x = rand(gd)
@test isa(x, Vector{Float64})
@test length(x) == dim(gd)

x = rand(gf)
@test isa(x, Vector{Float64})
@test length(x) == dim(gf)

x = rand(gsz)
@test isa(x, Vector{Float64})
@test length(x) == dim(gsz)

x = rand(gdz)
@test isa(x, Vector{Float64})
@test length(x) == dim(gdz)

x = rand(gfz)
@test isa(x, Vector{Float64})
@test length(x) == dim(gfz)

n = 10
x = rand(gs, n)
@test isa(x, Matrix{Float64})
@test size(x) == (dim(gs), n)

x = rand(gd, n)
@test isa(x, Matrix{Float64})
@test size(x) == (dim(gd), n)

x = rand(gf, n)
@test isa(x, Matrix{Float64})
@test size(x) == (dim(gf), n)

x = rand(gsz, n)
@test isa(x, Matrix{Float64})
@test size(x) == (dim(gsz), n)

x = rand(gdz, n)
@test isa(x, Matrix{Float64})
@test size(x) == (dim(gdz), n)

x = rand(gfz, n)
@test isa(x, Matrix{Float64})
@test size(x) == (dim(gfz), n)

##### MLE

# a slow but safe way to implement MLE for verification

function _gauss_mle(x::Matrix{Float64})
	mu = vec(mean(x, 2))
	z = x .- mu
	C = (z * z') * (1/size(x,2))
	return mu, C
end

function _gauss_mle(x::Matrix{Float64}, w::Vector{Float64})
	sw = sum(w)
	mu = (x * w) * (1/sw)
	z = x .- mu
	C = (z * scale(w, z')) * (1/sw)
	Base.LinAlg.copytri!(C, 'U')
	return mu, C
end

x = randn(3, 200) .+ randn(3) * 2.
w = rand(200)

g = fit(MvNormal, x)
mu, C = _gauss_mle(x)
@test_approx_eq mean(g) mu
@test_approx_eq cov(g) C

g = fit_mle(MvNormal, x, w)
mu, C = _gauss_mle(x, w)
@test_approx_eq mean(g) mu
@test_approx_eq cov(g) C

g = fit(IsoNormal, x)
mu, C = _gauss_mle(x)
@test_approx_eq g.μ mu
@test_approx_eq g.Σ.value mean(diag(C))

g = fit_mle(IsoNormal, x, w)
mu, C = _gauss_mle(x, w)
@test_approx_eq g.μ mu
@test_approx_eq g.Σ.value mean(diag(C))

assumed_g = Distributions.IsoNormalKnownSigma(3, 1.)
g = fit_mle(assumed_g, x, w)
mu, C = _gauss_mle(x, w)
@test_approx_eq g.μ mu
@test_approx_eq diag(g.Σ) diag(assumed_g.Σ)

g = fit(DiagNormal, x)
mu, C = _gauss_mle(x)
@test_approx_eq g.μ mu
@test_approx_eq g.Σ.diag diag(C)

g = fit_mle(DiagNormal, x, w)
mu, C = _gauss_mle(x, w)
@test_approx_eq g.μ mu
@test_approx_eq g.Σ.diag diag(C)

##### Sufficient statistics type
n = 3
X = reshape(Float64[1:12], 4, n)
w = rand(n)
Xw = X * diagm(w)

ss = suffstats(MvNormal, X)
ssw = suffstats(MvNormal, X, w)

s_t = sum(X, 2)
ws_t = sum(Xw, 2)
tmp = X .- (s_t ./ n)
ss_t = tmp*tmp'

tmp = X .- sum(Xw, 2) ./ sum(w)
wss_t = (tmp*diagm(w))*tmp'
tw_t = length(w)
wtw_t = sum(w)

@test_approx_eq ss.s s_t
@test_approx_eq ss.m (s_t ./ n)
@test_approx_eq ss.s2 ss_t
@test_approx_eq ss.tw tw_t

@test_approx_eq ssw.s ws_t
@test_approx_eq ssw.m (ws_t ./ wtw_t)
@test_approx_eq ssw.s2 wss_t
@test_approx_eq ssw.tw wtw_t


using OctreeBH
using StaticArrays
using Test

const BOXSIZE = 1.0

const N = 3 #spatial dimension
const T = Float64

Npart = 1000 #number of particles

@testset "gravtree" begin

#randomly distributing particles
X = [@SVector rand(N) for _ in 1:Npart]

topnode_length = @SVector(ones(N)) * BOXSIZE  #actual length of tree

center = topnode_length .* 0.5

#individual smoothing length (only relevant for scatter ngbs)
hsml = zeros(Npart)

mass = ones(Npart) #particle mass

part = [Data{N,T}(SVector(X[i]), i, hsml[i], mass[i]) for i in eachindex(X)]

#build the tree
tree = buildtree(part, center, topnode_length);

#search center
#x = @SVector rand(N)

boxsizes = @SVector(ones(N)) * BOXSIZE  #for periodic B.C.

#softening = BOXSIZE / Npart^(1/3.)
softening = 0.0
ANGLE = 0.0

pot = zeros(Npart)
pot_bf = zeros(Npart)
for i in eachindex(X)
    ga = GravTreeGather{N,T}()
    gravity_treewalk!(ga,X[i],tree,ANGLE,softening,boxsizes)
    pot[i] = ga.pot

    ga_bf = GravTreeGather{N,T}()
    gravity_bruteforce!(ga_bf,X[i],X,mass,boxsizes)
    pot_bf[i] = ga_bf.pot
end
@test all(pot .≈ pot_bf)

end


using Distributions, Plots
pyplot()

function flip_coin(biased_coin::Bernoulli, n_flips::Int64)
    heads_probabilities = zeros(n_flips)
    flips = Array{Bool}(undef, n_flips)
    x_s = Array{Int64}(undef, n_flips)
    heads = 0
    for i = 1:n_flips
        x_s[i] = i
        flip = rand(biased_coin, 1)[1]
        flips[i] = flip
        if flip
            heads = heads + 1
        end
        heads_probabilities[i] = heads / i
    end
    x = [1:n_flips]
    return x_s, heads_probabilities, flips
end

function main()
    n_flips = 500
    biased_coin = Bernoulli(0.60)
    x, y, _ = flip_coin(biased_coin, n_flips)
    pyplot()
    plot(x, y, title = "Learning a Coin's Bias via Sampling", legend=false,
        lw = 1, color=:red,
        xlabel = "Number of coin flip samples", ylabel = "Probability of Heads")
    savefig("flip.pdf")
end

main()


using Test, DiscreteExteriorCalculus, DiscretePDEs
using UniqueVectors: UniqueVector
using LinearAlgebra: norm
using AdmittanceModels: lossless_modes_dense, apply_transform, get_Y

# Modes of a rectangular box with constant μ and ϵ.
box_modes(m, n, l, a, b, c, μ, ϵ) = sqrt((m * π/a)^2 + (n * π/b)^2 +
    (l * π/c)^2)/(2π * sqrt(μ * ϵ))
# The lowest mode of our box is the 011 mode.
a, b, c = 10, 12, 14
correct_A(x, y, z) = sin((π/b) * y) * sin((π/c) * z) * [1, 0, 0]

file_name = joinpath(@__DIR__, "box.geo")
geo_write!(file_name, characteristic_length_factor=1,
    footer="""
    Box(1) = {0, 0, 0, $a, $b, $c};
    """)

initialize!()
gmsh_open!(file_name)
N, K = 3, 4
mesh!(K)
node_tags, points, tcomp = get_triangulated_complex(N, K)
group_dict = get_physical_groups(node_tags, points)
@test typeof(tcomp) <: TriangulatedComplex{N, K}
comp = tcomp.complex
orient!(comp)
m = Metric(N)
mesh = Mesh(tcomp, circumcenter(m))

μ⁻, ϵ = 2, 3
μ⁻_form = get_material(comp, μ⁻, 3)
Λ⁻_form = get_material(comp, 0, 2)
σ_form = get_material(comp, 0, 2)
ϵ_form = get_material(comp, ϵ, 2)

pso, null_basis = electrodynamics_pso(m, mesh, Vector{Cell{N}}[], boundary(comp),
    μ⁻_form, Λ⁻_form, σ_form, ϵ_form)
constrained_pso = apply_transform(pso, null_basis)

density(mat) = count(!iszero, mat)/(size(mat, 1) * size(mat, 2))
@test density(null_basis) < .002
@test all(map(density, get_Y(constrained_pso)) .< .05)

λs, vs = lossless_modes_dense(constrained_pso)
freq = imag(λs[1])/(2π)
correct_freq = box_modes(0,1,1,a,b,c,1/μ⁻,ϵ)

comp_points = UniqueVector([c.points[1] for c in comp.cells[1]])
correct_vec_A = [correct_A(p.coords...) for p in comp_points]
correct_vec_A /= maximum(norm.(correct_vec_A))
vec_A = sharp(m, comp, null_basis * vs[:,1])
vec_A /= maximum(norm.(vec_A))
i = argmax(norm.(correct_vec_A))
vec_A *= sign(transpose(vec_A[i]) * correct_vec_A[i])

@testset "box modes" begin
    @test isapprox(freq, correct_freq, rtol=5e-3)
    @test maximum([norm(x - y) for (x, y) in zip(vec_A, correct_vec_A)]) < .25
end

if false # set to true to display plots with Gmsh
    ordering = [findfirst(isequal(p), comp_points) for p in points]
    add_field!("Vector potential", node_tags, vec_A[ordering])
    add_field!("Correct vector potential", node_tags, correct_vec_A[ordering])
    gui!()
end
rm(file_name)


using ReservoirComputing

#model parameters
const approx_res_size = 30
const radius = 1.2
const degree = 6
const first_activation = tanh
const second_activation = identity
const first_lambda = 0.45
const second_lambda = 0.3
const sigma = 0.3
const beta = 0.0
const alpha = 1.0
const nla_type = NLADefault()
const in_size = 3
const out_size = 3
const extended_states = false
const h_steps = 2

W_in = init_input_layer(approx_res_size, in_size, sigma)
W = init_reservoir_givendeg(approx_res_size, radius, degree)

const train_len = 50
const predict_len = 12
data = ones(Float64, in_size, 100)
train = data[:, 1:1+train_len-1]
test = data[:, train_len:train_len+predict_len-1]

#constructor 1
esn = dafESN(approx_res_size,
            train,
            degree,
            radius,
            first_lambda,
            second_lambda,
            first_activation = first_activation,
            second_activation = second_activation,
            sigma = sigma,
            alpha = alpha,
            nla_type = nla_type,
            extended_states = extended_states)

#test constructor
@test isequal(Integer(floor(approx_res_size/in_size)*in_size), esn.res_size)
@test isequal(train, esn.train_data)
@test isequal(alpha, esn.alpha)
@test isequal(first_activation, esn.first_activation)
@test isequal(second_activation, esn.second_activation)
@test isequal(first_lambda, esn.first_lambda)
@test isequal(second_lambda, esn.second_lambda)
@test isequal(nla_type, esn.nla_type)
@test size(esn.W) == (esn.res_size, esn.res_size)
@test size(esn.W_in) == (esn.res_size, esn.in_size)
@test size(esn.states) == (esn.res_size, train_len)

#test train
W_out = ESNtrain(esn, beta)
@test size(W_out) == (out_size, esn.res_size)
#test predict
output = dafESNpredict(esn, predict_len, W_out)
@test size(output) == (out_size, predict_len)

#constructor 2
esn = dafESN(W,
            train,
            first_lambda,
            second_lambda,
            first_activation = first_activation,
            second_activation = second_activation,
            sigma = sigma,
            alpha = alpha,
            nla_type = nla_type,
            extended_states = extended_states)

#test constructor
@test isequal(Integer(floor(approx_res_size/in_size)*in_size), esn.res_size)
@test isequal(train, esn.train_data)
@test isequal(alpha, esn.alpha)
@test isequal(first_activation, esn.first_activation)
@test isequal(second_activation, esn.second_activation)
@test isequal(first_lambda, esn.first_lambda)
@test isequal(second_lambda, esn.second_lambda)
@test isequal(nla_type, esn.nla_type)
@test size(esn.W) == (esn.res_size, esn.res_size)
@test size(esn.W_in) == (esn.res_size, esn.in_size)
@test size(esn.states) == (esn.res_size, train_len)

#test train
W_out = ESNtrain(esn, beta)
@test size(W_out) == (out_size, esn.res_size)
#test predict
output = dafESNpredict(esn, predict_len, W_out)
@test size(output) == (out_size, predict_len)

#constructor 3
esn = dafESN(approx_res_size,
            train,
            degree,
            radius,
            first_lambda,
            second_lambda,
            W_in,
            first_activation = first_activation,
            second_activation = second_activation,
            alpha = alpha,
            nla_type = nla_type,
            extended_states = extended_states)

#test constructor
@test isequal(Integer(floor(approx_res_size/in_size)*in_size), esn.res_size)
@test isequal(train, esn.train_data)
@test isequal(alpha, esn.alpha)
@test isequal(first_activation, esn.first_activation)
@test isequal(second_activation, esn.second_activation)
@test isequal(first_lambda, esn.first_lambda)
@test isequal(second_lambda, esn.second_lambda)
@test isequal(nla_type, esn.nla_type)
@test size(esn.W) == (esn.res_size, esn.res_size)
@test size(esn.W_in) == (esn.res_size, esn.in_size)
@test size(esn.states) == (esn.res_size, train_len)

#test train
W_out = ESNtrain(esn, beta)
@test size(W_out) == (out_size, esn.res_size)
#test predict
output = dafESNpredict(esn, predict_len, W_out)
@test size(output) == (out_size, predict_len)

#constructor 4
esn = dafESN(W,
            train,
            first_lambda,
            second_lambda,
            W_in,
            first_activation = first_activation,
            second_activation = second_activation,
            alpha = alpha,
            nla_type = nla_type,
            extended_states = extended_states)

#test constructor
@test isequal(Integer(floor(approx_res_size/in_size)*in_size), esn.res_size)
@test isequal(train, esn.train_data)
@test isequal(alpha, esn.alpha)
@test isequal(first_activation, esn.first_activation)
@test isequal(second_activation, esn.second_activation)
@test isequal(first_lambda, esn.first_lambda)
@test isequal(second_lambda, esn.second_lambda)
@test isequal(nla_type, esn.nla_type)
@test size(esn.W) == (esn.res_size, esn.res_size)
@test size(esn.W_in) == (esn.res_size, esn.in_size)
@test size(esn.states) == (esn.res_size, train_len)

#test train
W_out = ESNtrain(esn, beta)
@test size(W_out) == (out_size, esn.res_size)
#test predict
output = dafESNpredict(esn, predict_len, W_out)
@test size(output) == (out_size, predict_len)

output = dafESNpredict_h_steps(esn, predict_len, h_steps, test, W_out)
@test size(output) == (out_size, predict_len)


#-------------------------------------------------------------------------------
# Decomposition
#-------------------------------------------------------------------------------
export proj_to_irrep, block_decomposition
"""
Transform matrix from a (reducible) representation to a given irrep.

For a representation D(g) and irrep d(g), find S that
    S⁺⋅D(g)⋅S = d(g)
"""
function proj_to_irrep(
    rep::AbstractVector{<:AbstractMatrix},
    irep::AbstractVector{<:AbstractMatrix};
    R::Bool=false
)
    N, D = length(rep), size(irep[1], 1)
    vecs = map(1:D) do i
        Pii = proj_operator(rep, [conj(m[i,i]) for m in irep])
        e, v = eigen(Hermitian(Pii))
        v[:, e .> 1e-7]
    end
    for i = 2:D
        Pi1 = proj_operator(rep, [conj(m[i,1]) for m in irep])
        U = vecs[i]' * Pi1 * vecs[1] * D / N
        #@assert norm(U*U'-I) < 1e-7 "Not unitary."
        vecs[i] = vecs[i] * U
    end
    spaces = map(1:size(vecs[1], 2)) do i
        hcat((vecs[j][:, i] for j=1:D)...)
    end
    if R && sum(norm.(imag.(rep))) > 1e-7
        [sqrt(2)*[real(s) imag(s)] for s in spaces]
    else
        spaces
    end
end
#-------------------------------------------------------------------------------
"""
Find a list of projection Sᵢ that
    Sᵢ⁺⋅D(g)⋅Sᵢ = dᵢ(g),
where {dᵢ} is a list of irreducible representations as an input.
"""
function proj_to_irrep(
    rep::AbstractVector{<:AbstractMatrix},
    ireps::AbstractVector{<:AbstractVector{<:AbstractMatrix}};
    R::Bool=false
)
    vcat([proj_to_irrep(rep, irep, R=R) for irep in ireps]...)
end
#-------------------------------------------------------------------------------
function block_decomposition(
    rep::AbstractVector{<:AbstractMatrix},
    group::AbstractFiniteGroup;
    R::Bool=true
)
    ct = charactertable(group)
    row = single_complex_row(ct)
    ireps =  irreps(group, ct[row, :])
    proj_to_irrep(rep, ireps, R=R)
end
#-------------------------------------------------------------------------------
function block_decomposition(
    rep::AbstractVector{<:AbstractMatrix},
    group::String;
    R::Bool=true
)
    block_decomposition(rep, pointgroup(group), R=R)
end
#-------------------------------------------------------------------------------
function block_decomposition(rep::AbstractVector{<:AbstractMatrix}; R::Bool=true)
    n = length(rep)
    multab = Matrix{Integer}(undef, n, n)
    for i=1:n, j=1:n
        gij = rep[i]*rep[j]
        gij_index = 0
        for k = 1:n
            if norm(rep[k] - gij) < 1e-10
                @assert iszero(gij_index) "Not faithful representation. An explicit `FiniteGroup` should be provided."
                gij_index = k
            end
        end
        @assert gij_index > 0 "The input `rep` is not a representation."
        multab[i,j] = gij_index
    end
    group = FiniteGroup(multab)
    block_decomposition(rep, group, R=R)
end


@symbol_func function perimeter(cur_reactor::AbstractReactor)
  cur_perimeter = 2.0

  cur_perimeter *= cur_reactor.pi

  cur_perimeter *= a(cur_reactor)

  cur_perimeter *= sqrt(
    g_p(cur_reactor)
  )

  cur_perimeter
end


using IntUtils

function main()
    primes = sieve_of_eratosthenes(2_000_000)
    sum_primes = 0
    for i in 1:length(primes)
        if primes[i]
            sum_primes += i
        end
    end

    @printf("The sum of primes under 2000000 is %d\n", sum_primes)
end

main()


function Armijo(nlp, f, g, x, d, τ₀)
  # Simple Armijo backtracking
  hp0 = g' * d
  t = 1.0
  ft = obj(nlp, x + t * d)
  while ft > (f + τ₀ * t * hp0)
    t /= 2.0
    ft = obj(nlp, x + t * d)
  end
end


using IMinuit
using Test
# using DataFrames, CSV

# @testset "IMinuit.jl" begin
    # Write your tests here.
# end

@testset "minimizing simple functions: " begin
    f(x) = x[1]^2 + (x[2]-1)^2 + (x[3]-2)^4
    f1(x, y, z) = x^2 + (y-1)^2 + (z-2)^4

    m = Minuit(f, [1,1,0])
    m1 = Minuit(f1, x = 1, y = 1, z = 0)

    migrad(m)
    migrad(m1)

    @test typeof(m) == ArrayFit
    @test typeof(m1) == Fit

    # the second parameter should be about 1
    @test get(m.values, 1) ≈ 1
    @test get(m1.values, 1) ≈ 1

    @test length(get_contours_all(m, f, npts = 3)) == 19
    @test length(get_contours_all(m1, f1, npts = 3)) == 19
end
#
# @testset "a simple but realistic fit: " begin
#     datadf = DataFrame!(CSV.File("../docs/testdata.csv"))
#     data = Data(datadf)
#
#     M = 3.686; mπ = 0.14; mJ = 3.097;
#
#     λ(x, y, z) = x^2 + y^2 + z^2 - 2x*y - 2y*z - 2z*x
#
#     # a simple function that will be used to fit the data: QCD multipole expansion model for ψ'→J/ψπ⁺π⁻
#     # The important ππ FSI effect is not taken into account
#     # bg is just for introducing a third parameter
#     function dist(w, N, c, bg)
#         if (w ≤ 2mπ || w ≥ M-mJ)
#             res = 0.0
#         else
#             q1 = sqrt(λ(w^2, mπ^2, mπ^2))/(2w)
#             q2 = sqrt(λ(M^2, w^2, mJ^2))/(2M)
#             res = N * q1 * q2 * (w^2 - c*mπ^2)^2 + bg
#         end
#         return res * 1e6
#     end
#
#     # dist(w, par) = dist(w, par...);
#
#     χsq(par) = chisq(dist, data, par)
#     χsq1(N, c, bg) = chisq(dist, data, (N, c, bg));
#
#     gradf(par) = gradient(χsq, par)
#     fit = Minuit(χsq, [1, 2, 0], error = 0.1*ones(3), grad = gradf)
#     fit.strategy = 0;
#
#     fit1 = Minuit(χsq1, N = 1, c = 2, bg = 0, error_N = 0.1, error_c = 0.1, error_bg = 0.1)
#     fit1.strategy = 0;
#
#     migrad(fit)
#
#     migrad(fit1)
#
#     @test isapprox(get(fit.values, 0), 2.61, rtol = 1e-2)
#     @test isapprox(get(fit1.values, 0), 2.61, rtol = 1e-2)
#
# end


using ArnoldiMethod, LinearAlgebra
using BenchmarkTools

function bench_schur(n = 30)
    H = triu(rand(n, n), -1)
    Q = Matrix{Float64}(I, n, n)
    a = @benchmark ArnoldiMethod.local_schurfact!(HH, 1, $n, QQ) setup = (HH = copy($H); QQ = copy($Q))
    b = @benchmark eigvals(HH) setup = (HH = copy($H))
    a, b
end


export formula_FR, formula_PR, formula_HS, formula_HZ

function formula_FR(∇f,∇ft,s,d)

    β = (∇ft⋅∇ft)/(∇f⋅∇f)

    return β
end

function formula_PR(∇f,∇ft,s,d)

    y = ∇ft - ∇f
    β = (∇ft⋅y)/(∇f⋅∇f)

    return β
end

function formula_HS(∇f,∇ft,s,d)

    y = ∇ft - ∇f
    β = (∇ft⋅y)/(d⋅y)

    return β
end

function formula_HZ(∇f,∇ft,s,d)

    y = ∇ft - ∇f
    n2y = y⋅y
    β1 = (y⋅d)
    β = ((y-2*d*n2y/β1)⋅∇ft)/β1

    return β
end


export rq_fact, qr_fact

function qr_fact(M::AbstractMatrix, Dcut::Int=typemax(Int), tol::Float64=1E-12, args...)
    F = qr(M, args...)
    q, r = _qr_fix(Array(F.Q), Array(F.R))
    if Dcut > size(q, 2) return q, r end
    U, Σ, V = svd(r, Dcut, tol)
    q * U, Diagonal(Σ) * V'
end

function rq_fact(M::AbstractMatrix, Dcut::Int=typemax(Int), tol::Float64=1E-12, args...)
    q, r = qr_fact(M', Dcut, tol, args...)
    r', q'
end

function _qr_fix(Q::T, R::AbstractMatrix) where {T <: AbstractMatrix}
    d = diag(R)
    for i ∈ eachindex(d)
        @inbounds d[i] = ifelse(isapprox(d[i], 0, atol=1e-14), 1, d[i])
    end
    ph = d ./ abs.(d)
    Q * Diagonal(ph), Diagonal(ph) * R
end

function LinearAlgebra.svd(A::AbstractMatrix, Dcut::Int=typemax(Int), tol::Float64=1E-12, args...)
    #U, Σ, V = psvd(A, rank=Dcut, args...)

    U, Σ, V = svd(A, args...)

    tol = Σ[1] * max(eps(), tol)
    δ = min(Dcut, sum(Σ .> tol))

    U = U[:, 1:δ]
    Σ = Σ[1:δ]
    Σ ./ sum(Σ .^ 2)
    V = V[:, 1:δ]

    d = diag(U)
    for i ∈ eachindex(d)
        @inbounds d[i] = ifelse(isapprox(d[i], 0, atol=1e-14), 1, d[i])
    end
    ph = d ./ abs.(d)
    U * Diagonal(ph), Σ, V * Diagonal(ph)
end


# This file is a part of AstroLib.jl. License is MIT "Expat".

"""
    mean_obliquity(jd) -> m_eps

### Purpose ###

Return the mean obliquity of the ecliptic for a given Julian date

### Explanation ###

The function is used by the [`co_nutate`](@ref) procedure.

### Arguments ###

* `jd`: julian date

### Output ###

* `m_eps`: mean obliquity of the ecliptic, in radians

### Example ###

```jldoctest
julia> using AstroLib

julia> mean_obliquity(jdcnv(1978,01,7,11, 01))
0.4091425159336512
```

### Notes ###

The algorithm used to find the mean obliquity(`eps0`) is mentioned in
USNO Circular 179, but the canonical reference for the IAU adoption
is apparently Hilton et al., 2006, Celest.Mech.Dyn.Astron. 94, 351. 2000
"""
function mean_obliquity(jd::Real)
    t = (jd - J2000) / JULIANCENTURY
    eps0 = @evalpoly t 84381.406 -46.836769 -0.0001831 0.0020034 -0.576e-6 -4.34e-8
    return sec2rad(eps0)
end


using Complementarity, Test

@testset "mcp test 1 with PATHSolver" begin

    m = MCPModel()

    @variable(m, x3 >= 0)
    @variable(m, x4 >= 0)
    @variable(m, x1 >= 0)
    @variable(m, x2 >= 0)

    @mapping(m, F2, x3-2x4 +2)
    @mapping(m, F3, x1-x2+2x3-2x4 -2)
    @mapping(m, F4, x1+2x2-2x3+4x4 -6)
    @mapping(m, F1, -x3-x4 +2)

    @complementarity(m, F4, x4)
    @complementarity(m, F1, x1)
    @complementarity(m, F3, x3)
    @complementarity(m, F2, x2)

    # This will show deprecation warning.
    status = solveLCP(m)
    z = [result_value(x1), result_value(x2), result_value(x3), result_value(x4)]
    @show z
    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])

    # This will show deprecation warning.
    status = solveMCP(m; linear=true)
    z = [result_value(x1), result_value(x2), result_value(x3), result_value(x4)]
    @show z
    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])

    status = solveMCP(m; convergence_tolerance=1e-8, output="yes", time_limit=3600)
    z = [result_value(x1), result_value(x2), result_value(x3), result_value(x4)]
    @show z
    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])

    status = solveMCP(m)
    z = [result_value(x1), result_value(x2), result_value(x3), result_value(x4)]
    @show z
    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])

end

println("------------------------------------------------------------------")

@testset "mcp test 2 with PATHSolver" begin

    m = MCPModel()

    @variable(m, x3 >= 0)
    @variable(m, x4 >= 0)
    @variable(m, x1 >= 0)
    @variable(m, x2 >= 0)

    @mapping(m, F2, x3-2x4 +2)
    @mapping(m, F3, x1-x2+2x3-2x4 -2)
    @mapping(m, F4, x1+2x2-2x3+4x4 -6)
    @mapping(m, F1, -x3-x4 +2)

    @complementarity(m, F2, x2)
    @complementarity(m, F3, x3)
    @complementarity(m, F1, x1)
    @complementarity(m, F4, x4)

    status = solveMCP(m, convergence_tolerance=1e-8, output="yes", time_limit=3600)
    z = [result_value(x1), result_value(x2), result_value(x3), result_value(x4)]
    @show z
    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])
end

println("------------------------------------------------------------------")

@testset "mcp test 3 with PATHSolver" begin

    m = MCPModel()

    M = [0  0 -1 -1 ;
         0  0  1 -2 ;
         1 -1  2 -2 ;
         1  2 -2  4 ]

    q = [2; 2; -2; -6]

    lb = zeros(4)
    ub = Inf*ones(4)

    @variable(m, lb[i] <= myvariablename[i in 1:4] <= ub[i])
    @mapping(m, myconst[i=1:4], sum(M[i,j]*myvariablename[j] for j in 1:4) + q[i])
    @complementarity(m, myconst, myvariablename)

    status = solveMCP(m; convergence_tolerance=1e-8, output="yes", time_limit=3600)
    z = result_value.(myvariablename)
    @test isapprox(z, [2.8, 0.0, 0.8, 1.2])
end

println("------------------------------------------------------------------")

@testset "mcp test 4 with PATHSolver" begin

    m = MCPModel()

    M = [0  0 -1 -1 ;
         0  0  1 -2 ;
         1 -1  2 -2 ;
         1  2 -2  4 ]

    q = [2; 2; -2; -6]

    lb = zeros(4)
    ub = Inf*ones(4)

    items = 1:4

    @variable(m, lb[i] <= x[i in items] <= ub[i])
    # @variable(m, x[i in items] >= 0)
    @mapping(m, F[i in items], sum(M[i,j]*x[j] for j in items) + q[i])
    @complementarity(m, F, x)

    status = solveMCP(m; convergence_tolerance=1e-8, output="no", time_limit=3600)
    z = result_value.(x)
    @test isapprox(z.data, [2.8, 0.0, 0.8, 1.2])

end

println("------------------------------------------------------------------")

@testset "mcp test 5 with PATHSolver" begin

    m = nothing
    m = MCPModel()

    lb = zeros(4)
    ub = Inf*ones(4)
    items = 1:4
    @variable(m, lb[i] <= x[i in items] <= ub[i])

    @mapping(m, F1, 3*x[1]^2 + 2*x[1]*x[2] + 2*x[2]^2 + x[3] + 3*x[4] -6)
    @mapping(m, F2, 2*x[1]^2 + x[1] + x[2]^2 + 3*x[3] + 2*x[4] -2)
    @mapping(m, F3, 3*x[1]^2 + x[1]*x[2] + 2*x[2]^2 + 2*x[3] + 3*x[4] -1)
    @mapping(m, F4, x[1]^2 + 3*x[2]^2 + 2*x[3] + 3*x[4] - 3)

    @complementarity(m, F1, x[1])
    @complementarity(m, F2, x[2])
    @complementarity(m, F3, x[3])
    @complementarity(m, F4, x[4])

    set_start_value(x[1], 1.25)
    set_start_value(x[2], 0.)
    set_start_value(x[3], 0.)
    set_start_value(x[4], 0.5)

    status = solveMCP(m)
    @show status

    z = result_value.(x)
    @show z

    @test isapprox(z.data, [1.22474, 0.0, 0.0, 0.5]; atol=1e-4)

    set_start_value(x[1], 1.19)
    set_start_value(x[2], 0.2)
    set_start_value(x[3], 0.1)
    set_start_value(x[4], 0.49)

    status = solveMCP(m)
    @show status

    z = result_value.(x)

    @show z

    @test isapprox(z.data, [1.22474, 0.0, 0.0, 0.5]; atol=1e-4)

end

println("------------------------------------------------------------------")


#Finds the representation of a permutation for a particular partition
function example1()
	example1(5,[3, 2],[2,3,1,4,5])
end

# Parameters:
#	N::Int
#	- the problem size
#	partition::Array{Int, 1}
#	- a partition of N
#	permutation::Array{Int, 1}
#	- a permutation of N
function example1(N::Int, partition::Array{Int, 1}, permutation::Array{Int, 1})

	P, WI = partitions(N) #Creates the partitions of i for i is 1 through N
	p = 1 #Find the index of the specified partitions
	while P[N][p] != partition
		p += 1
	end

	YS = ys_partition(N, partition) #Creates the Yamanouchi Symbols of the specified partition
	degree = length(YS)

	YOR, PT = yor(N) #Creates Young's Orthogonal Representations and the Pointer Tree for partition decomposistion
	YORnp = YOR[N][p] #Get the Representation of each Adjacent Transposition of the specified partition

	RM = yor_permutation(permutation, YORnp) #Creates the Representation Matrix of the specified permutation for the specified partition

	println("Finds the representation of a permutation for a particular partition")
	println("")
	println("Permutation: ")
	println(permutation_string(permutation))
	println("")
	println("Partition: ")
	println(partition_string(partition))
	println("")
	println("Degree: ")
	println(degree)
	println("")
	println("Standard Tableaux: ")
	for i = 1:degree #Converts the Yamanouchi Symbols into Standard Tableau and prints them
		print_ys(YS[i])
		println("")
	end
	println("Representation matrix of the permutation for this partition: ")
	print(full(round(RM,5)))
end

#If a and b are permutations and c = a * b, demonstrates that the representation of c is the representation of a multiplied by the representation of b
function example2()
	example2(5,[3,2],[1,2,4,3,5],[2,3,1,4,5])
end

# Parameters:
#	N::Int
#	- the problem size
#	partition::Array{Int, 1}
#	- a partition of N
#	p1::Array{Int, 1}
#	- the first permutation of N
#	p2::Array{Int, 1}
#	- the second permutation of N
function example2(N::Int, partition::Array{Int, 1}, p1::Array{Int, 1}, p2::Array{Int, 1})

	P, WI = partitions(N) #Creates the partitions of i for i is 1 through N
	p = 1 #Find the index of the specified partitions
	while P[N][p] != partition
		p += 1
	end

	pm = sn_multiply(p1, p2) #Find the product of the two permutations

	YOR, PT = yor(N) #Creates Young's Orthogonal Representations and the Pointer Tree for partition decomposistion
	YORnp = YOR[N][p] #Get the Representation of each Adjacent Transposition of the specified partition

	RM1 = yor_permutation(p1, YORnp) #Find the representation of the first permutation
	RM2 = yor_permutation(p2, YORnp) #Find the representation of the second permutation
	RMm = yor_permutation(pm, YORnp) #Find the representation of the product of the permutations
	error = RM1 * RM2 - RMm #Find the error in the process

	println("If a and b are permutations and c = a * b, demonstrates that the representation of c is the representation of a multiplied by the representation of b")
	println("")
	ST1 = permutation_string(p1)
	println("Representation of ", ST1)
	println(full(round(RM1, 5)))
	println("")
	ST2 = permutation_string(p2)
	println("Representation of ", ST2)
	println(full(round(RM2, 5)))
	println("")
	STm = permutation_string(pm)
	println(ST1," * ", ST2, " is ", STm)
	println("")
	println("Representation of ", STm)
	println(full(round(RMm, 5)))
	println("")
	println("Maximum error: ")
	println(maximum(abs(error)))
end

#Demonstrates how to put the values of a function on Sn in the order used to compute the FFT
function example3()

	PA = Array(Array{Int, 1}, 24) #Create an array to hold all of the permutations of 1,2,3,4
	PA[1] = [1,2,3,4]
	PA[2] = [1,2,4,3]
	PA[3] = [1,3,2,4]
	PA[4] = [1,3,4,2]
	PA[5] = [1,4,2,3]
	PA[6] = [1,4,3,2]
	PA[7] = [2,1,3,4]
	PA[8] = [2,1,4,3]
	PA[9] = [2,3,1,4]
	PA[10] = [2,3,4,1]
	PA[11] = [2,4,1,3]
	PA[12] = [2,4,3,1]
	PA[13] = [3,1,2,4]
	PA[14] = [3,1,4,2]
	PA[15] = [3,2,1,4]
	PA[16] = [3,2,4,1]
	PA[17] = [3,4,1,2]
	PA[18] = [3,4,2,1]
	PA[19] = [4,1,2,3]
	PA[20] = [4,1,3,2]
	PA[21] = [4,2,1,3]
	PA[22] = [4,2,3,1]
	PA[23] = [4,3,1,2]
	PA[24] = [4,3,2,1]

	VA = Array(Float64, 24) #Create the values of the function on Sn
	for i = 1:24
		VA[i] = Float64(i)
	end

	SNF = snf(4,PA,VA) #Put the values into the order used to compute the FFT

	println("Demonstrates how to put the values of a function on Sn in the order used to compute the FFT")
	println("")
	println("Start with any ordering: ")
	println("Permutation   Function value for that Permutation")
	for i = 1:24
		ST = permutation_string(PA[i])
		ST = string(ST, "  ", VA[i])
		println(ST)
	end
	println("")
	println("Is sorted into this order for SnFFT to use: ")
	println("Permutation   Function value for that Permutation")
	for i = 1:24
		ni = round(Int, SNF[i])
		ST = permutation_string(PA[ni])
		ST = string(ST, "  ", VA[ni])
		println(ST)
	end
end

#Demonstrates how to compute the FFT, the IFFT, and shows that the IFFT recovers the initial function
function example4()
	example4(4)
end

# Parameters:
#	N::Int
#	- the problem size
function example4(N::Int)

	YOR, PT = yor(N) #Creates Young's Orthogonal Representations and the Pointer Tree for Partition decomposistion
	SNF = 2 * rand(factorial(N)) #Create a random function on Sn
	FFT = sn_fft(N, SNF, YOR, PT) #Find the FFT
	iSNF = sn_ifft(N, FFT, YOR, PT) #Find the inverse FFT
	dif = SNF - iSNF #Find the error in the process

	P, WI = partitions(N) #Create the Partitions of 1:N
	Pn = P[N] #Get the partitions that act as the labels for the irreducibles

	println("Demonstrates how to compute the FFT, the IFFT, and shows that the IFFT recovers the initial function")
	println("")
	println("A random function on Sn: ")
	for i = 1:length(SNF)
		println(SNF[i])
	end
	println("")
	println("The Fourier Transform: ")
	for i = 1:length(FFT)
		println(partition_string(Pn[i]))
		println(round(FFT[i], 4))
		println("")
	end
	println("Maximum error in recovering the original function on Sn: ")
	println(maximum(abs(dif)))
	end

#Demonstrates how to compute a sparse FFT and shows that it produces the same results as the normal FFT
function example5()
	example5(4,0.8)
end

# Parameters:
#	N::Int
#	- the problem size
#	SC::Float64
#	- the portion of the function that is zero-valued
function example5(N::Int, SC::Float64)

	#create a sparse function on Sn
	SNF = rand(factorial(N))
	L = 0
	for(i = 1:length(SNF))
		if(SNF[i] < SC) #determine if the value will be zero
			SNF[i] = 0.0
		else #if non-zero, assign it a random value
			L += 1
			SNF[i] = 2 * rand()
		end
	end
	sSNF = Array(Float64, L)
	NZL = Array(Int, L)
	index = 1
	for i = 1:length(SNF)
		if SNF[i] != 0.0
			sSNF[index] = SNF[i]
			NZL[index] = i
			index += 1
		end
	end

	YOR, PT = yor(N) #Creates Young's Orthogonal Representations and the Pointer Tree for Partition decomposistion

	nFFT = sn_fft(N, SNF, YOR, PT) #Compute the FFT normally
	sFFT = sn_fft_sp(N, sSNF, NZL, YOR, PT) #Compute the sparse FFT

	P, WI = partitions(N) #Create the Partitions of 1:N
	Pn = P[N] #Get the partitions that act as the labels for the irreducibles

	println("Demonstrates how to compute a sparse FFT and shows that it produces the same results as the normal FFT")
	println("")
	println("A random sparse function on Sn")
	for i = 1:length(SNF)
		println(SNF[i])
	end
	println("")
	println("The sparse Fourier Transform: ")
	for i = 1:length(sFFT)
		println(partition_string(Pn[i]))
		println(round(sFFT[i], 4))
		println("")
	end
	println("Comparing the results of the two methods of computing the FFT using '==' evaluates to: ")
	println(sFFT == nFFT)
end

#Demonstrates the property of the FFT where the components of the FFT are the representations when the FFT is computed on a delta function
function example6()
	example6(5,[1,3,2,4,5])
end

# Parameters:
#	N::Int
#	- the problem size
#	Permutation::Array{Int, 1}
#	- a permutation of N
function example6(N::Int, Permutation::Array{Int, 1})

	YOR, PT = yor(N) #Creates Young's Orthogonal Representations and the Pointer Tree for Partition decomposistion
	YORn = YOR[N]

	#Create a delta function on Sn
	SNF = [1.0]
	NZL = [permutation_index(Permutation)]

	FFT = sn_fft_sp(N, SNF, NZL, YOR, PT) #Find the FFT, because delta functions are almost completely sparse the sparse FFT is used

	error = 0.0
	for i = 1:length(FFT)
		YORp = yor_permutation(Permutation, YORn[i]) #Find the representation for the selected permutation for this partition

		#Find the error
		error_p = maximum(abs(YORp - FFT[i]))
		if error_p > error
			error = error_p
		end
	end

	P, WI = partitions(N) #Create the Partitions of 1:N
	Pn = P[N] #Get the partitions that act as the labels for the irreducibles

	println("Demonstrates the property of the FFT where the components of the FFT are the representations when the FFT is computed on a delta function")
	println("")
	println("The permutation for which the funciton is non zero: ", permutation_string(Permutation))
	println("")
	println("The Fourier transform: ")
	for i = 1:length(FFT)
		println(partition_string(Pn[i]))
		println(round(FFT[i], 4))
		println("")
	end
	println("Maximum absolute difference between the Fourier components and the YOR representations for the permutation: ")
	println(error)
end

#Creates a random Band Limited funtion and returns both a compressed version (that FFT_BL() can use) and a full version (that FFT() can use)
#computing a bandlimited FFT and iFFT
function example7()
	example7(4,2)
end

# Parameters:
#	N::Int
#	- the problem size
#	K::Int
#	- the problem is homogenous at N-K
function example7(N::Int, K::Int)

	blSNF = 2 * rand(round(Int, factorial(N) / factorial(N - K)))
	nSNF = Array(Float64, factorial(N))
	index = 1
	for i = 1:length(blSNF)
		v = blSNF[i]
		for j = 1:factorial(N - K)
			nSNF[index] = v
			index += 1
		end
	end

	YOR, PT = yor(N)  #Creates Young's Orthogonal Representations and the Pointer Tree for Partition decomposistion
	nFFT = sn_fft(N, nSNF, YOR, PT) #compute the normal FFT

	YOR, PT, ZFI = yor_bl(N, K)  #Creates Young's Orthogonal Representations and the Pointer Tree for Partition decomposistion for bandlimited functions
	blFFT = sn_fft_bl(N, K, blSNF, YOR, PT, ZFI) #compute the band limited FFT

	iblSNF = sn_ifft_bl(N, K, blFFT, YOR, PT, ZFI) #compute the band limited iFFT

	P, ZFI, WI = partitions_bl(N,K) #Create the Partitions of 1:N
	Pn = P[N] #Get the partitions that act as the labels for the irreducibles

	error = 0.0
	shift = length(nFFT) - length(blFFT)
	for i = 1:length(blFFT)
		#Find the error
		error_p = maximum(abs(nFFT[i + shift] - blFFT[i]))
		if error_p > error
			error = error_p
		end
	end

	println("Creates a random Band Limited funtion and returns both a compressed version (that FFT_BL() can use) and a full version (that FFT() can use) computing a bandlimited FFT and iFFT")
	println("")
	println("The full representation of a bandlimited function on Sn:")
	for i = 1:length(nSNF)
		println(nSNF[i])
	end
	println("")
	println("The compressed representation of the same function used by SnFFT:")
	for i = 1:length(blSNF)
		println(blSNF[i])
	end
	println("")
	println("The band limited Fourier transform: ")
	for i = 1:length(blFFT)
		println(partition_string(Pn[i]))
		println(round(blFFT[i], 4))
		println("")
	end

	println("The maximum absolute difference between the full and bandlimited FFT: ")
	println(error)
	println("")
	println("The absolute error in recovering the band limited function on Sn from the inverse bandlimited FFT: ")
	println(maximum(abs(iblSNF - blSNF)))
end

#Calculating a partial iFFT
function example8()
	example8(4,3)
end

# Parameters:
#	N::Int
#	- the problem size
#	M::Int
#	- the number of the top frequencies of the Fourier transform to use
function example8(N::Int, M::Int)

	SNF = rand(factorial(N)) #create a random function on Sn
	YOR, PT = yor(N) #create YOR matrics and the Pointer Tree
	FFT = sn_fft(N, SNF, YOR, PT) #calculate the normal FFT (the sparse FFT also works)
	piSNF = sn_ifft_p(N, M, FFT, YOR, PT) #calculate the partial iFFT

	println("Demonstrates how to calculate a partial iFFT")
	println("")
	println("The original random function on Sn:")
	for i = 1:length(SNF)
		println(SNF[i])
	end
	println("")
	println("The recovered function from the partial iFFT considering the lowest ", M, " frequencies: ")
	for i = 1:length(piSNF)
		println(piSNF[i])
	end
end

#Uses sparcl (an R library) to perform sparse Hierarchical Clustering
#The output is saved into your home directory
function example_clustering()
	example_clustering(2,5,1.0,7)
end

# Parameters:
#	C::Int
#	- number of clusters
#	S::Int
#	- sizes of the clusters
#	F::Float64
#	- spreading factors for the distributions
#	N::Int
#	- the problem size
# Return Values:
# None - saves the data to CSV files and runs the clustering script
# Notes: For Julia V0.4
# - the TmStruct and related code can be replaced with "now()"
# - the appropriate version number needs to be used to define $loc
function example_clustering(C::Int,S::Int,F::Float64,N::Int)

	mkdir = "mkdir"
	Time = now()
	dir = string("ClusteringExample-",Time)
	R = "R"
	CMD = "CMD"
	BATCH = "BATCH"
	loc = string(homedir(),"/.julia/v0.4/SnFFT/src/sparcl_script.R")

	run(`$mkdir $dir`)
	cd(dir)
	clustering_setup(C,S,F,N)
	run(`$R $CMD $BATCH $loc`)
end

#########################################
# Functionality needed for the examples #
#########################################

###
# ClusteringExample
###

#Sets up the data for the clustering
function clustering_setup(C::Int64, S::Int, F::Float64, N::Int64)

	#Construct the distribution Centers
	Centers = Array(Array{Int64, 1}, C)
	for i = 1:C
		Centers[i] = sn_p(N)
		println(Centers[i])
	end

	#Construct the Distributions
	Permutations = Array(Array{Int64, 1}, C * S)
	Distributions = Array(Array{Float64, 1}, C * S)
	k = 1
	for i = 1:C
		center = Centers[i]
		for j = 1:S
			rt = sn_t(N)
			perm = sn_multiply(rt, center)
			Permutations[k] = perm
			Distributions[k] = mallowsdistribution(perm, F)
			println("Distribution ", k)
			k += 1
		end
	end

	#Calculate the Fourier Transforms
	FTA = Array(Array{Array{Float64, 2}, 1}, C * S)
	YOR, PT = yor(N)
	for i = 1:(C * S)
		FTA[i] = sn_fft(N, Distributions[i], YOR, PT)
		println("FFT ", i)
	end

	X = Array(Float64, C * S, factorial(N))
	for c = 1:(C * S)
		k = 1
		FT = FTA[c]
		for p = 1:length(FT)
			FC = FT[p]
			d = size(FC, 1)
			for i = 1:d
				for j = 1:d
					X[c, k] = FC[i,j]
					k += 1
				end
			end
		end
	end

	writecsv("centers.csv",Centers)
	writecsv("perms.csv",Permutations)
	writecsv("input-x.csv",X)
end

# Parameters:
#	P::Array{Int, 1}
#	- a permutation
#	Gamma::Float64
#	- the spreading factor
# Return Values:
#	MD::Array{Float64, 1}
#	- the mallows distribution with spreading factor Gamma centered at P
function mallowsdistribution(P::Array{Int64, 1}, Gamma::Float64)
	N = length(P)
	N_F = factorial(N)
	Q = preferencematrix(P)
	MD = Array(Float64, N_F)
	Gamma_neg = -1 * Gamma
	V = (1 - exp(Gamma_neg)) ^ N
	for j = 1:N
		V /= (1 - exp(j * Gamma_neg))
	end
	for i = 1:N_F
		Pi = index_permutation(N, i)
		Qi = preferencematrix(Pi) #Find the Preference Matrix of that permutation
		MD[i] = V * exp(Gamma_neg * kendalldistance(Qi, Q)) #Find the value of the Mallow Distribution for that permutation
	end
	return MD
end

# Parameters:
#	P::Array{Int, 1}
#	- a permutation
# Return Values:
#	Q::Array{Int, 2}
#	- the preference matrix for P
#	- Q[i,j] = 1 if and only if j precedes i in P
function preferencematrix(P::Array{Int64, 1})
	N = length(P)
	Q = zeros(Int64, N, N)
	for i = 1:N
		for j = 1:N
			p = P[j]
			if p == i
				break
			else
				Q[i, p] = 1
			end
		end
	end
	return Q
end

# Parameters:
#	Q1::Array{Int, 2}
#	- the preference matrix for the first permutation
#	Q2::Array{Int, 2}
#	- the preference matrix for the second permutation
# Return Values:
#	D::Int
#	- the Kendall Tau Distance between two permutations
#	- the the number of pairs (i, j) such that: P1[i] < P1[j] and P2[i] > P2[j]
function kendalldistance(Q1::Array{Int64, 2}, Q2::Array{Int64, 2})
	N = size(Q1, 1)
	D = 0
	for i = 1:N
		for j = 1:N
			D += abs(Q1[i,j] - Q2[i,j])
		end
	end
	D /= 2
	D = round(Int, D)
	return D
end

###
# Printing methods
###

# Parameters:
#	Permutation::Array{Int, 1}
#	- a permutation
# Return Values:
#	ST::String
#	- the string representation of permutation
function permutation_string(Permutation::Array{Int, 1})
	ST = "[ "
	for i = 1:length(Permutation)
		ST = string(ST, Permutation[i], " ")
	end
	ST = string(ST, "] ")
	return ST
end

# Parameters:
#	Partition::Array{Int, 1}
#	- a partition
# Return Values:
#	ST::String
#	- the string representation of partition
function partition_string(Partition::Array{Int, 1})
	ST = "( "
	for i = 1:length(Partition)
		ST = string(ST, Partition[i], " ")
	end
	ST = string(ST, ") ")
	return ST
end

# Parameters:
#	YS::Array{Int, 1}
#	- a yamanouchi symbol
# Return Values:
#	None - prints YS
function print_ys(YS::Array{Int, 1})
	N = length(YS)
	numRows = maximum(YS)
	ST = Array(AbstractString, numRows)
	IA = ones(Int, numRows)
	for i = 1:numRows
		ST[i] = ""
	end
	for n = 1:N
		row = YS[n]
		NI = IA[row]
		ST[row] = string(ST[row]," ", n)
		NI += 1
		IA[row] = NI
	end
	for i = 1:numRows
		println(ST[i])
	end
end


using SBML
using Catalyst
using EzXML
using MathML
using ModelingToolkit
using OrdinaryDiffEq
using Symbolics
using Test

DATA = joinpath(@__DIR__, "data")
SBML_FILE = joinpath(DATA, "case01.xml")
SBML_DOC = readxml(SBML_FILE)
SYSDICT = sbml_to_sysinfo(SBML_FILE)

t = Variable(:t)
A = Num(Variable(:A))
B = Num(Variable(:B))
k1 = Num(Sym{ModelingToolkit.Parameter{Real}}(:k1))
k2 = Num(Sym{ModelingToolkit.Parameter{Real}}(:k2))
ConvA_k2 = Num(Sym{ModelingToolkit.Parameter{Real}}(:ConvA_k2))
comp1 = Num(Sym{ModelingToolkit.Parameter{Real}}(:comp1))

# test build_par_map
trueparmap = [k1 => 0.8]
parmap = build_par_map(SYSDICT["listOfParameters"])
@test isequal(parmap, trueparmap)

# test build_comp_map
truecompmap = [comp1 => 1.0]
compmap = build_comp_map(SYSDICT["listOfCompartments"])
@test isequal(compmap, truecompmap)

# test build_spec_map
truespecmap = [A => (1.0, comp1),
               B => (0.0, comp1)]
specmap = build_spec_map(SYSDICT["listOfSpecies"])
@test isequal(specmap, truespecmap)

# test promote_local_parameters
doc = promotelocalparameters(readxml(SBML_FILE))
localpar = lastelement(lastelement(lastelement(lastelement(lastelement(firstelement(doc.root))))))
@test isequal(localpar["id"], "ConvA_k2")

# test getkineticlaw
reaction = firstelement(lastelement(firstelement(readxml(SBML_FILE).root)))
kineticlaw = getkineticlaw(reaction)
@test isequal(nodename(kineticlaw),"kineticLaw")

# test getmath
kineticlaw = lastelement(reaction)
math = getmath(kineticlaw)
@test isequal(nodename(math),"math")

# test _getlistofspeciesreference
reactants, r_stoich = SBML._getlistofspeciesreference(reaction,"Reactant")
products, p_stoich = SBML._getlistofspeciesreference(reaction,"Product")
@test isequal(reactants, [A])
@test isequal(r_stoich, [1])
@test isequal(products, [B])
@test isequal(p_stoich, [1])

# test build_reactions()
truesubs = Dict(Num(Variable(:comp1)) => Num(Sym{ModelingToolkit.Parameter{Real}}(:comp1)),
                Num(Variable(:k1)) => Num(Sym{ModelingToolkit.Parameter{Real}}(:k1)),
                Num(Variable(:k2)) => Num(Sym{ModelingToolkit.Parameter{Real}}(:k2)))
reactions = build_reactions([reaction],truesubs)
kineticlaw = comp1*(A*k1 - (B*k2))
truereactions = [(kineticlaw,[A],[B],[1],[1])]
@test isequal(reactions[1][1],truereactions[1][1])

# test _process_doc
model = SBML._process_doc(readxml(SBML_FILE))
@test isequal(model.parameters, [k1 => 0.8, ConvA_k2 => 0.6])
@test isequal(model.compartments, truecompmap)
@test isequal(model.species, truespecmap)
kineticlaw = comp1*(A*k1 - (B*ConvA_k2))
truereactions = [(kineticlaw,[A],[B],[1],[1])]
@test isequal(model.reactions, truereactions)

# test _mathml_substitutions
subs = SBML._mathml_substitutions(SYSDICT)
@test isequal(subs, truesubs)

# test _par_subs
truesubs = [Num(Variable(:k1)) => Num(Sym{ModelingToolkit.Parameter{Real}}(:k1))]
subs = SBML._par_subs(SYSDICT["listOfParameters"])
@test isequal(subs, truesubs)

# test _var_subs

# test ReactionSystem
model = SbmlModel([k1=>1.],[comp1=>1.],[A=>1.],[(comp1*k1*A, [A], nothing)])
rs = ReactionSystem(model)
@test isequal(rs.eqs, Reaction[Reaction(comp1*k1*A, [A], nothing; use_only_rate=true)])
@test isequal(rs.iv, t)
@test isequal(rs.states, [A])
@test isequal(rs.ps, [k1,comp1])

model = SbmlModel(SBML_DOC)
rs = ReactionSystem(model)
@test isequal(rs.eqs, Reaction[Reaction(comp1*(k1*A - (ConvA_k2*B)), [A], [B]; use_only_rate=true)])
@test isequal(rs.iv, t)
@test isequal(rs.states, [A, B])
@test isequal(rs.ps, [k1,ConvA_k2,comp1])

model = SbmlModel(SBML_FILE)
rs = ReactionSystem(model)
@test isequal(rs.eqs, Reaction[Reaction(comp1*(k1*A - (ConvA_k2*B)), [A], [B]; use_only_rate=true)])
@test isequal(rs.iv, t)
@test isequal(rs.states, [A, B])
@test isequal(rs.ps, [k1,ConvA_k2,comp1])

# test ODESystem
odesys = ODESystem(model)  # Todo: Add test cases
trueeqs = Equation[Differential(t)(A) ~ -comp1*(A*k1 - (B*ConvA_k2)), Differential(t)(B) ~ comp1*(A*k1 - (B*ConvA_k2))]
@test isequal(odesys.eqs, trueeqs)
@test isequal(odesys.iv, t)
@test isequal(odesys.states, [A, B])
# isequal(Sym{ModelingToolkit.Parameter{Real},Nothing}[k1, ConvA_k2, comp1, comp1], [k1, ConvA_k2, comp1])
@test isequal(odesys.ps, [k1, ConvA_k2, comp1])
u0 = [A => 1., B => 0.]
par = [k1 => 0.8, ConvA_k2 => 0.6, comp1 => 1.]
@test isequal(odesys.defaults, Dict(append!(u0, par)))

odesys = ODESystem(SBML_DOC)
e1 = odesys.eqs[1]
println(e1)
println(typeof(e1))
println(e1.lhs)
println(e1.rhs)
@test isequal(odesys.eqs, trueeqs)
@test isequal(odesys.iv, t)
@test isequal(odesys.states, [A, B])
# isequal(Sym{ModelingToolkit.Parameter{Real},Nothing}[k1, ConvA_k2, comp1, comp1], [k1, ConvA_k2, comp1])
@test isequal(odesys.ps, [k1, ConvA_k2, comp1])
u0 = [A => 1., B => 0.]
par = [k1 => 0.8, ConvA_k2 => 0.6, comp1 => 1.]
@test isequal(odesys.defaults, Dict(append!(u0, par)))

odesys = ODESystem(SBML_FILE)
@test isequal(odesys.eqs, trueeqs)
@test isequal(odesys.iv, t)
@test isequal(odesys.states, [A, B])
# isequal(Sym{ModelingToolkit.Parameter{Real},Nothing}[k1, ConvA_k2, comp1, comp1], [k1, ConvA_k2, comp1])
@test isequal(odesys.ps, [k1, ConvA_k2, comp1])
u0 = [A => 1., B => 0.]
par = [k1 => 0.8, ConvA_k2 => 0.6, comp1 => 1.]
@test isequal(odesys.defaults, Dict(append!(u0, par)))

# test ODEProblem
oprob = ODEProblem(model, [0., 10.])
sol = solve(oprob, Tsit5())
println("soln")
println(sol.u)
println(typeof(sol.u))
@test isapprox(sol.u[end], [0.42857284277223784, 0.5714271572277619])

@test_nowarn ODEProblem(SBML_DOC, [0., 10.])

@test_nowarn ODEProblem(SBML_FILE, [0., 10.])

# test _substitute_par!
doc = deepcopy(SBML_DOC)
kineticlaw = findfirst("//x:kineticLaw", doc.root, ["x"=>namespace(doc.root)])
SBML._substitute_par!(kineticlaw, "k2", "ConvA")
k1 = Num(Variable(:k1))
ConvA_k2 = Num(Variable(:ConvA_k2))
comp1 = Num(Variable(:comp1))
@test isequal(parse_node(firstelement(kineticlaw))[1], comp1*(k1*A - (ConvA_k2*B)))

#=DATA = "./test/data"
SBML_FILE = joinpath(DATA, "case01.xml")
SBML_DOC = readxml(SBML_FILE)
node = SBML_DOC.root
ns = namespace(node)
list_name = "listOfParameters"
list_name = "listOfReactions/x:reaction/x:kineticLaw/x:listOfParameters"
list_name = "listOfReactions/x:reaction/x:kineticLaw/x:listOfLocalParameters"
str = "/x:sbml/x:model/x:$(list_name)"
findall(str, node, ["x" => ns])

using ModelingToolkit
@parameters β γ t
@variables S(t) I(t) R(t)

rxs = [Reaction(β, [S,I], [I], [1,1], [2])
       Reaction(γ, [I], [R])]
rs  = ReactionSystem(rxs, t, [S,I,R], [β,γ])

u₀map    = [S => 999.0, I => 1.0, R => 0.0]
parammap = [β => 1/10000, γ => 0.01]
tspan    = (0.0, 250.0)

# solve as ODEs
odesys = ODESystem(rs; defaults=merge(u₀map,parammap))=#


# # Rotation of a gaussian distribution
#
#md # [`notebook`](https://@__NBVIEWER_ROOT_URL__notebooks/rotation_2d_bsl.ipynb)
#
# ```math
# \frac{df}{dt} +  (y \frac{df}{dx} - x \frac{df}{dy}) = 0
# ```
#
#

import Splittings: advection!, UniformMesh
import Splittings: @Magic, CubicSpline
using Plots

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

function with_bsl(tf::Float64, nt::Int)

   n1, n2 = 32, 64
   mesh1 = UniformMesh(-π, π, n1)
   mesh2 = UniformMesh(-π, π, n2)
   x = mesh1.points
   y = mesh2.points

   dt = tf/nt

   f = zeros(Float64,(n1,n2))

   for (i, xp) in enumerate(x), (j, yp) in enumerate(y)
       xn = cos(tf)*xp - sin(tf)*yp
       yn = sin(tf)*xp + cos(tf)*yp
       f[i,j] = exp(-(xn-1)*(xn-1)/0.2)*exp(-(yn-1)*(yn-1)/0.2)
   end

   anim = @animate for n=1:nt

	   @Magic(advection!( f,  mesh1,  y, dt, CubicSpline(), 1),
		    advection!( f,  mesh2, -x, dt, CubicSpline(), 2)
		   )

      surface(f)

   end

   gif(anim, "rotanim.gif", fps=15); nothing #hide

end

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

@time f = with_bsl( 2π, 6)

# ![](rotanim.gif)


# %% Interactive GALI psos for henon heiles
# color coded with lyapunov exponent
using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
include(srcdir("colorscheme.jl"))

using DynamicalSystems, InteractiveDynamics
using OrdinaryDiffEq
import GLMakie

diffeq = (alg = Vern9(), abstol = 1e-9, reltol = 1e-9)

hh = Systems.henonheiles()

potential(x, y) = 0.5(x^2 + y^2) + (x^2*y - (y^3)/3)
energy(x,y,px,py) = 0.5(px^2 + py^2) + potential(x,y)
E = energy(get_state(hh)...)

function complete(y, py, x)
    V = potential(x, y)
    Ky = 0.5*(py^2)
    Ky + V ≥ E && error("Point has more energy!")
    px = sqrt(2(E - V - Ky))
    ic = [x, y, px, py]
    return ic
end

plane = (1, 0.0) # first variable crossing 0

cmap = cgrad(:cividis)
function λcolor(u)
    λ = lyapunov(hh, 10000; u0 = u, diffeq...)
    λmax = 0.06
    v = clamp(λ/λmax, 0, 1)
    return cmap.colors[round(Int, v*255 + 1)]
    # RGBf0(v/2, 0, v)
end

fig, state = interactive_poincaresos(
    hh, plane, (2, 4), complete;
    labels = ("q₂" , "p₂"), color = λcolor, diffeq...
)

# %%
record_interaction(fig, string(@__FILE__)[1:end-2]*"mp4"; total_time = 10)


export RandomizedSystem, square_up

"""
    RandomizedSystem(F::Union{System,AbstractSystem}, k::Integer;
            identity_block = true,
            compile = $(COMPILE_DEFAULT[]))

Given a ``n × N`` system `F`` this constructs the system
``\\mathfrak{R}(F; k)(x) = A⋅F(x)`` where ``A`` is a ``k × N`` matrix.
If `identity_block = true` then the first `k` columns of ``A`` form an identity matrix.
The other entries are random complex numbers. See Chapter 13.5 in [^SW05] for more details.

    RandomizedSystem(F::Union{System,AbstractSystem}, A::Matrix{ComplexF64};
        identity_block = true,
        compile = $(COMPILE_DEFAULT[]))

Explicitly provide the used randomization matrix. If `identity = true` then `A` has to be `k × (n - k)` matrix.
Otherwise `A` has to be a `k × n` matrix..

[^SW05]: Sommese, A. J., & Wampler, C. W. (2005). The Numerical Solution of Systems of Polynomials Arising in Engineering and Science. World Scientific.
"""
struct RandomizedSystem{S<:AbstractSystem} <: AbstractSystem
    system::S
    A::Matrix{ComplexF64}
    identity_block::Bool
    u::Vector{ComplexF64}
    ū::Vector{ComplexDF64}
    U::Matrix{ComplexF64}
    taylor_u::TaylorVector{4,ComplexF64}
end

function RandomizedSystem(
    F::Union{AbstractSystem,System},
    k::Integer;
    compile::Union{Bool,Symbol} = COMPILE_DEFAULT[],
    identity_block = true,
)
    n, N = size(F)
    if identity_block
        A = randn(ComplexF64, k, n - k)
    else
        # Create a random matrix unit upper triangular
        A = [LA.UnitUpperTriangular(rand_unitary_matrix(k)) randn(ComplexF64, k, n - k)]
    end
    RandomizedSystem(F, A; identity_block = identity_block, compile = compile)
end

RandomizedSystem(
    F::System,
    A::Matrix{ComplexF64};
    compile::Union{Bool,Symbol} = COMPILE_DEFAULT[],
    kwargs...,
) = RandomizedSystem(fixed(F; compile = compile), A; kwargs...)

function RandomizedSystem(
    F::AbstractSystem,
    A::Matrix{ComplexF64};
    compile::Union{Bool,Symbol} = COMPILE_DEFAULT[],
    identity_block = true,
)
    n, N = size(F)
    is_compatible = identity_block ? size(A, 1) + size(A, 2) == n : size(A, 2) == n
    if !is_compatible
        throw(ArgumentError("Randomization matrix has incompatible size."))
    end

    u = zeros(ComplexF64, n)
    ū = zeros(ComplexDF64, n)
    U = zeros(ComplexF64, n, N)
    taylor_u = TaylorVector{4}(ComplexF64, n)
    RandomizedSystem(F, A, identity_block, u, ū, U, taylor_u)
end

function Base.show(io::IO, mime::MIME"text/plain", F::RandomizedSystem)
    println(io, typeof(F), ":")
    println(io, "A:")
    show(io, mime, F.A)
    println(io, "\n\nF:")
    show(io, mime, F.system)
end

Base.size(F::RandomizedSystem) = (size(F.A, 1), last(size(F.system)))
ModelKit.variables(F::RandomizedSystem) = variables(F.system)
ModelKit.parameters(F::RandomizedSystem) = parameters(F.system)
ModelKit.variable_groups(F::RandomizedSystem) = variable_groups(F.system)

"""
    square_up(F::Union{System, AbstractSystem}; identity_block = true, compile = compile = $(COMPILE_DEFAULT[]))

Creates the [`RandomizedSystem`](@ref) ``\\mathfrak{R}(F(x); N)`` where ``N`` is the number
of variables of `F`.
"""
square_up(F::Union{AbstractSystem,System}; kwargs...) =
    RandomizedSystem(F, last(size(F)); kwargs...)

@inline function randomize!(u, A, v::AbstractVector; identity_block::Bool)
    n, m = size(A, 1), length(v)

    if identity_block
        for i = 1:n
            u[i] = v[i]
        end
        for j = 1:(m-n)
            vnj = v[n+j]
            for i = 1:n
                u[i] += A[i, j] * vnj
            end
        end
    else
        for i = 1:n
            u[i] = 0
        end
        for j = 1:m, i = 1:n
            u[i] += A[i, j] * v[j]
        end
    end

    u
end

@inline function randomize!(
    u::Union{AbstractMatrix,TaylorVector},
    A,
    v::Union{AbstractMatrix,TaylorVector},
    ncols::Int = size(A, 1);
    identity_block::Bool,
)
    n, m = size(A, 1), size(v, 1)

    if identity_block
        for j = 1:ncols, i = 1:n
            u[i, j] = v[i, j]
        end
        for j = 1:ncols, k = 1:(m-n), i = 1:n
            u[i, j] += A[i, k] * v[n+k, j]
        end
    else
        for j = 1:ncols, i = 1:n
            u[i, j] = 0
        end
        for j = 1:ncols, k = 1:m, i = 1:n
            u[i, j] += A[i, k] * v[k, j]
        end
    end

    u
end

function (F::RandomizedSystem)(x, p = nothing)
    if F.identity_block
        [LA.I F.A] * F.system(x, p)
    else
        F.A * F.system(x, p)
    end
end

function ModelKit.evaluate!(u, F::RandomizedSystem, x::Vector{ComplexDF64}, p = nothing)
    evaluate!(F.ū, F.system, x, p)
    randomize!(u, F.A, F.ū; identity_block = F.identity_block)
    u
end
function ModelKit.evaluate!(u, F::RandomizedSystem, x, p = nothing)
    evaluate!(F.u, F.system, x, p)
    randomize!(u, F.A, F.u; identity_block = F.identity_block)
    u
end

function ModelKit.evaluate_and_jacobian!(u, U, F::RandomizedSystem, x, p = nothing)
    evaluate_and_jacobian!(F.u, F.U, F.system, x, p)
    randomize!(u, F.A, F.u; identity_block = F.identity_block)
    randomize!(U, F.A, F.U; identity_block = F.identity_block)
end

function ModelKit.taylor!(
    u::AbstractVector,
    v::Val{N},
    F::RandomizedSystem,
    tx,
    p = nothing,
) where {N}
    taylor!(F.u, v, F.system, tx, p)
    randomize!(u, F.A, F.u; identity_block = F.identity_block)
end
function ModelKit.taylor!(
    u::TaylorVector,
    v::Val{N},
    F::RandomizedSystem,
    tx,
    p = nothing,
) where {N}
    taylor!(F.taylor_u, v, F.system, tx, p)
    randomize!(u, F.A, F.taylor_u, N + 1; identity_block = F.identity_block)
end


@testset "Norms" begin
    x = [2.0im, 3.0 - 1im, 5.0 + 2.0im]
    y = [-2.0im, 3.0 - 1im, 5.0 + 2.0im]
    norm = HC2.InfNorm()
    @test norm isa HC2.AbstractNorm
    @test norm(x) ≈ abs(5.0 + 2.0im)
    @test norm(x, x) ≈ 0.0
    @test norm(x, y) ≈ abs(4im)

    # test overflow check
    huge_x = exp2(700) .* x
    huge_y = exp2(700) .* y
    @test norm(huge_x) ≈ exp2(700) * abs(5.0 + 2.0im)
    @test norm(huge_x, huge_x) ≈ 0.0
    @test norm(huge_x, huge_y) ≈ exp2(700) * abs(4im)

    weighted_norm = HC2.WeightedNorm(norm, x)
    @test HC2.weights(weighted_norm) == ones(3)
    weighted_norm .= 2.0
    @test HC2.weights(weighted_norm) == [2.0, 2, 2]
    weighted_norm[1] = 4.0
    @test weighted_norm isa HC2.AbstractNorm
    @test weighted_norm(x) ≈ 0.5 * abs(5 + 2.0im)
    @test weighted_norm(x, x) ≈ 0.0
    @test weighted_norm(x, y) ≈ 0.25 * abs(4im)
    @test !isempty(sprint(show, weighted_norm))

    weighted_norm = HC2.WeightedNorm(norm, huge_x)
    weighted_norm .= 2.0
    weighted_norm[1] = 4.0
    @test weighted_norm isa HC2.AbstractNorm
    @test weighted_norm(huge_x) ≈ exp2(700) * 0.5 * abs(5 + 2.0im)
    @test weighted_norm(huge_x, huge_x) ≈ exp2(700) * 0.0
    @test weighted_norm(huge_x, huge_y) ≈ exp2(700) * 0.25 * abs(4im)
end


"""
    mean(A::AbstractArray, δ::Real, Estimator::EmpiricalMean)

The usual empirical mean estimator, for convenience, we authorize the extra dummy argument δ.
"""
function mean(A::AbstractArray, δ::Real, Estimator::EmpiricalMean)
    return mean(A)
end

k_mom(δ) = ceil(Int, 8log(1 / δ))
"""
    mean(A::AbstractArray, Estimator::MoM)

"""
function mean(A::AbstractArray, δ::Real, Estimator::MedianOfMean)
    k = k_mom(δ)
    return mean(A, MedianOfMean(k))
end

"""
    TrimmedMean(z, ε)

"""
function TrimMean(z, ε)
    n2 = length(z)
    @assert ε < 1 "ε > 1: you cannot remove more samples than there is"
    @assert iseven(n2)
    n = n2 ÷ 2
    y = z[1:n]
    x = z[n+1:n2]
    sort!(y)
    i_min = ceil(Int, ε * n)
    i_max = n - floor(Int, ε * n)
    α = y[i_min]
    β = y[i_max]
    return TrimMean(x, α, β)
end
ε_trimmed(δ, n) = 32log(8 / δ) / (3n)

"""
     mean(A::AbstractArray, Estimator::TrimmedMean)

"""
function mean(A::AbstractArray, δ::Real, Estimator::TrimmedMean)
    ε = ε_trimmed(δ, length(A))
    return TrimMean(A, ε)
end

struct Catoni{F} <: RobustMean
    σ::F
end
α_Catoni(δ, n, σ) = sqrt((2log(2 / δ)) / (n * (1 + (2log(2 / δ)) / (n - 2log(2 / δ))))) / σ
"""
   mean(A::AbstractArray, Estimator::Catoni, kwargs...)

Reference: Catoni
"""
function mean(A::AbstractArray, δ::Real, Estimator::Catoni, kwargs...)
    n = length(A)
    α = α_Catoni(δ, n, Estimator.σ)
    z = Z_Estimator(α, ψ_Catoni)
    return mean(A, z; kwargs...)
end

struct Huber{F} <: RobustMean
    σ::F
end
α_Huber(δ, n, σ) = sqrt(2log(4 / δ) / n) / σ #sqrt(2 * (log(2 / δ) - log(1 - exp(-n / 2) / δ)) / n) / σ
"""
   mean(A::AbstractArray, Estimator::Huber, kwargs...)

Reference: Huber
"""
function mean(A::AbstractArray, δ::Real, Estimator::Huber, kwargs...)
    n = length(A)
    α = α_Huber(δ, n, Estimator.σ)
    z = Z_Estimator(α, ψ_Huber)
    mean(A, z; kwargs...)
end

"""
    LeeVal(x, δ; α₀ = 0.0)

Reference: Optimal Sub-Gaussian Mean Estimation in R by Lee et Valiant
"""
function LeeVal(x, δ; α₀=0.0)
    κ = MoM(x, ceil(Int, log(1 / δ)))
    f(α) = sum(min(α * (x[i] - κ)^2, 1) for i in eachindex(x)) - 1 / 3 * log(1 / δ)
    α = find_zero(f, α₀)
    return κ + mean((x[i] - κ) * (1 - min(α * (x[i] - κ)^2, 1)) for i in eachindex(x))
end
struct LeeValiant <: RobustMean end

"""
    mean(A::AbstractArray, Estimator::δLeeValiant; kwargs...)

Reference: Optimal Sub-Gaussian Mean Estimation in R by Lee et Valiant
"""
function mean(A::AbstractArray, δ::Real, Estimator::LeeValiant; kwargs...)
    return LeeVal(A, δ; kwargs...)
end

"""
    MinskerNdaoud(x, k, p)

Reference: Robust and efficient mean estimation: an approach based on the properties of self-normalized sums
"""
function MinNda(x, k, p)
    n = length(x)
    @assert n % k == 0 "Check that n=$n is a mutliple of k=$k"
    m = n ÷ k
    μ̄ = [mean(@view(x[(1+(l-1)*m):(l*m)])) for l in 1:k]
    σ̂p = [stdm(@view(x[(1+(l-1)*m):(l*m)]), μ̄[l], corrected=false) for l in 1:k] .^ p
    return mean(μ̄ ./ σ̂p) * harmmean(σ̂p)
end
k_mn(δ) = ceil(Int, -log(δ / 3))
struct MinskerNdaoud{T} <: RobustMean
    p::T
end

"""
    mean(A::AbstractArray, Estimator::δMinskerNdaoud; kwargs...)

Reference: Robust and efficient mean estimation: an approach based on the properties of self-normalized sums
"""
function mean(A::AbstractArray, δ::Real, Estimator::MinskerNdaoud; kwargs...)
    k = k_mn(δ)
    return MinNda(A, k, Estimator.p; kwargs...)
end


# Portions translated from SLICOT-Reference distribution
# Copyright (c) 2002-2020 NICONET e.V.
function run_mb05md(datfile, io=stdout)
    NIN = 5
    NOUT = 6
    NMAX = 20
    LDA = NMAX
    LDV = NMAX
    LDY = NMAX
    LDWORK = 4*NMAX
    A = Array{Float64,2}(undef, LDA,NMAX)
    V = Array{Float64,2}(undef, LDV,NMAX)
    Y = Array{Float64,2}(undef, LDY,NMAX)
    VALR = Array{Float64,1}(undef, NMAX)
    VALI = Array{Float64,1}(undef, NMAX)
    DWORK = Array{Float64,1}(undef, LDWORK)
    IWORK = Array{BlasInt,1}(undef, NMAX)
    f = open(datfile,"r")
    readline(f)
    BALANC = 'N'
    vs = split(readline(f))
    N = parse(BlasInt, vs[1])
    DELTA = parse(Float64, replace(vs[2],'D'=>'E'))
    if ( N<=0 || N>NMAX )
        @error "Illegal N=$N"
    end

    vs = String[]
    _isz,_jsz = (N,N)
    while length(vs) < _isz*_jsz
        append!(vs, replace.(split(readline(f)),'D'=>'E'))
    end
    for i in 1:_isz
       _i0 = (i-1)*_jsz
       A[i,1:_jsz] .= parsex.(Float64, vs[_i0+1:_i0+_jsz])
    end
    close(f)
    # interp call 1

    INFO = SLICOT.mb05md!(BALANC, N, DELTA, A, V, Y, VALR, VALI, LDWORK)
    @test INFO == 0
    INFO == 0 || return

# interp output 1
    println(io, "A:")
    _nc = N
    _nr = N
    show(io, "text/plain", A[1:_nr,1:_nc])
    println(io)

# unable to translate write loop:
#  write (I), VALI(I), I = 1,N
# interp output 2
    println(io, "eigvals:")
    show(io, "text/plain", VALR[1:N]+im*VALI[1:N])
    println(io)

# interp output 3
    println(io, "V:")
    _nc = N
    _nr = N
    show(io, "text/plain", V[1:_nr,1:_nc])
    println(io)

# interp output 4
    println(io, "Y:")
    _nc = N
    _nr = N
    show(io, "text/plain", Y[1:_nr,1:_nc])
    println(io)

end # run_mb05md()


using Test
using Random
using Base.Order

using BinaryHeaps
using BinaryHeaps:
    walk,
    heapparent, heapleft, heapright,
    heapify!, bubble!,
    PermutationTracker,
    reduce_subtrees!, Reducer!

function isheap(v,order)
    n = length(v)

    # Nodes with two children
    for i = 1:heapparent(n-1)
        if lt(order, v[heapleft(i)], v[i]) ||
           lt(order, v[heapright(i)], v[i])
            return false
        end
    end

    # Node with one child
    for i = heapparent(n+1) : heapparent(n)
        if lt(order, v[heapleft(i)], v[i])
            return false
        end
    end

    return true
end

macro return_if_false(expr)
    return quote
        if !$(esc(expr))
            return false
        end
    end
end

function isreduction(v,r,reduce)
    n = length(v)
    if n == 0; return true; end

    # Check no-child nodes
    for i = n:-1:heapparent(n)+1
        @return_if_false r[i] == reduce(v[i])
    end

    # Check one-child node, if present
    for i = heapparent(n+1) : heapparent(n)
        @return_if_false r[i] == reduce(v[i],r[n])
    end

    # Reduce two-children nodes
    for i = heapparent(n)-1:-1:1
        @return_if_false r[i] == reduce(v[i], r[heapleft(i)], r[heapright(i)])
    end

    return true
end

include("walk.jl")
include("binary_heaps.jl")
include("permutations.jl")
include("reduction.jl")

include("MutableBinaryHeapsWithReduction.jl")


using Distributions

mutable struct RandomWalkAsset <: AbstractAsset
    last_rate::Float64

    # Random Walk Parameters
    α::Float64 # Drift
    ϕ::Float64 # Phi of the random walk (1=stationary)
    σ::Float64 # Standard deviation of a step

    name::String
end

mutable struct FixedAsset <: AbstractAsset
    last_rate::Float64

    rate::Float64

    name::String
    function FixedAsset(rate::Float64; name::String="asset")
        new(NaN, rate, name) # , Array{BidLimitOrder, 1}()
    end
    function FixedAsset(name::String="asset")
        new(NaN, 0.0, name) # , Array{BidLimitOrder, 1}()
    end
end

"Get cashflow of the RandomWalkAsset.
The cashflow would be interest in case of currency
and dividend in case of stock"
function get_rate(asset::RandomWalkAsset)
    return get_random_walk_step(
        asset.α,
        asset.ϕ,
        asset.σ,
        asset.last_interest
    )
end

function get_random_walk_step(α::Float64, ϕ::Float64, σ::Float64, x_prev::Float64)
    μ = 0
    u = rand(Normal(μ, σ))
    return α + ϕ * x_prev + u
end

function get_rate(asset::FixedAsset)
    return asset.rate
end


## Example script for constant-strength doublet-source panel method
using AeroMDAO

##
alpha_u = [0.2, 0.3, 0.2, 0.15, 0.2]
alpha_l = [-0.2, -0.1, -0.1, -0.001]
dzs     = (0., 0.)
airfoil = (Foil ∘ kulfan_CST)(alpha_u, alpha_l, dzs, 0.0, 60);      # Kulfan CST airfoil
# airfoil = Foil(naca4((0,0,1,2), 100; sharp_trailing_edge = true)) # NACA 4-digit airfoil
uniform = Uniform2D(1., 0.)
@time cl, cls, cms, cps, panels = solve_case(airfoil,
                                             uniform;
                                             num_panels = 80)

# Printing
println("Cl: $cl")
println("Σᵢ Clᵢ: $(sum(cls))")
println("Σᵢ Cmᵢ: $(sum(cms))")

## Plotting libraries
using Plots
plotlyjs();

## Pressure coefficients
plot((first ∘ collocation_point).(panels[1:end-1]), cps, marker = 2, label = :none, yflip = true, xlabel = "(x/c)", ylabel = "Cp")

## Lift distribution
plot((first ∘ collocation_point).(panels[1:end-1]), cls, marker = 2, label = :none, xlabel = "(x/c)", ylabel = "Cl")

## Angle of attack sweep
function alpha_sweep(α, airfoil)
    uniform = Uniform2D(1.0, α)
    solve_case(airfoil, uniform, num_panels = 80)[1]
end

αs = 0:10
cls = @time alpha_sweep.(αs, Ref(airfoil))


module MatrixNetworks

using DataStructures
using SparseArrays
using LinearAlgebra
using Printf
using Arpack
using DelimitedFiles
using Random
using Statistics
using KahanSummation

"""
Module ``MatrixNetworks``: Documentation on the module

- Option 1: start with a sparse matrix A:
- example: ``M = MatrixNetwork(A)``
- Option 2: start with row and column indexes for the nonzeros in the matrix
- example: ``M = MatrixNetwork(ei,ej)``

Some available functions: (use ?function_name to get more documentation)
- bfs
- bipartite_matching
- clustercoeffs
- corenums
- cosineknn
- csr_to_sparse
- dfs
- biconnected_components
- dijkstra
- dirclustercoeffs
- floydwarshall
- largest_component
- mst_prim
- scomponents
- sparse_to_csr
- pagerank
- erdos_renyi_undirected
- biconnected_components
- triangles

You can check the readme file here: \n
"https://github.com/nassarhuda/MatrixNetworks.jl/blob/master/README.md"
"""
MatrixNetworks

include("MatrixNetwork.jl")
export MatrixNetwork, sparse_transpose,
    is_undirected, is_connected, is_empty, empty_graph,
    random_edge, undirected_edges, directed_edges

include("scomponents.jl")
include("csr_to_sparse.jl")
include("sparse_to_csr.jl")
include("bipartite_matching.jl")
include("bfs.jl")
include("dfs.jl")
include("clustercoeffs.jl")
include("corenums.jl")
include("floydwarshall.jl")

include("manage_data.jl")
export load_matrix_network, load_matrix_network_metadata, load_matrix_network_all,
    matrix_network_datasets
include("largest_component.jl")
include("cosineknn.jl")
include("dirclustercoeffs.jl")
include("dijkstra.jl")
include("mst_prim.jl")

include("spectral.jl")
export fiedler_vector, sweepcut, spectral_cut, bestset, SweepcutProfile

# export everything to make them accessible as functions
export bipartite_matching, edge_list, create_sparse,
bipartite_matching_setup, bipartite_matching_indicator, bfs,
dfs, clustercoeffs, corenums, scomponents, strong_components_map,
enrich, csr_to_sparse, floydwarshall, largest_component,
sparse_to_csr, cosineknn, dirclustercoeffs, dijkstra, mst_prim, mst_prim_matrix,
csr_to_sparse_matrix, edge_indicator

include("diffusions.jl")
export pagerank, pagerank_power!, personalized_pagerank, seeded_pagerank, stochastic_mult!,
        seeded_stochastic_heat_kernel, stochastic_heat_kernel_series!

include("generators.jl")
export erdos_renyi_undirected, erdos_renyi_directed,
    erdős_rényi_undirected, erdős_rényi_directed,
    chung_lu_undirected, is_graphical_sequence, havel_hakimi_graph,
    pa_graph, preferential_attachment_graph,
    pa_edges!, preferential_attachment_edges!,
    gpa_graph, generalized_preferential_attachment_graph,
    gpa_edges!, generalized_preferential_attachment_edges!,
    roach_graph, lollipop_graph

include("biconnected.jl")
export biconnected_components, biconnected_components!

include("triangles.jl")
export unzip_triangles,triangles

end # end module


# binomial.jl
# Binomial tests
#
# Copyright (C) 2013   Simon Kornblith
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

export BinomialTest, SignTest

## BINOMIAL TEST

immutable BinomialTest <: HypothesisTest
    p::Float64
    x::Int
    n::Int

    BinomialTest(x::Real, n::Real, p::Real=0.5) = new(p, x, n)
end

BinomialTest(x::AbstractVector{Bool}, p=0.5) =
    BinomialTest(sum(x), length(x), p)

"""
```julia
testname(::HypothesisTest)
```

Returns the string value. E.g. "Binomial test", "Sign Test"
"""
testname(::BinomialTest) = "Binomial test"
population_param_of_interest(x::BinomialTest) = ("Probability of success", x.p, x.x/x.n) # parameter of interest: name, value under h0, point estimate

function show_params(io::IO, x::BinomialTest, ident="")
    println(io, ident, "number of observations: $(x.n)")
    println(io, ident, "number of successes:    $(x.x)")
end

pvalue(x::BinomialTest; tail=:both) = pvalue(Binomial(x.n, x.p), x.x; tail=tail)

# Confidence interval

"""
```julia
function ci(x::HypothesisTest, alpha::Float64=0.05; tail=:both, method=:clopper_pearson)
```
Compute a confidence interval with coverage 1-alpha for multinomial proportions using one of the following methods. Possible values for method are:

Sison, Glaz intervals :sison_glaz (default):
Bootstrap intervals :bootstrap :
Quesenberry, Hurst intervals :quesenberry_hurst :
Gold intervals :gold (Asymptotic simultaneous intervals):
"""
function ci(x::BinomialTest, alpha::Float64=0.05; tail=:both, method=:clopper_pearson)
    check_alpha(alpha)

    if tail == :left
        (0.0, ci(x, alpha*2, method=method)[2])
    elseif tail == :right
        (ci(x, alpha*2, method=method)[1], 1.0)
    elseif tail == :both
        if method == :clopper_pearson
            ci_clopper_pearson(x, alpha)
        elseif method == :wald
            ci_wald(x, alpha)
        elseif method == :wilson
            ci_wilson(x, alpha)
        elseif method == :jeffrey
            ci_jeffrey(x, alpha)
        elseif method == :agresti_coull
            ci_agresti_coull(x, alpha)
        else
            throw(ArgumentError("method=$(method) is not implemented yet"))
        end
    else
        throw(ArgumentError("tail=$(tail) is invalid"))
    end
end

# Clopper-Pearson interval (confidence interval by inversion)
function ci_clopper_pearson(x::BinomialTest, alpha::Float64=0.05)
    (quantile(Beta(x.x, x.n - x.x + 1), alpha/2), quantile(Beta(x.x + 1, x.n - x.x), 1-alpha/2))
end

# Wald interval / normal approximation interval
function ci_wald(x::BinomialTest, alpha::Float64=0.05)
    μ = x.x / x.n
    σ = sqrt(μ*(1-μ)/x.n)
    (quantile(Normal(μ, σ), alpha/2), quantile(Normal(μ, σ), 1-alpha/2))
end

# Jeffreys interval
function ci_jeffrey(x::BinomialTest, alpha::Float64=0.05)
    (quantile(Beta(x.x + 1/2, x.n - x.x + 1/2), alpha/2), quantile(Beta(x.x + 1/2, x.n - x.x + 1/2), 1-alpha/2))
end

# Agresti-Coull interval
function ci_agresti_coull(x::BinomialTest, alpha::Float64=0.05)
    q = quantile(Normal(), 1-alpha/2)
    n = x.n + q^2
    μ = (x.x + q^2/2)/n
    σ = sqrt(μ*(1-μ)/n)
    (μ-q*σ, μ+q*σ)
end

# Wilson score interval
function ci_wilson(x::BinomialTest, alpha::Float64=0.05)
    q = quantile(Normal(), 1-alpha/2)
    p = x.x / x.n
    denominator = 1 + q^2/x.n
    μ = p + q^2/(2*x.n)
    μ /= denominator
    σ = sqrt(p*(1-p)/x.n + q^2/(4x.n^2))
    σ /= denominator
    (μ-q*σ, μ+q*σ)
end

## SIGN TEST

immutable SignTest <: HypothesisTest
    median::Float64
    x::Int
    n::Int
    data
end

SignTest{T<:Real}(x::AbstractVector{T}, median::Real=0) =
    SignTest(median, sum(x .> median), sum(x .!= median), sort(x))
SignTest{T<:Real, S<:Real}(x::AbstractVector{T}, y::AbstractVector{S}) = SignTest(x - y, 0.0)

testname(::SignTest) = "Sign Test"
population_param_of_interest(x::SignTest) = ("Median", x.median, median(x.data)) # parameter of interest: name, value under h0, point estimate

function show_params(io::IO, x::SignTest, ident="")
    text1 = "number of observations:"
    text2 = "observations larger than $(x.median): "
    maxlen = length(text2)

    println(io, ident, text1, repeat(" ", maxlen-length(text1)), x.n)
    println(io, ident, text2, x.x)
end

"""
```julia
pvalue(x::HypothesisTest; tail=:both)
```

Compute the p-value for a given significance test.

If tail is :both (default), then the p-value for the two-sided test is returned. If tail is :left or :right, then a one-sided test is performed.
"""
pvalue(x::SignTest; tail=:both) = pvalue(Binomial(x.n, 0.5), x.x; tail=tail)

"""
```julia
function ci(x::HypothesisTest, alpha::Float64=0.05; tail=:both)
```

Compute a confidence interval C with coverage 1-alpha.

If tail is :both (default), then a two-sided confidence interval is returned. If tail is :left or :right, then a one-sided confidence interval is returned
"""
@compat function ci(x::SignTest, alpha::Float64=0.05; tail=:both)
    check_alpha(alpha)

    if tail == :left
        q = Int(quantile(Binomial(x.n, 0.5), alpha))
        (x.data[q+1], x.median)
    elseif tail == :right
        q = Int(quantile(Binomial(x.n, 0.5), alpha))
        (x.median, x.data[end-q])
    elseif tail == :both
        q = Int(quantile(Binomial(x.n, 0.5), alpha/2))
        (x.data[q+1], x.data[end-q])
    else
        throw(ArgumentError("tail=$(tail) is invalid"))
    end
end


#using Base.Cartesian

"""
    auc(scores, events, increasing = true)

compute the Area Under the receiver operator Curve (AUC), given some output `scores` array and some ground truth (`events`).
By default, it is assumed, that the `scores` are ordered increasingly (`increasing = true`), i.e. high scores represent events.
AUC is computed according to Fawcett, T. (2006). An introduction to ROC analysis. Pattern Recognition Letters, 27(8), 861–874. http://doi.org/10.1016/j.patrec.2005.10.010

# Examples
```
julia> scores = rand(10, 2)
julia> events = rand(0:1, 10, 2)
julia> auc(scores, events)
julia> auc(scores, boolevents(events))
```
"""
function auc(scores,events; increasing::Bool = true)
    s = sortperm(reshape(scores,length(scores)),rev=increasing);
    length(scores) == length(events) || error("Scores and events must have same number of elements")
    f=scores[s]
    L=events[s]
    fp=0
    tp=0
    fpprev=0
    tpprev=0
    A=0.0
    fprev=-Inf
    P=sum(L)
    N=length(L)-P
    for i=1:length(L)
        if f[i]!=fprev
            A+=trap_area(fp,fpprev,tp,tpprev)
            @inbounds fprev=f[i]
            fpprev=fp
            tpprev=tp
        end
        if isextreme(L[i])
            tp+=1
        else
            fp+=1
        end
    end
    A+=trap_area(N,fpprev,P,tpprev)
    A=A/(P*N)
end

function trap_area(x1,x2,y1,y2)
    b=abs(x1-x2)
    h=0.5*(y1+y2)
    return b*h
end

isextreme(l::Bool)=l
isextreme(l::Integer)=l>0

###################################
# end


"""
Presolve is a key to accelerate the linear programming solution.

TODO: Need to check and implement the algorithms in
Andersen and Andersen. Presolving in linear programming. 1995
"""
function presolve(lp::MatOI.LPForm{T, AT, VT}) where {T, AT, VT}
    @timeit TO "row reduction" begin
        tmp_A = lp.A
        to_keep = collect(1:nrows(lp))
        # Remove empty rows
        non_empty = get_empty_rows(tmp_A)
        if length(non_empty) < nrows(lp)
            println("Presolve removed $(nrows(lp)-length(non_empty)) empty rows.")
            tmp_A = tmp_A[non_empty, :]
            to_keep = non_empty
        end
        # Remove dependent rows
        dep_rows = get_dependent_rows(tmp_A)
        if length(dep_rows) > 0
            indeprows = [i for i=1:size(tmp_A, 1) if !in(i,dep_rows)]
            println("Presolve reduced $(length(dep_rows)) rows.")
            to_keep = to_keep[indeprows]
            tmp_A = tmp_A[indeprows, :]
        end

        # @show size(lp.A)
    end
    return MatOI.LPForm{T, AT, VT}(
        lp.direction,
        lp.c,
        sparse(tmp_A),
        lp.c_lb[to_keep],
        lp.c_ub[to_keep],
        lp.v_lb,
        lp.v_ub,
    )
end

function get_empty_rows(A::AbstractArray{T, 2}) where T
    nonempty = Int[]
    m = size(A, 1)
    sizehint!(nonempty, m)

    for i = 1:m
        if norm(A[i,:]) > 1e-10
            push!(nonempty, i)
        end
    end

    return nonempty
end

"""
Eliminate linearly dependent rows

The implementation is based on
Chvatal. Linear programming. 1981

The computational efficiency can be improved by
Andersen. Finding all linearly dependent rows in large-scale linear programming. 1995
"""
function get_dependent_rows(A::AbstractArray{T, 2}) where T
    m, n = size(A)

    # Append identity matrix to the last
    A2 = [A I]

    basis = collect((1+n):(m+n))
    # e_i = sparsevec([1],[1.0],m)
    e_i = Array{Float64}(undef, m)
    pi = Array{Float64}(undef, m)

    depind = Int[]
    sizehint!(depind, m)

    for i in 1:m
        # initialize compute components
        # e_i.nzind[1] = i
        fill!(e_i, 0)
        e_i[i] = 1

        # compute pi
        pi .= A2'[basis,:] \ e_i

        is_dependent = true
        for j = 1:n
            # @show A2[:,j]
            if !in(j, basis)
                view_A2_j = @view A2[:,j]
                if abs(view_A2_j' * pi) > 1e-7
                    basis[i] = j
                    # println("Pivot: row $i column $j")
                    is_dependent = false
                    break
                end
            end
        end
        if is_dependent
            # println("Row $i is dependent.")
            push!(depind, i)
        end
    end
    # @show basis[basis .> n]
    # @show basis[basis .> n] .- n

    return depind
end

# function remove_dependent_rows!(lp::StandardLpData{CuArray})
#     m, n = size(lp.A)

#     # Append identity matrix to the last
#     A2 = [lp.A CuMatrix(Matrix(I,m,m))]

#     basis = collect((1+n):(m+n))
#     e_i = CuArray{Float64}(undef, m)
#     pi = CuArray{Float64}(undef, m)

#     depind = Int[]
#     sizehint!(depind, m)

#     for i in 1:m
#         # initialize compute components
#         fill!(e_i, 0)
#         e_i[i] = 1

#         # compute pi
#         pi .= A2'[basis,:] \ e_i

#         is_dependent = true
#         for j = 1:n
#             # @show A2[:,j]
#             if !in(j, basis)
#                 view_A2_j = @view A2[:,j]
#                 if abs(view_A2_j' * pi) > 1e-7
#                     basis[i] = j
#                     # println("Pivot: row $i column $j")
#                     is_dependent = false
#                     break
#                 end
#             end
#         end
#         if is_dependent
#             # println("Row $i is dependent.")
#             push!(depind, i)
#         end
#     end
#     # @show basis[basis .> n]
#     # @show basis[basis .> n] .- n

#     if length(depind) > 0
#         deprows = [i for i=1:m if !in(i,depind)]
#         lp.A = lp.A[deprows,:]
#         lp.bl = lp.bl[deprows]
#         lp.bu = lp.bu[deprows]
#         lp.rd = lp.rd[deprows]
#         nrows(lp) -= length(depind)
#         println("Presolve reduced $(length(depind)) rows.")
#     end
# end

"""
Scaling algorithm from Implementation of the Simplex Method, Ping-Qi Pan (2014)
"""
function scaling!(lp::MatOI.AbstractLPForm)
    maxrounds = 50
    round = 1
    while round <= maxrounds
        aratio = matrix_coefficient_ratio(lp.A)
        # @show aratio
        scaling_rows!(lp)

        scaling_columns!(lp)
        sratio = matrix_coefficient_ratio(lp.A)
        # @show sratio, aratio
        if sratio >= 0.9 * aratio
            break
        end
        round += 1
    end
    normalizing_rows!(lp)
    normalizing_columns!(lp)
end

function normalizing_rows!(lp::MatOI.AbstractLPForm{T}) where T
    m, n = size(lp.A)
    rd = zeros(T, m)
    for i = 1:m
        rd[i] = -Inf
        for j = 1:n
            v = abs(lp.A[i,j])
            if v > 0
                rd[i] = rd[i] < v ? v : rd[i]
            end
        end
    end
    lp.A ./= rd
    lp.c_lb ./= rd
    lp.c_ub ./= rd
end

function scaling_rows!(lp::MatOI.AbstractLPForm{T}) where T
    m, n = size(lp.A)
    rd = zeros(T, m)
    for i = 1:m
        mina = Inf; maxa = -Inf
        for j = 1:n
            v = abs(lp.A[i,j])
            if v > 0
                mina = mina > v ? v : mina
                maxa = maxa < v ? v : maxa
            end
        end
        rd[i] = sqrt(mina*maxa)
    end
    lp.A ./= rd
    lp.c_lb ./= rd
    lp.c_ub ./= rd
    println("Scaling rows: max|aij| $(maximum(rd)) min|aij| $(minimum(rd))")
end

function normalizing_columns!(lp::MatOI.AbstractLPForm{T}) where T
    m, n = size(lp.A)
    cd = zeros(T, n)
    vals = nonzeros(lp.A)
    for j = 1:n
        cd[j] = -Inf
        for i in nzrange(lp.A, j)
            v = abs(vals[i])
            if v > 0
                cd[j] = cd[j] < v ? v : cd[j]
            end
        end
    end
    lp.A ./= cd'
    lp.c ./= cd
    lp.v_lb .*= cd
    lp.v_ub .*= cd
end

# function normalizing_columns!(lp::StandardLpData{CuArray})
#     m, n = size(lp.A)
#     for j = 1:n
#         lp.cd[j] = -Inf
#         for i in 1:m
#             v = abs(lp.A[i,j])
#             if v > 0
#                 lp.cd[j] = lp.cd[j] < v ? v : lp.cd[j]
#             end
#         end
#     end
#     lp.A ./= lp.cd'
#     lp.c ./= lp.cd
#     lp.xl .*= lp.cd
#     lp.xu .*= lp.cd
# end

function scaling_columns!(lp::MatOI.AbstractLPForm{T}) where T
    m, n = size(lp.A)
    cd = zeros(T, n)
    vals = nonzeros(lp.A)
    for j = 1:n
        mina = Inf; maxa = -Inf
        for i in nzrange(lp.A, j)
            v = abs(vals[i])
            if v > 0
                mina = mina > v ? v : mina
                maxa = maxa < v ? v : maxa
            end
        end
        cd[j] = sqrt(mina*maxa)
    end
    lp.A ./= cd'
    lp.c ./= cd
    lp.v_lb .*= cd
    lp.v_ub .*= cd
    println("Scaling columns: max|aij| $(maximum(cd)) min|aij| $(minimum(cd))")
end

# function scaling_columns!(lp::StandardLpData{CuArray})
#     m, n = size(lp.A)
#     for j = 1:n
#         mina = Inf; maxa = -Inf
#         for i in 1:m
#             v = abs(lp.A[i,j])
#             if v > 0
#                 mina = mina > v ? v : mina
#                 maxa = maxa < v ? v : maxa
#             end
#         end
#         lp.cd[j] = sqrt(mina*maxa)
#     end
#     lp.A ./= lp.cd'
#     lp.c ./= lp.cd
#     lp.xl .*= lp.cd
#     lp.xu .*= lp.cd
# end

"""
Compute the ratio of the constraint matrix values
"""

function matrix_coefficient_ratio(A::Union{Matrix,CuMatrix})::Float64
    ratio = -Inf
    m, n = size(A)
    for j = 1:n
        mina = Inf; maxa = -Inf
        for i in 1:m
            v = abs(A[i,j])
            if v > 0
                mina = mina > v ? v : mina
                maxa = maxa < v ? v : maxa
            end
        end
        ratio = ratio > maxa / mina ? ratio : maxa / mina
    end
    return ratio
end

function matrix_coefficient_ratio(A::SparseMatrixCSC)::Float64
    ratio = -Inf
    m, n = size(A)
    vals = nonzeros(A)
    for j = 1:n
        mina = Inf; maxa = -Inf
        for i in nzrange(A, j)
            v = abs(vals[i])
            if v > 0
                mina = mina > v ? v : mina
                maxa = maxa < v ? v : maxa
            end
        end
        ratio = ratio > maxa / mina ? ratio : maxa / mina
    end
    return ratio
end


# using Revise
using Test, BifurcationKit, ForwardDiff, RecursiveArrayTools, LinearAlgebra
const BK = BifurcationKit
N = 10
M = 5
δ = 1e-8
####################################################################################################
# test the jacobian of the multiple shooting functional using Linear flow
# TODO do example with A matrix and exp(At)
vf(x, p) = x
flow(x, p, t) = (u=exp(t) .* x, t=t)
dflow(x, p, dx, t) = (flow(x, p, t)..., du = exp(t) .* dx)
section(x, T) = dot(x[1:N], ones(N))
section(x, T, dx, dT) = dot(dx[1:N], ones(N)) * T
section(x::BorderedArray, T) = section(vec(x.u[:,:]), T)
section(x::BorderedArray, T, dx, dT) = section(vec(x.u[:,:]), T, vec(dx.u[:,:]), dT)
par = nothing

fl = BK.Flow(vf, flow, dflow)

probSh = BK.ShootingProblem(M, fl,
	LinRange(0, 1, M+1) |> diff,
	section, false)

show(probSh)

poguess = VectorOfArray([rand(N) for ii=1:M])
	po = BorderedArray(poguess, 1.)

dpoguess = VectorOfArray([rand(N) for ii=1:M])
	dpo = BorderedArray(dpoguess, 2.)

# use of AbstractArray structure
pov = vcat(vec(po.u), po.p)
dpov = vcat(vec(dpo.u), dpo.p)
resv = probSh(pov, par)

dresv = probSh(pov, par, dpov; δ = δ)
resad = ForwardDiff.derivative(t -> probSh(pov .+ t .* dpov, par), 0.)
@test norm(resad[1:end-1] - dresv[1:end-1], Inf) < 1e-14

# use of BorderedArray structure
res = probSh(po, par)
@test norm(vec(res.u[:,:]) - resv[1:end-1] , Inf) ≈ 0
@test norm(res.p - resv[end], Inf) ≈ 0

dres = probSh(po, par, dpo; δ = δ)
@test norm(dres.p - dresv[end], Inf) ≈ 0
@test norm(vec(dres.u[:,:]) - dresv[1:end-1], Inf) ≈ 0
####################################################################################################
# test the jacobian of the multiple shooting functional using nonLinear flow
vf(x, p) = x.^2
flow(x, p, t) = (u=x ./ (1 .- t .* x),t=t)
dflow(x, p, dx, t) = (flow(x, p, t)..., du = dx ./ (1 .- t .* x).^2)

fl = BK.Flow(vf, flow, dflow)

probSh = BK.ShootingProblem(M, fl,
	LinRange(0,1,M+1) |> diff ,
	section, false)

poguess = VectorOfArray([rand(N) for ii=1:M])
	po = BorderedArray(poguess, 1.)

dpoguess = VectorOfArray([rand(N) for ii=1:M])
	dpo = BorderedArray(dpoguess, 2.)

# use of AbstractArray structure
pov = vcat(vec(po.u), po.p)
dpov = vcat(vec(dpo.u), dpo.p)
resv = probSh(pov, par)

dresv = probSh(pov, par, dpov; δ = δ)
resad = ForwardDiff.derivative(t -> probSh(pov .+ t .* dpov, par), 0.)
@test norm(resad[1:end-1] - dresv[1:end-1], Inf) < 1e-14

# use of BorderedArray structure
res = probSh(po, par)
@test norm(vec(res.u[:,:]) - resv[1:end-1] , Inf) ≈ 0
@test norm(res.p - resv[end], Inf) ≈ 0

dres = probSh(po, par, dpo; δ = δ)
@test norm(dres.p - dresv[end], Inf) ≈ 0
@test norm(vec(dres.u[:,:]) - dresv[1:end-1], Inf) ≈ 0
####################################################################################################
# test the hyperplane projections for Poincare Shooting
M = 1
normals = [rand(2) for ii=1:M]
for ii=1:M
	normals[ii] /= norm(normals[ii])
end
centers = [rand(2) for ii=1:M]

hyper = BK.SectionPS(normals, centers)
poguess = VectorOfArray([rand(2) for ii=1:M])

x = rand(2)
xb = BK.R(hyper, x, 1)
# test
for ii=1:M
	x2 = BK.E(hyper, xb, ii)
	# test that x2 in Sigma2
	@test dot(x2 - centers[ii], normals[ii]) < 1e-14
	# that we have Rk∘Ek = Id and Ek∘Rk = IdΣ
	@test BK.R(hyper, x2, ii) ≈ xb
end

# test of the derivatives of E and R
dx = rand(2)
_out1 = (BK.R(hyper, x .+ δ .* dx, 1) - BK.R(hyper, x, 1)) ./ δ
_out2 = zero(_out1)
_out2 = BK.dR!(hyper, _out2, dx, 1)

@test norm(_out2 - _out1, Inf) < 1e-5

dx = rand(2-1)
_out1 = ForwardDiff.derivative(t -> BK.E(hyper, xb .+ t .* dx,1), 0.)
_out2 = BK.dE(hyper, dx, 1)
@test norm(_out2 - _out1, Inf) < 1e-12

# flow for Poincare return map
# we consider the ODE dr = r⋅(1-r), dθ = 1 [2π]
# the flow is given by Φ(r0,θ0,t) = (r0 e^{t}/(1-r0+r0 e^{t}),θ+t)
vf(x, p) = [x[1]*(1-x[1]), 1.]
flow(x, p, t; k...) = (t = t, u = [exp(t) .* x[1] / (1-x[1]+x[1]*exp(t)),x[2]+t])
Π2(x, p, t = 0) = (t = 2pi -x[2], u = flow(x, p, 2pi-x[2]).u)
Π(x, p, t = 0) = flow(x, p, 2pi-x[2])	# return map
dflow(x, p, dx, t; k...) = (t = t, u = flow(x, p, t).u, du = ForwardDiff.derivative( z -> flow(x .+ z .* dx, p, t).u, 0),)
section(x, T) = dot(x[1:2], [1, 0])
# section(x::BorderedArray, T) = section(vec(x.u[:,:]), T)
par = nothing

fl = BK.Flow(F = vf, flow = Π, flowSerial = Π2, dfSerial = dflow)
sectionps = SectionPS(normals, centers)
probPSh = PoincareShootingProblem(flow = fl, M = M, section = sectionps)

ci = reduce(vcat, BK.projection(probPSh, poguess.u))
dci = rand(length(ci))

# we test that we have the analytical version of the flow
z0 = rand(2)
@test ForwardDiff.derivative(z -> flow(z0, par, z).u, 0.) ≈ vf(z0, par)

#test the show method
show(probPSh)

# test functional and its differential
probPSh(ci, par)
probPSh(ci, par, dci)

# test the analytical of the differential of the return Map
z0 = rand(2)
dz0 = rand(2)
_out0 = BK.diffPoincareMap(probPSh, z0, par, dz0, 1)
_out1 = ForwardDiff.derivative(z -> Π(z0 .+ z .* dz0, par).u, 0)
display(_out0)
display(_out1)

# test the analytical version of the functional
_out0 = probPSh(ci, par, dci)
	δ = 1e-6
	_out2 = (probPSh(ci .+ δ .* dci, par) .- probPSh(ci, par)) ./ δ
	_out1 = ForwardDiff.derivative(z -> probPSh(ci .+ z .* dci, par), 0)
	display(_out0)
	display(_out1)
	display(_out2)
	_out0 - _out1 |> display