# Abstracts for algorithm parameters
mutable struct SPEA2 <: AbstractNSGA
    N::Int
    η_cr::Float64
    p_cr::Float64
    η_m::Float64
    p_m::Float64
    fitness::Vector{Float64}
end

"""
    SPEA2(;
        N = 100,
        η_cr = 20,
        p_cr = 0.9,
        η_m = 20,
        p_m = 1.0 / D,
        information = Information(),
        options = Options(),
    )

Parameters for the metaheuristic NSGA-II.

Parameters:

- `N` Population size.
- `η_cr`  η for the crossover.
- `p_cr` Crossover probability.
- `η_m`  η for the mutation operator.
- `p_m` Mutation probability (1/D for D-dimensional problem by default).

To use SPEA2, the output from the objective function should be a 3-touple
`(f::Vector, g::Vector, h::Vector)`, where `f` contains the objective functions,
`g` and `h` are inequality, equality constraints respectively.

A feasible solution is such that `g_i(x) ≤ 0 and h_j(x) = 0`.

```julia
using Metaheuristics

# Dimension
D = 2

# Objective function
f(x) = ( x, [sum(x.^2) - 1], [0.0] )

# bounds
bounds = [-1 -1;
           1  1.0
        ]

# define the parameters (use `SPEA2()` for using default parameters)
nsga2 = SPEA2(N = 100, p_cr = 0.85)

# optimize
status = optimize(f, bounds, nsga2)

# show results
display(status)
```

"""
function SPEA2(;
    N = 100,
    η_cr = 20,
    p_cr = 0.9,
    η_m = 20,
    p_m = -1,
    information = Information(),
    options = Options(),
)

    parameters = SPEA2(N, promote( Float64(η_cr), p_cr, η_m, p_m )...,[])
    Algorithm(
        parameters,
        information = information,
        options = options,
    )

end

function update_state!(
    status::State,
    parameters::SPEA2,
    problem::AbstractProblem,
    information::Information,
    options::Options,
    args...;
    kargs...
    )

    if isempty(parameters.fitness)
        parameters.fitness = compute_fitness(status.population)
    end

    Q = reproduction(status, parameters, problem)

    append!(status.population, create_solutions(Q, problem))

    # non-dominated sort, crowding distance, elitist removing
    environmental_selection!(status.population, parameters)
end

function environmental_selection!(population, parameters::SPEA2)
    Distance = compute_distances(population)
    fitness = compute_fitness(population, Distance)
    N = parameters.N

    next = fitness .< 1
    K = count(next)
    if K < N
        rank = sortperm(fitness)
        next[rank[1:N]] .= true
    elseif K > N
        del  = truncation(population[next], K-N, Distance[next,next])
        temp = findall(next)
        next[temp[del]] .= false
    end

    deleteat!(population, .!next)
    deleteat!(fitness, .!next)
    parameters.fitness = fitness

    return
end

function compute_distances(population)
    N = length(population)

    distances = zeros(N,N)
    for i in 1:N
        distances[i,i] = Inf
        for j in i+1:N
            distances[i,j] = norm(fval(population[i]) - fval(population[j]))
            distances[j,i] = distances[i,j]
        end
    end

    return distances
end

function truncation(population, K, distance = compute_distances(population))
    del = zeros(Bool, length(population))
    while count(del) < K
        remain   = findall(.!del)
        #temp     = sortperm(distance[remain,remain], dims = 2)
        # rank     = sortslicesperm(temp, dims=1)
        nn = argmin(distance[remain,remain])
        del[remain[nn.I[1]]] = true
    end

    return del
end

function compute_fitness(population, Distance = compute_distances(population))
    N = length(population)
    dominate = zeros(Bool,N, N)
    for i in 1:N
        for j in i+1:N
            k = compare(population[i], population[j])
            dominate[i,j] = k == 1
            dominate[j,i] = k == 2
        end
    end

    S = sum(dominate,dims=2)[:,1]
    R = [ sum(S[dominate[:,i]]) for i in 1:N]

    distance = sort(Distance,dims=2)

    D = 1 ./ (distance[:,floor(Int,sqrt(N))] .+ 2)
    return R + D
end


module DataSet

function two_squared(n)
    x = rand(0:100,n)*0.01
    y = rand(0:100,n)*0.01
    _t_data = [] #正解データ（上1か下0か）

    for i in 1:n
        if x[i]^2 >= y[i]
            #下にある
            append!(_t_data,0)
        else
            append!(_t_data,1)
        end
    end

    _data = hcat(x,y)

    return _data,_t_data
end

function noise_sin(n)
    data,t_data = normal_sin(n,interval=0.05)
    t_data += rand(size(t_data,1))*0.2

    return data,t_data
end

function normal_sin(n;interval=0.1)
    data = Array(-pi*2*n : interval : pi*2*n)
    t_data = sin.(data)

    return data,t_data
end

end


module Test_cplyap

using LinearAlgebra
using MatrixEquations
using Test

@testset "Testing positive continuous Lyapunov equation solvers" begin

n = 30
m = 5
p = 3
Ty = Float64

@testset "Positive continuous Lyapunov equations" begin

reltol = eps(float(100))
a = -1; b = 2im; @time u = plyapc(a,b)
@test abs(a*u*u'+u*u'*a'+b*b') < reltol

reltol = eps(float(100f0))
a = -1f0; b = 2f0im; @time u = plyapc(a,b)
@test abs(a*u*u'+u*u'*a'+b*b') < reltol

for Ty in (Float64, Float32)

ar = rand(Ty,n,n)
ar = ar-2*norm(ar)*Matrix(I,n,n)
ac = rand(Ty,n,n)+im*rand(Ty,n,n)
ac = ac-2*norm(ac)*Matrix(I,n,n)
br = rand(Ty,n,m)
brw = rand(Ty,n,n+m)
bc = br+im*rand(Ty,n,m)
bcw = brw+im*rand(Ty,n,n+m)
cr = rand(Ty,p,n)
crt = rand(Ty,n+p,n)
cc = cr+im*rand(Ty,p,n)
cct = crt+im*rand(Ty,n+p,n)
Ty == Float64 ? reltol = eps(float(100)) : reltol = eps(100*n*one(Ty))

@time u = plyapc(ar,br);
x = u*u'; @test norm(ar*x+x*ar'+br*br')/norm(x)/norm(ar) < reltol

@time u = plyapc(ar,0*br);
x = u*u'; @test norm(ar*x+x*ar') < reltol

@time u = plyapc(ar,brw);
x = u*u'; @test norm(ar*x+x*ar'+brw*brw')/norm(x)/norm(ar) < reltol

@time u = plyapc(ar,I,br);
x = u*u'; @test norm(ar*x+x*ar'+br*br')/norm(x)/norm(ar) < reltol

@time u = plyapc(ar',cr');
x = u'*u; @test norm(ar'*x+x*ar+cr'*cr)/norm(x)/norm(ar) < reltol

@time u = plyapc(ar',crt');
x = u'*u; @test norm(ar'*x+x*ar+crt'*crt)/norm(x)/norm(ar) < reltol

@time u = plyapc(ac,bc);
x = u*u'; @test norm(ac*x+x*ac'+bc*bc')/norm(x)/norm(ac) < reltol

@time u = plyapc(ac,0*bc);
x = u*u'; @test norm(ac*x+x*ac') < reltol

@time u = plyapc(ac,bcw);
x = u*u'; @test norm(ac*x+x*ac'+bcw*bcw')/norm(x)/norm(ac) < reltol

@time u = plyapc(ac',cc');
x = u'*u; @test norm(ac'*x+x*ac+cc'*cc)/norm(x)/norm(ac) < reltol

@time u = plyapc(ac',cct');
x = u'*u; @test norm(ac'*x+x*ac+cct'*cct)/norm(x)/norm(ac) < reltol

@time u = plyapc(ac',I,cc');
x = u'*u; @test norm(ac'*x+x*ac+cc'*cc)/norm(x)/norm(ac) < reltol

@time u = plyapc(ac,br);
x = u*u'; @test norm(ac*x+x*ac'+br*br')/norm(x)/norm(ac) < reltol

@time u = plyapc(ac',cr');
x = u'*u; @test norm(ac'*x+x*ac+cr'*cr)/norm(x)/norm(ac) < reltol

@time u = plyapc(ar,bc);
x = u*u'; @test norm(ar*x+x*ar'+bc*bc')/norm(x)/norm(ar) < reltol

@time u = plyapc(ar',cc');
x = u'*u; @test norm(ar'*x+x*ar+cc'*cc)/norm(x)/norm(ar)  < reltol

end
end

@testset "Positive continuous generalized Lyapunov equations" begin

reltol = eps(float(100))
a = -1; ee = 4; b = 2im; @time u = plyapc(a,ee,b)
@test abs(a*u*u'*ee'+ee*u*u'*a'+b*b') < reltol

reltol = eps(float(100f0))
a = -1f0; ee = 4f0; b = 2f0im; @time u = plyapc(a,ee,b)
@test abs(a*u*u'*ee'+ee*u*u'*a'+b*b')  < reltol

for Ty in (Float64, Float32)

ar = rand(Ty,n,n)
ar = ar-2*norm(ar)*Matrix(I,n,n)
er = rand(n,n)
ar = er*ar
ac = rand(Ty,n,n)+im*rand(Ty,n,n)
ac = ac-2*norm(ac)*Matrix(I,n,n)
ec = er+im*rand(Ty,n,n)
ac = ec*ac
br = rand(Ty,n,m)
brw = rand(Ty,n,n+m)
bc = br+im*rand(Ty,n,m)
bcw = brw+im*rand(Ty,n,n+m)
cr = rand(Ty,p,n)
crt = rand(Ty,n+p,n)
cc = cr+im*rand(Ty,p,n)
cct = crt+im*rand(Ty,n+p,n)
Ty == Float64 ? reltol = eps(float(100)) : reltol = eps(100*n*one(Ty))

@time u = plyapc(ar,er,br);
x = u*u'; @test norm(ar*x*er'+er*x*ar'+br*br')/norm(x)/norm(ar) < reltol

@time u = plyapc(ar,er,0*br);
x = u*u'; @test norm(ar*x*er'+er*x*ar') < reltol

@time u = plyapc(ar,er,brw);
x = u*u'; @test norm(ar*x*er'+er*x*ar'+brw*brw')/norm(x)/norm(ar) < reltol

@time u = plyapc(ar',er',cr');
x = u'*u; @test norm(ar'*x*er+er'*x*ar+cr'*cr)/norm(x)/norm(ar) < reltol

@time u = plyapc(ar',er',crt');
x = u'*u; @test norm(ar'*x*er+er'*x*ar+crt'*crt)/norm(x)/norm(ar) < reltol

@time u = plyapc(ac,ec,bc);
x = u*u'; @test norm(ac*x*ec'+ec*x*ac'+bc*bc')/norm(x)/norm(ac)/norm(ec) < reltol

@time u = plyapc(ac,ec,0*bc);
x = u*u'; @test norm(ac*x*ec'+ec*x*ac') < reltol

@time u = plyapc(ac,ec,bcw);
x = u*u'; @test norm(ac*x*ec'+ec*x*ac'+bcw*bcw')/norm(x)/norm(ac)/norm(ec) < reltol

@time u = plyapc(ac',ec',cc');
x = u'*u; @test norm(ac'*x*ec+ec'*x*ac+cc'*cc)/norm(x)/norm(ac) < reltol

@time u = plyapc(ac',ec',cct');
x = u'*u; @test norm(ac'*x*ec+ec'*x*ac+cct'*cct)/norm(x)/norm(ac) < reltol

@time u = plyapc(ar,er,bc);
x = u*u'; @test norm(ar*x*er'+er*x*ar'+bc*bc')/norm(x)/norm(ar) < reltol

@time u = plyapc(ar',er',cc');
x = u'*u; @test norm(ar'*x*er+er'*x*ar+cc'*cc)/norm(x)/norm(ar) < reltol

@time u = plyapc(ac,ec,br);
x = u*u'; @test norm(ac*x*ec'+ec*x*ac'+br*br')/norm(x)/norm(ac)/norm(ec) < reltol

@time u = plyapc(ac',ec',cr');
x = u'*u; @test norm(ac'*x*ec+ec'*x*ac+cr'*cr)/norm(x)/norm(ac) < reltol

end
end

@testset "Positive 2x2 continuous Lyapunov equations" begin

A = [-1.1 1.; -1. -1.]
E = [1. 1.; 0. 1.]
R = UpperTriangular([1. 1.; 0. 1.])
reltol = eps(float(100))

U = copy(R)
β, α = MatrixEquations.plyap2!(A, U, adj = true, disc = false)
X = U'*U; @test norm(A'*X+X*A+R'*R)/max(1,norm(X))/norm(A) < reltol &&
                norm(β*U-U*A)/max(1,norm(U))/norm(A) < reltol &&
                norm(α*U - R)/max(1,norm(R)) < reltol

U = copy(R)
β, α = MatrixEquations.plyap2!(A, U, adj = false, disc = false)
X = U*U'; @test norm(A*X+X*A'+R*R')/max(1,norm(X))/norm(A) < reltol &&
                norm(U*β-A*U)/max(1,norm(U))/norm(A) < reltol &&
                norm(U*α - R)/max(1,norm(R)) < reltol

U = copy(R)
β, α = MatrixEquations.pglyap2!(A, E, U, adj = true, disc = false)
X = U'*U; @test norm(A'*X*E+E'*X*A+R'*R)/max(1,norm(X))/norm(A) < reltol &&
                norm(β*U*E-U*A)/max(1,norm(U))/norm(A) < reltol &&
                norm(α*U*E - R)/max(1,norm(R)) < reltol

U = copy(R)
β, α = MatrixEquations.pglyap2!(A, E, U, adj = false, disc = false)
X = U*U'; @test norm(A*X*E'+E*X*A'+R*R')/max(1,norm(X))/norm(A) < reltol &&
                norm(A*U-E*U*β)/max(1,norm(U))/norm(A) < reltol &&
                norm(E*U*α - R)/max(1,norm(R)) < reltol

U = copy(R)
Q = qr(rand(2,2)).Q; A1 = Q*A; E1 = Q*E;
β, α = MatrixEquations.pglyap2!(A1, E1, U, adj = false, disc = false)
X = U*U'; @test norm(A1*X*E1'+E1*X*A1'+R*R')/max(1,norm(X))/norm(A) < reltol &&
                norm(A1*U-E1*U*β)/max(1,norm(U))/norm(A) < reltol &&
                norm(E1*U*α - R)/max(1,norm(R)) < reltol

U = copy(0*R)
β, α = MatrixEquations.pglyap2!(A, E, U, adj = false, disc = false)
X = U*U'; @test norm(A*X*E'+E*X*A')/max(1,norm(X))/norm(A) < reltol &&
                norm(A*U-E*U*β)/max(1,norm(U))/norm(A) < reltol &&
                norm(E*U*α - 0*R)/max(1,norm(R)) < reltol

A = [-1.1 1.; -1. -1.]/10; A = convert(Matrix{Float32},A)
E = [1. 1.; 0. 1.]; E = convert(Matrix{Float32},E)
R = [1. 1.; 0. 1.]; R = UpperTriangular(convert(Matrix{Float32},R))
reltol = eps(100f0)

U = copy(R)
β, α = MatrixEquations.plyap2!(A, U, adj = true, disc = false)
X = U'*U; @test norm(A'*X+X*A+R'*R)/max(1,norm(X))/norm(A) < reltol &&
                norm(β*U-U*A)/max(1,norm(U))/norm(A) < reltol &&
                norm(α*U - R)/max(1,norm(R)) < reltol

U = copy(R)
β, α = MatrixEquations.plyap2!(A, U, adj = false, disc = false)
X = U*U'; @test norm(A*X+X*A'+R*R')/max(1,norm(X))/norm(A) < reltol &&
                norm(U*β-A*U)/max(1,norm(U))/norm(A) < reltol &&
                norm(U*α - R)/max(1,norm(R)) < reltol

U = copy(R)
β, α = MatrixEquations.pglyap2!(A, E, U, adj = true, disc = false)
X = U'*U; @test norm(A'*X*E+E'*X*A+R'*R)/max(1,norm(X))/norm(A) < reltol &&
                norm(β*U*E-U*A)/max(1,norm(U))/norm(A) < reltol &&
                norm(α*U*E - R)/max(1,norm(R)) < reltol

U = copy(R)
β, α = MatrixEquations.pglyap2!(A, E, U, adj = false, disc = false)
X = U*U'; @test norm(A*X*E'+E*X*A'+R*R')/max(1,norm(X))/norm(A) < reltol &&
                norm(A*U-E*U*β)/max(1,norm(U))/norm(A) < reltol &&
                norm(E*U*α - R)/max(1,norm(R)) < reltol

U = copy(0*R)
β, α = MatrixEquations.pglyap2!(A, E, U, adj = false, disc = false)
X = U*U'; @test norm(A*X*E'+E*X*A')/max(1,norm(X))/norm(A) < reltol &&
                norm(A*U-E*U*β)/max(1,norm(U))/norm(A) < reltol &&
                norm(E*U*α)/max(1,norm(R)) < reltol

end

@testset "Continuous positive Lyapunov equations - Schur form" begin

for Ty in (Float64, Float32)

ar = rand(Ty,n,n)
ar = ar-2*norm(ar)*Matrix(I,n,n)
as,  = schur(ar)
ac = rand(Ty,n,n)+im*rand(Ty,n,n)
ac = ac-2*norm(ac)*Matrix(I,n,n)
acs,  = schur(ac)
br = rand(Ty,n,m)
brw = rand(Ty,n,n+m)
bc = br+im*rand(Ty,n,m)
cr = rand(Ty,p,n)
crt = rand(Ty,n+p,n)
cc = cr+im*rand(Ty,p,n)
Ty == Float64 ? reltol = eps(float(100)) : reltol = eps(100*n*one(Ty))

@time u = plyaps(as,br);
x = u*u'; @test norm(as*x+x*as'+br*br')/norm(x)/norm(as) < reltol

@time u = plyaps(as,brw);
x = u*u'; @test norm(as*x+x*as'+brw*brw')/norm(x)/norm(as) < reltol

@time u = plyaps(as,I,br);
x = u*u'; @test norm(as*x+x*as'+br*br')/norm(x)/norm(as) < reltol

@time u = plyaps(as',cr');
x = u'*u; @test norm(as'*x+x*as+cr'*cr)/norm(x)/norm(as) < reltol

@time u = plyaps(as',crt');
x = u'*u; @test norm(as'*x+x*as+crt'*crt)/norm(x)/norm(as) < reltol

@time u = plyaps(acs,bc);
x = u*u'; @test norm(acs*x+x*acs'+bc*bc')/norm(x)/norm(as) < reltol

@time u = plyaps(acs',cc');
x = u'*u; @test norm(acs'*x+x*acs+cc'*cc)/norm(x)/norm(as) < reltol

@time u = plyaps(acs',I,cc');
x = u'*u; @test norm(acs'*x+x*acs+cc'*cc)/norm(x)/norm(as) < reltol

F = UpperTriangular(rand(Ty,n,n))
R = copy(F)
@time plyapcs!(as,R,adj = true);
x = R'*R; @test norm(as'*x+x*as+F'*F)/norm(x)/norm(as) < reltol

F = UpperTriangular(rand(Ty,n,n))
R = copy(F)
@time plyapcs!(as,I,R,adj = true);
x = R'*R; @test norm(as'*x+x*as+F'*F)/norm(x)/norm(as) < reltol

F = UpperTriangular(rand(Ty,n,n))
R = copy(F)
@time plyapcs!(as,R,adj = false);
x = R*R'; @test norm(as*x+x*as'+F*F')/norm(x)/norm(as) < reltol

F = UpperTriangular(rand(Ty,n,n)+im*rand(Ty,n,n))
R = copy(F)
@time plyapcs!(acs,R,adj = true);
x = R'*R; @test norm(acs'*x+x*acs+F'*F)/norm(x)/norm(acs) < reltol

F = UpperTriangular(rand(Ty,n,n)+im*rand(Ty,n,n))
R = copy(F)
@time plyapcs!(acs,R,adj = false);
x = R*R'; @test norm(acs*x+x*acs'+F*F')/norm(x)/norm(acs) < reltol

F = UpperTriangular(rand(Ty,n,n)+im*rand(Ty,n,n))
R = copy(F)
@time plyapcs!(acs,I,R,adj = true);
x = R'*R; @test norm(acs'*x+x*acs+F'*F)/norm(x)/norm(acs) < reltol

er = rand(Ty,n,n)
ar = er*ar
as, es = schur(ar,er)
ec = er+im*rand(Ty,n,n)
ac = ec*ac
acs, ecs = schur(ac,ec)

@time u = plyaps(as,es,br);
x = u*u'; @test norm(as*x*es'+es*x*as'+br*br')/norm(x)/norm(as) < reltol

@time u = plyaps(as,es,brw);
x = u*u'; @test norm(as*x*es'+es*x*as'+brw*brw')/norm(x)/norm(as) < reltol

@time u = plyaps(as',es',cr');
x = u'*u; @test norm(as'*x*es+es'*x*as+cr'*cr)/norm(x)/norm(as) < reltol

@time u = plyaps(as',es',crt');
x = u'*u; @test norm(as'*x*es+es'*x*as+crt'*crt)/norm(x)/norm(as) < reltol

@time u = plyaps(acs,ecs,bc);
x = u*u'; @test norm(acs*x*ecs'+ecs*x*acs'+bc*bc')/norm(x)/norm(as) < reltol

@time u = plyaps(acs',ecs',cc');
x = u'*u; @test norm(acs'*x*ecs+ecs'*x*acs+cc'*cc)/norm(x)/norm(as) < reltol

F = UpperTriangular(rand(Ty,n,n))
R = copy(F)
@time plyapcs!(as,es,R,adj = true);
x = R'*R; @test norm(as'*x*es+es'*x*as+F'*F)/norm(x)/norm(as) < reltol  #fails

F = UpperTriangular(rand(Ty,n,n))
R = copy(0*F)
@time plyapcs!(as,es,R,adj = true);
x = R'*R; @test norm(as'*x*es+es'*x*as) < reltol

F = UpperTriangular(rand(Ty,n,n))
R = copy(F)
@time plyapcs!(as,es,R,adj = false);
x = R*R'; @test norm(as*x*es'+es*x*as'+F*F')/norm(x)/norm(as) < reltol

F = UpperTriangular(rand(Ty,n,n)+im*rand(Ty,n,n))
R = copy(F)
@time plyapcs!(acs,ecs,R,adj = false);
x = R*R'; @test norm(acs*x*ecs'+ecs*x*acs'+F*F')/norm(x)/norm(acs) < reltol

F = UpperTriangular(rand(Ty,n,n)+im*rand(Ty,n,n))
R = copy(0*F)
@time plyapcs!(acs,ecs,R,adj = false);
x = R*R'; @test norm(acs*x*ecs'+ecs*x*acs') < reltol

F = UpperTriangular(rand(Ty,n,n)+im*rand(Ty,n,n))
R = copy(F)
@time plyapcs!(acs,ecs,R,adj = true);
x = R'*R; @test norm(acs'*x*ecs+ecs'*x*acs+F'*F)/norm(x)/norm(acs) < reltol

end

end
end

end


include("det_loocv.jl")
using Test

#####
##### test det_loocv
#####
#the inremental_cholesky is just a wrapper type that allows one to easily extend the cholesky
#decomposition without allocation more space at each iteration, because a fixed amount of space
#is allocated at the beginning, and what you get to use is a view of the top left n x n corner
#println("test det_loocv.jl...")
n=5
A = rand(n, n); A = A'*A + UniformScaling(4.0)
#IC = incremental_cholesky!(A, n-1) #make it n-1 just for kicks
IC = cholesky(A)

vals = zeros(1, n)
vals1 = zeros(1, n)
for i=1:n
    subdet = det(A[[1:i-1;i+1:end], [1:i-1;i+1:end]])
    vals[i] = subdet
    #println("actual subdets $i: ", subdet)
end

det_original = det(IC)
for i = 1:n
    subdet = det_loocv(IC, det_original, i)
    vals1[i] = subdet
    #println("fast subdets $i: ", subdet)
end

@testset "det_loocv.jl" begin
    @test norm(vals-vals1)<1e-10
end

#####
##### test det_XΣX_loocv
#####
#println("test det_XΣX_loocv...")

n = 20
m = 3
X = rand(n, m)
A = (w = rand(n, n);w'*w + UniformScaling(5))
cholA = cholesky(A)
detA = det(cholA)
bigmat = [A X; X' zeros(m, m)]
#for i = 1:n
#    println("actual subdet $i: ", det(bigmat[[1:i-1;i+1:end], [1:i-1;i+1:end]]))
#end
vals = zeros(1, n)
vals1 = zeros(1, n)
for i = 1:n
    X_minus_i = X[[1:i-1;i+1:end], :]
    sigma_minus_i = A[[1:i-1;i+1:end], [1:i-1;i+1:end]]
    vals[i] = det(X_minus_i'*(sigma_minus_i\X_minus_i))
    #println("actual subdet $i: ", det(X_minus_i'*(sigma_minus_i\X_minus_i)))
end
for i =1:n
    subdet =  det_XΣX_loocv(X, cholA, detA, i)
    vals1[i] = subdet
    #println("fast subdet $i: ", subdet)
end

@testset "det_XΣX_loocv" begin
    @test norm(vals-vals1)<1e-10
end

#####
##### test logdet_loocv_IC
#####
#println("test logdet_loocv_IC...")
n=5
A = rand(n, n); A = A'*A + UniformScaling(4.0)
B = copy(A)
IC = incremental_cholesky!(B, n)
#IC = cholesky(A)
vals = zeros(1, n)
for i=1:n
    sublogdet = log(det(A[[1:i-1;i+1:end], [1:i-1;i+1:end]]))
    vals[i] = sublogdet
    #println("actual sublogdets $i: ", sublogdet)
end
vals1 = zeros(1, n)
logdet_original = log(det(IC))
for i = 1:n
    sublogdet = logdet_loocv_IC(IC, logdet_original, i)
    vals1[i] = sublogdet
    #println("fast sublogdet $i: ", sublogdet)
end
@testset "logdet_loocv_IC" begin
    @test norm(vals-vals1)<1e-10
end

#####
##### test logdet_loocv
#####
#println("test logdet_loocv...")
n=5
#A = rand(n, n); A = A'*A + UniformScaling(1.0)
#println("A: ", A)
val = zeros(1, n)
chol = cholesky(A)
for i=1:n
    sublogdet = log(det(A[[1:i-1;i+1:end], [1:i-1;i+1:end]]))
    vals[i] = sublogdet
    #println("actual sublogdets $i: ", sublogdet)
end

val1 =zeros(1, n)
logdet_original = log(det(chol))
for i = 1:n
    sublogdet = logdet_loocv(chol, logdet_original, i)
    vals1[i] = sublogdet
    #println("fast sublogdet $i: ", sublogdet)
end

@testset "logdet_loocv" begin
    @test norm(vals-vals1)<1e-10
end

#####
##### test logdet_XΣX_loocv
#####
#println("Running test logdet_XΣX_loocv...")
n = 30
m = 10
X = rand(n, m)
A = (w = rand(n, n);w'*w + UniformScaling(5))
cholA = cholesky(A)
logdetA = logdet(cholA)
bigmat = [A X; X' zeros(m, m)]
#for i = 1:n
#    println("actual subdet $i: ", det(bigmat[[1:i-1;i+1:end], [1:i-1;i+1:end]]))
#end
vals = zeros(1, n)
@time begin
for i = 1:n
    X_minus_i = X[[1:i-1;i+1:end], :]
    sigma_minus_i = A[[1:i-1;i+1:end], [1:i-1;i+1:end]]
    vals[i] = log(abs(det(X_minus_i'*(sigma_minus_i\X_minus_i))))
    #println("actual sublogdet $i: ", log(abs(det(X_minus_i'*(sigma_minus_i\X_minus_i)))))
end
end
vals1 = zeros(1, n)
@time begin
for i =1:n
    sublogdet =  logdet_XΣX_loocv(X, cholA, logdetA, i)
    vals1[i] = sublogdet
    #println("fast sublogdet $i: ", sublogdet)
end
end
println("norm of errors: ", norm(vals-vals1))

@testset "logdet_XΣX_loocv" begin
    @test norm(vals-vals1)<1e-10
end

#####
##### test logdet_XΣX_loocv_IC
#####
#println("Running test logdet_XΣX_loocv_IC...")
n = 30
m = 10
X = rand(n, m)
#A = (w = rand(n, n);w'*w + UniformScaling(5))
B = copy(A) #from previous
cholA = incremental_cholesky!(B, size(A)[1])
logdetA = logdet(cholA)
bigmat = [A X; X' zeros(m, m)]
#for i = 1:n
#    println("actual subdet $i: ", det(bigmat[[1:i-1;i+1:end], [1:i-1;i+1:end]]))
#end
vals = zeros(1, n)
@time begin
for i = 1:n
    X_minus_i = X[[1:i-1;i+1:end], :]
    sigma_minus_i = A[[1:i-1;i+1:end], [1:i-1;i+1:end]]
    vals[i] = log(abs(det(X_minus_i'*(sigma_minus_i\X_minus_i))))
    #println("actual sublogdet $i: ", log(abs(det(X_minus_i'*(sigma_minus_i\X_minus_i)))))
end
end
vals1 = zeros(1, n)
@time begin
for i =1:n
    sublogdet =  logdet_XΣX_loocv_IC(X, cholA, logdetA, i)
    vals1[i] = sublogdet
    #println("fast sublogdet $i: ", sublogdet)
end
end
#println("norm of errors: ", norm(vals-vals1))

@testset "logdet_XΣX_loocv_IC" begin
    @test norm(vals-vals1)<1e-10
end


using SemiclassicalOrthogonalPolynomials
using ClassicalOrthogonalPolynomials, ContinuumArrays, BandedMatrices, QuasiArrays, Test, LazyArrays, LinearAlgebra, InfiniteArrays
import LazyArrays: AbstractCachedVector
import SemiclassicalOrthogonalPolynomials: initialα, αdirect, αdirect!, backαcoeff!, αcoefficients!, evalϕn, neg1c_tolegendre, evalQn, getindex, initialα_gen, symlowered_jacobimatrix, αgenfillerbackwards!, symlowered_jacobimatrix

@testset "Special case: SemiclassicalJacobi(t,0,0,-1) " begin
    @testset "inital α" begin
        t1 = 1.1
        t2 = 1.841
        t3 = 3.91899
        t4 = BigFloat("1.0000000000000000000001")
        # Mathematica
        @test initialα(t1) ≈ 0.4430825224938773
        @test initialα(t2) ≈ 0.1980462516542294
        @test initialα(t3) ≈ 0.0865853392346796
        @test initialα(t4) ≈ 0.9610516212042500
    end

    @testset "αdirect! consistency" begin
        N = 5
        t1 = 1.1
        t2 = 1.841
        t3 = 3.91899
        t4 = BigFloat("1.0000000000000000000001")
        α1 = zeros(eltype(float(t1)), N)
        α2 = zeros(eltype(float(t2)), N)
        α3 = zeros(eltype(float(t3)), N)
        α4 = zeros(eltype(float(t3)), N)
        αdirect!(α1,t1,1:N)
        αdirect!(α2,t2,1:N)
        αdirect!(α3,t3,1:N)
        αdirect!(α4,t4,1:N)
        @test αdirect.((1:N),t1) ≈ α1
        @test αdirect.((1:N),t2) ≈ α2
        @test αdirect.((1:N),t3) ≈ α3
        @test αdirect.((1:N),t4) ≈ α4
    end

    @testset "basic forward and back recurrence" begin
        # set parameters
        N = 30
        t1 = BigFloat("1.841")
        t2 = BigFloat("1.0000000000000000000001")
        # initialize α
        α1f = zeros(eltype(float(t1)), N)
        α1f[1] = initialα(t1)
        α2f = zeros(eltype(float(t2)), N)
        α2f[1] = initialα(t2)
        α1b = zeros(eltype(float(t1)), N)
        α2b = zeros(eltype(float(t2)), N)
        # compute coefficients via forward recurrence
        αcoefficients!(α1f,t1,BigInt.(2:N))
        αcoefficients!(α2f,t2,BigInt.(2:N))
        # compute coefficients via back recurrence
        backαcoeff!(α1b,t1,BigInt.(1:N))
        backαcoeff!(α2b,t2,BigInt.(1:N))
        # Mathematica α1
        @test α1b[4]  ≈ 0.262708732908399743 ≈ α1f[4]
        @test α1b[6]  ≈ 0.272687692260606064 ≈ α1f[6]
        @test α1b[10] ≈ 0.281264391787711543 ≈ α1f[10]
        @test α1b[20] ≈ 0.288083843194443346 ≈ α1f[20]
        # Mathematica α2
        @test α2b[3]  ≈ 0.98621165663772362 ≈ α2f[3]
        @test α2b[5]  ≈ 0.99152243369113373 ≈ α2f[5]
        @test α2b[10] ≈ 0.99562287407137481 ≈ α2f[10]
        @test α2b[20] ≈ 0.99774034482793111 ≈ α2f[20]
    end

    @testset "evaluation normalized" begin
        t = BigFloat("1.1")
        # Mathematica values
        @test evalQn(0,0.99,t) ≈ 1
        @test evalQn(1,0.5,t) ≈ -0.6623723753894052
        @test evalQn(6,0.12,t) ≈ -1.965171674178137
    end

    @testset "evaluation non-normalized" begin
        t = 1.1
        x = 0.1
        n = 5
        α = zeros(n+1)'
        α[1] = initialα(2*t-1)
        αcoefficients!(α,2*t-1,2:n)
        # compare versions with and without recomputing α with Mathematica results
        @test evalϕn(0,x,t) == 1
        @test evalϕn(1,x,t) ≈ -1.165935217151491
        @test evalϕn(2,x,t) ≈ 0.806910345733665
    end

    @testset "Expansion" begin
        # basis
        t = 1.00001
        Q = SemiclassicalJacobi(t,0,0,-1)
        x = axes(Q,1)
        # test functions
        f1(x) = x^2
        f2(x) = (t-x)^2
        f3(x) = exp(t-x)
        f4(x) = sinh(t*x)
        # test expansion
        y = rand(1)[1]
        @test (Q[:,1:30]*(Q[:,1:30]\f1.(x)))[y] ≈ f1(y)
        @test (Q[:,1:30]*(Q[:,1:30]\f2.(x)))[y] ≈ f2(y)
        @test (Q[:,1:30]*(Q[:,1:30]\f3.(x)))[y] ≈ f3(y)
        @test (Q[:,1:30]*(Q[:,1:30]\f4.(x)))[y] ≈ f4(y)
    end

    @testset "Expansion (adaptive)" begin
        # basis
        t = 1.23
        Q = SemiclassicalJacobi(t,0,0,-1)
        x = axes(Q,1)
        # test functions
        f1(x) = x^2
        f2(x) = (t-x)^2
        f3(x) = exp(t-x)
        f4(x) = sinh(t*x)
        # test expansion
        y = rand(1)[1]
        @test (Q*(Q\f1.(x)))[y] ≈ f1(y)
        @test (Q*(Q\f2.(x)))[y] ≈ f2(y)
        @test (Q*(Q\f3.(x)))[y] ≈ f3(y)
        @test (Q*(Q\f4.(x)))[y] ≈ f4(y)
    end

    @testset "Multiplication by x" begin
        # basis
        t = 1.00001
        Q = SemiclassicalJacobi(t,0,0,-1)
        x = axes(Q,1)
        X = jacobimatrix(Q)
        # test functions
        f1(x) = x^2
        f2(x) = (t-x)^2
        f3(x) = exp(t-x)
        f4(x) = sinh(t*x)
        # test expansion
        y = 0.4781
        @test (Q*(X*(Q\x.^2)))[y] ≈ y*f1(y)
        @test (Q*(X*X*(Q\(t.-x).^2)))[y] ≈ y^2*f2(y)
        @test (Q*(X*(Q\exp.(t.-x))))[y] ≈ y*f3(y)
        @test (Q*(X*(Q\sinh.(t.*x))))[y] ≈ y*f4(y)
    end
end

@testset "Generic lowering operators" begin
    @testset "Iterative lowering" begin
        # lowering a iteratively
        t = 1.812
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,15,3,6,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,12,3,6,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,12,3,6,RaisetoLower)
        @test LoweredPoly.X[1:100,1:100] ≈ RaisetoCompare.X[1:100,1:100]
        # lowering b iteratively
        t = 1.1
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,7,6,8,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,7,4,8,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,7,4,8,RaisetoLower)
        @test LoweredPoly.X[1:100,1:100] ≈ RaisetoCompare.X[1:100,1:100]
        # lowering c iteratively
        t = 1.001
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,5,8,8,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,5,8,5,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,5,8,5,RaisetoLower)
        @test LoweredPoly.X[1:100,1:100] ≈ RaisetoCompare.X[1:100,1:100]
    end

    @testset "Lower all by 1" begin
        t = 1.13
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,15,13,16,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,14,12,15,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,14,12,15,RaisetoLower)
        @test LoweredPoly.X[1:200,1:200] ≈ RaisetoCompare.X[1:200,1:200]
    end

    @testset "Lower all by 2" begin
        t = 1.102
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,15,13,16,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,13,11,14,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,13,11,14,RaisetoLower)
        @test LoweredPoly.X[1:200,1:200] ≈ RaisetoCompare.X[1:200,1:200]
    end

    @testset "Mixed lowering" begin
        # mixed a and c lowering
        t = 1.212
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,15,3,6,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,12,3,4,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,12,3,4,RaisetoLower)
        @test LoweredPoly.X[1:100,1:100] ≈ RaisetoCompare.X[1:100,1:100]
        # mixed b and c lowering
        t = 1.11
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,7,16,8,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,7,15,5,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,7,15,5,RaisetoLower)
        @test LoweredPoly.X[1:100,1:100] ≈ RaisetoCompare.X[1:100,1:100]
        # mixed a and b lowering
        t = 1.1
        PLegendre = SemiclassicalJacobi(t,0,0,0)
        RaisetoLower = SemiclassicalJacobi(t,5,18,8,PLegendre)
        RaisetoCompare = SemiclassicalJacobi(t,4,17,8,PLegendre)
        LoweredPoly = SemiclassicalJacobi(t,4,17,8,RaisetoLower)
        @test LoweredPoly.X[1:100,1:100] ≈ RaisetoCompare.X[1:100,1:100]
    end

    @testset "Higher order stress tests" begin
        @testset "lowering a" begin
            t = 1.0001
            @test symlowered_jacobimatrix(SemiclassicalJacobi(t,10,10,5,SemiclassicalJacobi(t,0,0,0)),:a)[1:500,1:500] ≈ jacobimatrix(SemiclassicalJacobi(t,9,10,5,SemiclassicalJacobi(t,0,0,0)))[1:500,1:500]
        end
        @testset "lowering b" begin
            t = 3.1
            @test symlowered_jacobimatrix(SemiclassicalJacobi(t,6,6,6,SemiclassicalJacobi(t,0,0,0)),:b)[1:500,1:500]≈ jacobimatrix(SemiclassicalJacobi(t,6,5,6,SemiclassicalJacobi(t,0,0,0)))[1:500,1:500]
            t = 1.11
            @test symlowered_jacobimatrix(SemiclassicalJacobi(t,5,7,4,SemiclassicalJacobi(t,0,0,0)),:b)[1200,1200]≈ jacobimatrix(SemiclassicalJacobi(t,5,6,4,SemiclassicalJacobi(t,0,0,0)))[1200,1200]
        end
        @testset "lowering c" begin
            t = 1.1
            @test symlowered_jacobimatrix(SemiclassicalJacobi(t,2,3,4,SemiclassicalJacobi(t,0,0,0)),:c)[1:500,1:500]≈ jacobimatrix(SemiclassicalJacobi(t,2,3,3,SemiclassicalJacobi(t,0,0,0)))[1:500,1:500]
            t = 1.00001
            @test symlowered_jacobimatrix(SemiclassicalJacobi(t,12,4,7,SemiclassicalJacobi(t,0,0,0)),:c)[1:500,1:500] ≈ jacobimatrix(SemiclassicalJacobi(t,12,4,6,SemiclassicalJacobi(t,0,0,0)))[1:500,1:500]
        end
    end
end


@testset "Kronecker graphs" begin
    P1 = [0.1 0.0 0.0;
          0.3 0.4 0.5;
          1.0 0.7 0.2]

    P2 = [0.1 0.1 0.0;
          0.3 0.4 0.6;
          1.0 0.2 0.2]

    P = P1 ⊗ P2

    P4order = kronecker(P1, 4)

    @testset "Naive sample" begin
          G = naivesample(P)
          @test G isa AbstractSparseMatrix
          @test size(G) == size(P)
          @test G[9, 1]  # probability of 1
          @test !G[2, 5]  # probability of 0
    end

    @testset "Fast sample" begin
          G = fastsample(P)
          @test G isa AbstractSparseMatrix
          @test size(G) == size(P)
          # exact sample does not always generate this, so don't test
          #@test G[9, 1]  # probability of 1
          @test !G[2, 5]  # probability of 0
    end

    @testset "helpers" begin
         @test isprob(P1)
         @test !isprob([2 .01; 0.2 0])
         @test isprob(P)
         @test isprob(P4order)

         indices = sampleindices(P1, 100)
         @test all(maximum(indices) .<= size(P1))
         for ind in indices
               @test P1[ind...] > 0
         end

         indices = sampleindices(P, 100)
         @test all(maximum(indices) .<= size(P))
         for ind in indices
               @test P[ind...] > 0
         end

         indices = sampleindices(P4order, 1000)
         @test all(maximum(indices) .<= size(P4order))
         for ind in indices
               @test P4order[ind...] > 0
         end
   end

end


"""
hadamarddev(data, rate; [frequency=false], [overlapping=true], [taus=Octave])
Calculates the hadamard deviation

#parameters:
* `<data>`:			The data array to calculate the deviation from either as as phases or frequencies.
* `<rate>`:			The rate of the data given.
* `[frequency]`:		True if `data` contains frequency data otherwise (default) phase data is assumed.
* `[overlapping]`:	True (default) to calculate overlapping deviation, false otherwise.
* `[taus]`:			Taus to calculate the deviation at. This can either be an AllanTauDescriptor type `AllTaus, Decadade, HalfDecade, Octave, HalfOctave, QuarterOctave`, an array of taus to calculate at, a float number to build a custom log-scale on or an integer to build a specific number of log spaced points.

#returns: named tupple (tau, deviation, error, count)
* `tau`:		Taus which where used.
* `deviation`:	Deviations calculated.
* `error`:		Respective errors.
* `count`:		Number of contributing terms for each deviation.
"""
function hadamarddev(
		data::Array{T, 1},
		rate::AbstractFloat;
		frequency::Bool = false,
		overlapping::Bool = true,
		taus::Union{Type{U}, Integer, AbstractFloat, Array{Float64}} = 192) where {T, U <: AllanTauDescriptor}

	#frequency to phase calculation
	if frequency
		data = frequencytophase(data, rate)
	end

	n = length(data)
	if n < 5
		error("Length for `data` in hadamarddev must be at least 5 or greater")
	end

	#tau calculations
	m = taudescription_to_m(taus, rate, n)

	dev = zeros(T, length(m)) #hadamarddeviation
	deverr = zeros(T, length(m)) #hadamarddeviation error
	devcount = zeros(Int, length(m)) #sum term count

	mStride = 1 #overlapping - can be overwritten in loop for consecutive
	@inbounds for (index, τ) in enumerate(m)

		if !overlapping #overwrite stride for consecutive operation
			mStride = τ
		end

		#hadamard deviation: http://www.leapsecond.com/tools/adev_lib.c
		sum = zero(T)
		i = 1
		terms = 0
		while (i + 3 * τ) <= n
			v = data[i + 3 * τ] - 3 * data[i + 2 * τ] + 3 * data[i + τ] - data[i]
			sum += v * v
			i += mStride
			terms += 1
		end
		if terms <= 1 #break the tau loop if no contribution with term-count > 1 is done
			break
		end

		dev[index] = sqrt(sum / (6 * terms)) / τ * rate
		deverr[index] = dev[index] / sqrt(terms)
		devcount[index] = terms
	end

	selector = devcount .> 1 #select only entries, where 2 or more terms contributed to the deviation
	(tau = m[selector] ./ rate, deviation = dev[selector], error = deverr[selector], count = devcount[selector])
end


const obsnames = ["Energy", "Energy^2", "Specific Heat",
                  "|Magnetization|", "Magnetization^2", "Magnetization^4",
                  "Susceptibility", "Connected Susceptibility", "Binder Ratio",
                 ]
const Ns = [2,8]
const Qs = [2,4]
const J = 1.0
const Ts = [0.3, 1.0, 3.0, 10.0]

"""
exact calculation for `Q` state Potts model on `N` site fully connected network
"""
function exact(Q, J, N, Ts)
    j = J/N
    Es = zeros(0)
    Ms = zeros(0)
    spins = zeros(Int, N)
    nst = Q^N
    for ist in 0:(nst-1)
        spins .= 1
        for i in 1:N
            spins[i] = mod(ist,Q)+1
            ist = div(ist,Q)
        end
        E = 0.0
        M = 0.0
        Qinv = 1.0/Q
        nq = count(s->s==1, spins)
        E -= 0.5j*(nq*(nq-1))
        M += nq*(1.0-Qinv)
        for q in 2:Q
            nq = count(s->s==q, spins)
            E -= 0.5j*(nq*(nq-1))
            M -= nq*Qinv
        end
        M /= N
        push!(Es, E)
        push!(Ms, M)
    end
    sorted = sortperm(Es,rev=true)
    E0 = Es[sorted[end]]

    mbetas = Ts .\ (-1.0)
    nT = length(Ts)
    res = Dict(n=>zeros(nT) for n in obsnames)
    res["Magnetization"] = zeros(nT)
    Zs = zeros(nT)
    for i in sorted
        E = Es[i] - E0
        zs = exp.(mbetas.*E)
        Zs .+= zs
        E += E0
        res["Energy"] .+= zs.*(E/N)
        res["Energy^2"] .+= zs.*(E/N)^2

        M = Ms[i]
        res["Magnetization"] .+= zs.*M
        res["|Magnetization|"] .+= zs.*abs(M)
        res["Magnetization^2"] .+= zs.*M^2
        res["Magnetization^4"] .+= zs.*M^4
    end
    res["Magnetization"] ./= Zs
    for n in obsnames
        res[n] ./= Zs
    end
    return res
end

betas = 1.0 ./ Ts

for N in Ns
    for Q in Qs
        res = exact(Q,J,N,Ts)
        res["Specific Heat"] = N.*(res["Energy^2"] .- res["Energy"].^2).*betas.^2
        res["Susceptibility"] = N.*(res["Magnetization^2"] .- res["Magnetization"].^2).*betas
        res["Connected Susceptibility"] = N.*(res["Magnetization^2"] .- res["|Magnetization|"].^2).*betas
        res["Binder Ratio"] = res["Magnetization^4"] ./ (res["Magnetization^2"].^2)
        open(@sprintf("Q_%d__J_%.1f__N_%d.dat", Q, J, N), "w") do io
            for i in 1:length(Ts)
                @printf(io, "%.15f", Ts[i])
                for n in obsnames
                    @printf(io, " %.15f", res[n][i])
                end
                println(io)
            end
        end
    end
end


# *******************************************
#  Elementary Functions
# *******************************************
const ELEMFUNC = (
    :sin, :cos, :tan, :cot, :sec, :csc,
    :sinh, :cosh, :tanh, :coth, :sech, :csch,
    :asin, :acos, :atan, :acot, :asec, :acsc,
    :asinh, :acosh, :atanh, :acoth, :asech, :acsch,
    :sinpi, :cospi, :sinc, :cosc,
    :exp, :log, :sqrt, :cbrt,
    :abs
)

for fn = ELEMFUNC
    @eval begin
        Base.$fn(f::XFunc) = XFunc($fn.(f.vals), f.config)
        Base.$fn(f::KFunc) = KFunc($fn.(f.vals), f.config)
    end
end

# *******************************************
#  Analysis Tools
# *******************************************
function dVgen(config::ConfigFFT)
    xlens = (x -> -(.-(x...))).(config.xranges)
    return prod(xlens ./ config.ngrids)
end

function integ_X(f::XFunc)
    sum(f) * dVgen(f.config)
end

∫(f) = integ_X(f)

function norm_X(f::XFunc, p::Real=2)

    if p == Inf
        return max(abs(f)...)
    else
        return ( ∫( abs(f)^p ) )^(1/p)
    end

end

function l2inpr_X_X(f::XFunc{T,N}, g::XFunc{T,N}) where {T,N}
    check_config_consistency(f.config, g.config)
    return ∫(f * g)
end

# *******************************************
#  Supplimental Tools
# *******************************************
function xlens_xranges(xranges)
    (x -> -(-(x...))).(xranges)
end


#=

  Euler #3 in Julia.

  Problem 3
  """
  The prime factors of 13195 are 5, 7, 13 and 29.
  What is the largest prime factor of the number 600851475143 ?
  """

  This Julia program was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

include("Euler.jl")

# 0.00354527s
function euler3a()
    n = 600851475143
    max = 0
    while (n > 1)
        m = round(Int,ceil(sqrt(n)))
        for i in 2:m
            if (n % i == 0 && isPrime(i))
                max = i;
                n /= i;
                continue;
            end
        end
    end
    return max;

end

# 0.00607204s
function euler3b()
    return maximum(prime_divisors(600851475143))
end

# 0.00169180s
function euler3c()
    return maximum(factors(600851475143))
end

# run_euler(euler3a)
# run_euler(euler3b)
run_euler(euler3c)


function get_grids1()

    r = ( 1/β )
    w = 1/(Lₛ * r * 𝜋)
    t = (1 - Lₛ) * ϐ / ( Lₛ * w)
    loan_max =  (w * (1 - t) - ϐ) / ((r - 1) * w * (1 - t))

    loan_grid_points = Int(32)

    b = 0.25
    a = ( - log( loan_max ) + log( b )) / ( ( loan_grid_points - 1 ) * log( 1 - δᶠ))

    function GridGen(x::Int64,a_::Float64=a, b_::Float64=b)

        return (b_ * ( (1-δᶠ )^(-a_*x) ) )

    end

    loan_grid = Vector{Float64}([0.0])
    push!(loan_grid, [ GridGen(x) for x in 1:(loan_grid_points-1) ]...)

    depo_grid_points = Int(32)
    depo_grid = Vector{Float64}([0.0])
    push!(depo_grid, [ GridGen(x) for x in 1:(depo_grid_points-1) ]...)

    return(loan_grid, depo_grid)

end

function get_grids2(x::Int)

    r = ( 1/β )
    w = 1/(Lₛ * r * 𝜋)
    t = (1 - Lₛ) * ϐ / ( Lₛ * w)
    loan_max =  (w * (1 - t) - ϐ) / ((r - 1) ) #* w * (1 - t))
    depo_max = loan_max
    loan_grid_points = Int(x)

    # "distance between two neighbouring grid points. Used in utility function"
    𝔤 = loan_max / (loan_grid_points-1)
    loan_grid = 0:𝔤:loan_max

    # loan_grid = (0:(loan_grid_points - 1))*(loan_max/(loan_grid_points - 1))
    depo_grid_points = Int(x)

    𝔤 = depo_max / (depo_grid_points-1)
    depo_grid = 0:𝔤:loan_max

    return(loan_grid, depo_grid)

end

function get_grids3()

    r = ( 1/β )
    w = 1/(Lₛ * r * 𝜋)
    t = (1 - Lₛ) * ϐ / ( Lₛ * w)
    loan_max = 1.16 * (w * (1 - t) - ϐ) / ((r - 1) * w * (1 - t))
    depo_max = loan_max
    loan_min =  68.0
    depo_min = loan_min
    loan_grid_points = Int(32)

    loan_grid = [0,collect( range(loan_min, loan_max, length = (loan_grid_points) ))... ]

    # loan_grid = (0:(loan_grid_points - 1))*(loan_max/(loan_grid_points - 1))
    depo_grid_points = Int(32)

    depo_grid = [0,collect( range(loan_min, depo_max, length = (depo_grid_points) ))... ]

    return(loan_grid, depo_grid)

end

loan_grid, depo_grid = get_grids2(32)
#

## Set starting prices (vector Real):

## Define Grid. Run if have any questions (uncomment first):
# ?help GridDef
## make use of build in function from HANS_utilities:

grid_def = make_dimmensions( ["d", "l","λᵢ"], [ depo_grid , loan_grid, Int.([1, 2]) ])


module Pagano_examples
using FinEtools
using FinEtoolsDeforLinear
using FinEtoolsDeforLinear.AlgoDeforLinearModule
using FinEtools.MeshUtilModule
using FinEtools.AlgoBaseModule
using Statistics: mean

function Pagano_3lay_cyl_bend_MST10_conv()
    elementtag = "MST10"
    println("""
    Pagano 3layer cylindrical bending: $(elementtag)
    """)

    # This example provides three-dimensional finite element model for  the
    # transverse shear stress calculations. The problem consists of a one-, two- or
    # three-layer plate subjected to a sinusoidal distributed load, as
    # described by Pagano (1969). The resulting transverse shear and axial
    # stresses through the thickness of the plate are compared to two existing
    # analytical solutions by Pagano (1969). The first solution is derived from
    # classical laminated plate theory (CPT), while the second is an exact
    # solution from linear elasticity theory.

    for (extrap, nodevalmeth) = zip([:extrapmean, :extraptrend, :default], [:averaging, :averaging, :invdistance])
        for Span_to_thickness = [4, 50]

            filebase = "Pagano_3lay_cyl_bend_conv_$(elementtag)_$(Span_to_thickness)_$(extrap)"
            modeldatasequence = FDataDict[]
            for Refinement = [1, 2, 4, 8]

                # Orthotropic material for the 3 layers
                E1 = 25e6*phun("PSI"); E2 = 1e6*phun("PSI"); E3 = E2;
                G12 = 0.5e6*phun("PSI");  G13 = G12; G23 = 0.2e6*phun("PSI")
                nu12 =  0.25; nu13 =  0.25; nu23 =  0.25;
                T = 2.5*phun("in"); # total thickness of the plate
                L = Span_to_thickness*T;
                h = 1.0/Refinement*phun("in");  # depth of the plate
                q0 = 1*phun("PSI")
                CTE1 =  CTE2 =  CTE3 = 0.0

                # Here we define the layout and the thicknesses of the layers.
                angles = vec([0.0 90.0 0.0]);
                nLayers = length(angles)
                ts = T/nLayers * ones(nLayers); # layer thicknesses

                tolerance = 0.0001*T

                # Select how fine the mesh should be
                nL, nh = Refinement * 2 * 6, 1;
                nts= Refinement * 2 * ones(Int, nLayers);# number of elements per layer

                xs = collect(linearspace(0.0, L, nL+1))
                ys = collect(linearspace(0.0, h, nh+1))
                fens,fes = T10layeredplatex(xs, ys, ts, nts)
                display(typeof(fes))
                println("Mesh: nL, nh, nts = $nL, $nh, $nts")
                println("count(fens) = $(count(fens))")

                # This is the material  model
                MR = DeforModelRed3D
                skinmaterial = MatDeforElastOrtho(MR,
                0.0, E1, E2, E3,
                nu12, nu13, nu23,
                G12, G13, G23,
                CTE1, CTE2, CTE3)

                # The material coordinate system function is defined as:
                function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
                    rotmat3!(csmatout, angles[fe_label]/180.0*pi* [0.0; 0.0; 1.0]);
                end

                # The vvolume integrals are evaluated using this rule
                gr = SimplexRule(3, 4)

                # We will create 3 regions, one for each of the layers
                regions = FDataDict[]
                for layer = 1:nLayers
                    rls = selectelem(fens, fes, label =  layer)
                    push!(regions, FDataDict("femm"=>FEMMDeforLinearMST10(MR, IntegDomain(subset(fes, rls), gr), CSys(3, 3, updatecs!), skinmaterial)))
                end

                # The essential boundary conditions are applied to enforce the plane strain constraint.
                ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
                lyh = selectnode(fens, box=[-Inf Inf h h -Inf Inf], inflate=tolerance)
                ey = FDataDict("displacement"=>  0.0, "component"=> 2, "node_list"=>vcat(ly0, lyh))
                # The transverse displacement is fixed at the two ends.
                lz0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
                lzL = selectnode(fens, box=[L L -Inf Inf -Inf Inf], inflate=tolerance)
                ez = FDataDict("displacement"=>  0.0, "component"=> 3, "node_list"=>vcat(lz0, lzL))
                ex = FDataDict("displacement"=>  0.0, "component"=> 1, "node_list"=>[1])

                # The traction boundary condition is applied at the top of the plate.
                bfes = meshboundary(fes)
                function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                    forceout[1] = 0.0
                    forceout[2] = 0.0
                    forceout[3] = -q0*sin(pi*XYZ[1]/L)
                    return forceout
                end
                # From  the entire boundary we select those quadrilaterals that lie on the plane
                # Z = thickness
                tl = selectelem(fens, bfes, box = [-Inf Inf -Inf Inf T T], inflate=tolerance)
                Trac = FDataDict("traction_vector"=>pfun,
                "femm"=>FEMMBase(IntegDomain(subset(bfes, tl), SimplexRule(2, 3))))

                modeldata = FDataDict("fens"=>fens,
                "regions"=>regions,
                "essential_bcs"=>[ex, ey, ez],
                "traction_bcs"=> [Trac]
                )
                modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

                # modeldata["postprocessing"] = FDataDict("file"=>filebase * "-u")
                # modeldata = AlgoDeforLinearModule.exportdeformation(modeldata)

                u = modeldata["u"]
                geom = modeldata["geom"]

                # The results of the displacement and stresses will be reported at
                # nodes located at the appropriate points.
                ntopcenter = selectnode(fens, box=[L/2 L/2 0.0 h T T], inflate=tolerance)
                ncenterline = selectnode(fens, box=[L/2 L/2 0.0 0.0 0.0 T], inflate=tolerance)
                nx0line = selectnode(fens, box=[0.0 0.0 0.0 0.0 0.0 T], inflate=tolerance)

                zclo = sortperm(vec(geom.values[ncenterline, 3]))
                ncenterline = ncenterline[zclo]
                centerz = geom.values[ncenterline, 3]

                println("Top Center deflection: $(mean(u.values[ntopcenter, 3], dims = 1)/phun("in")) [in]")

                #  Compute  all stresses
                modeldata["postprocessing"] = FDataDict("file"=>filebase * "-s", "quantity"=>:Cauchy, "component"=>collect(1:6), "outputcsys"=>CSys(3), "nodevalmethod"=>nodevalmeth, "reportat"=>extrap)
                modeldata = AlgoDeforLinearModule.exportstress(modeldata)

                modeldata["elementsize"] = T/Refinement
                modeldata["geometricaltolerance"] = tolerance
                push!(modeldatasequence, modeldata)
            end # for refinement

            println("")
            println("Stress RMS error")
            for md = modeldatasequence
                md["targetfields"] = [e["field"] for e in md["postprocessing"]["exported"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Stress" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            println("")
            println("Displacement RMS error")
            for md = modeldatasequence
                md["targetfields"] = [md["u"] for r in md["regions"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Displ" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            # @async run(`"paraview.exe" $csvFile`)
        end # Span_to_thickness
    end # extrap
    println("Done")

end # Pagano_3lay_cyl_bend_MST10_conv

function Pagano_3lay_cyl_bend_H8_conv()
    elementtag = "H8"
    println("""
    Pagano 3layer cylindrical bending: $(elementtag)
    """)

    # This example provides three-dimensional finite element model for  the
    # transverse shear stress calculations. The problem consists of a one-, two- or
    # three-layer plate subjected to a sinusoidal distributed load, as
    # described by Pagano (1969). The resulting transverse shear and axial
    # stresses through the thickness of the plate are compared to two existing
    # analytical solutions by Pagano (1969). The first solution is derived from
    # classical laminated plate theory (CPT), while the second is an exact
    # solution from linear elasticity theory.

    for (extrap, nodevalmeth) = zip([:extrapmean, :extraptrend, :default], [:averaging, :averaging, :invdistance])
        for Span_to_thickness = [4, 50]

            filebase = "Pagano_3lay_cyl_bend_conv_$(elementtag)_$(Span_to_thickness)_$(extrap)"
            modeldatasequence = FDataDict[]
            for Refinement = [1, 2, 4, 8]

                # Orthotropic material for the 3 layers
                E1 = 25e6*phun("PSI"); E2 = 1e6*phun("PSI"); E3 = E2;
                G12 = 0.5e6*phun("PSI");  G13 = G12; G23 = 0.2e6*phun("PSI")
                nu12 =  0.25; nu13 =  0.25; nu23 =  0.25;
                T = 2.5*phun("in"); # total thickness of the plate
                L = Span_to_thickness*T;
                h = 1.0/Refinement*phun("in");  # depth of the plate
                q0 = 1*phun("PSI")
                CTE1 =  CTE2 =  CTE3 = 0.0

                # Here we define the layout and the thicknesses of the layers.
                angles = vec([0.0 90.0 0.0]);
                nLayers = length(angles)
                ts = T/nLayers * ones(nLayers); # layer thicknesses

                tolerance = 0.0001*T

                # Select how fine the mesh should be
                nL, nh = Refinement * 2 * 6, 1;
                nts= Refinement * 2 * ones(Int, nLayers);# number of elements per layer

                xs = collect(linearspace(0.0, L, nL+1))
                ys = collect(linearspace(0.0, h, nh+1))
                fens,fes = H8layeredplatex(xs, ys, ts, nts)

                println("Mesh: nL, nh, nts = $nL, $nh, $nts")
                println("count(fens) = $(count(fens))")

                # This is the material  model
                MR = DeforModelRed3D
                skinmaterial = MatDeforElastOrtho(MR,
                0.0, E1, E2, E3,
                nu12, nu13, nu23,
                G12, G13, G23,
                CTE1, CTE2, CTE3)

                # The material coordinate system function is defined as:
                function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
                    rotmat3!(csmatout, angles[fe_label]/180.0*pi* [0.0; 0.0; 1.0]);
                end

                # The vvolume integrals are evaluated using this rule
                gr = GaussRule(3, 2)

                # We will create 3 regions, one for each of the layers
                regions = FDataDict[]
                for layer = 1:nLayers
                    rls = selectelem(fens, fes, label =  layer)
                    push!(regions, FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rls), gr), CSys(3, 3, updatecs!), skinmaterial)))
                end

                # The essential boundary conditions are applied to enforce the plane strain constraint.
                ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
                lyh = selectnode(fens, box=[-Inf Inf h h -Inf Inf], inflate=tolerance)
                ey = FDataDict("displacement"=>  0.0, "component"=> 2, "node_list"=>vcat(ly0, lyh))
                # The transverse displacement is fixed at the two ends.
                lz0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
                lzL = selectnode(fens, box=[L L -Inf Inf -Inf Inf], inflate=tolerance)
                ez = FDataDict("displacement"=>  0.0, "component"=> 3, "node_list"=>vcat(lz0, lzL))
                ex = FDataDict("displacement"=>  0.0, "component"=> 1, "node_list"=>[1])

                # The traction boundary condition is applied at the top of the plate.
                bfes = meshboundary(fes)
                function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                    forceout[1] = 0.0
                    forceout[2] = 0.0
                    forceout[3] = -q0*sin(pi*XYZ[1]/L)
                    return forceout
                end
                # From  the entire boundary we select those quadrilaterals that lie on the plane
                # Z = thickness
                tl = selectelem(fens, bfes, box = [-Inf Inf -Inf Inf T T], inflate=tolerance)
                Trac = FDataDict("traction_vector"=>pfun,                "femm"=>FEMMBase(IntegDomain(subset(bfes, tl), GaussRule(2, 2))))

                modeldata = FDataDict("fens"=>fens, "regions"=>regions, "essential_bcs"=>[ex, ey, ez], "traction_bcs"=> [Trac])
                modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

                # modeldata["postprocessing"] = FDataDict("file"=>filebase * "-u")
                # modeldata = AlgoDeforLinearModule.exportdeformation(modeldata)

                u = modeldata["u"]
                geom = modeldata["geom"]

                # The results of the displacement and stresses will be reported at
                # nodes located at the appropriate points.
                ntopcenter = selectnode(fens, box=[L/2 L/2 0.0 h T T], inflate=tolerance)
                ncenterline = selectnode(fens, box=[L/2 L/2 0.0 0.0 0.0 T], inflate=tolerance)
                nx0line = selectnode(fens, box=[0.0 0.0 0.0 0.0 0.0 T], inflate=tolerance)

                zclo = sortperm(vec(geom.values[ncenterline, 3]))
                ncenterline = ncenterline[zclo]
                centerz = geom.values[ncenterline, 3]

                println("Top Center deflection: $(mean(u.values[ntopcenter, 3], dims = 1)/phun("in")) [in]")

                #  Compute  all stresses
                modeldata["postprocessing"] = FDataDict("file"=>filebase * "-s",
                "quantity"=>:Cauchy, "component"=>collect(1:6), "outputcsys"=>CSys(3),
                "nodevalmethod"=>nodevalmeth, "reportat"=>extrap)
                modeldata = AlgoDeforLinearModule.exportstress(modeldata)

                modeldata["elementsize"] = T/Refinement
                modeldata["geometricaltolerance"] = tolerance
                push!(modeldatasequence, modeldata)
            end # for refinement

            println("")
            println("Stress RMS error")
            for md = modeldatasequence
                md["targetfields"] = [e["field"] for e in md["postprocessing"]["exported"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Stress" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            println("")
            println("Displacement RMS error")
            for md = modeldatasequence
                md["targetfields"] = [md["u"] for r in md["regions"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Displ" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            # @async run(`"paraview.exe" $csvFile`)
        end # Span_to_thickness
    end # extrap
    println("Done")

end # Pagano_3lay_cyl_bend_H8_conv

function Pagano_3lay_cyl_bend_MSH8_conv()
    elementtag = "MSH8"
    println("""
    Pagano 3layer cylindrical bending: $(elementtag)
    """)

    # This example provides three-dimensional finite element model for  the
    # transverse shear stress calculations. The problem consists of a one-, two- or
    # three-layer plate subjected to a sinusoidal distributed load, as
    # described by Pagano (1969). The resulting transverse shear and axial
    # stresses through the thickness of the plate are compared to two existing
    # analytical solutions by Pagano (1969). The first solution is derived from
    # classical laminated plate theory (CPT), while the second is an exact
    # solution from linear elasticity theory.

    for (extrap, nodevalmeth) = zip([:extrapmean, :extraptrend, :default], [:averaging, :averaging, :invdistance])
        for Span_to_thickness = [4, 50]

            filebase = "Pagano_3lay_cyl_bend_conv_$(elementtag)_$(Span_to_thickness)_$(extrap)"
            modeldatasequence = FDataDict[]
            for Refinement = [1, 2, 4, 8]

                # Orthotropic material for the 3 layers
                E1 = 25e6*phun("PSI"); E2 = 1e6*phun("PSI"); E3 = E2;
                G12 = 0.5e6*phun("PSI");  G13 = G12; G23 = 0.2e6*phun("PSI")
                nu12 =  0.25; nu13 =  0.25; nu23 =  0.25;
                T = 2.5*phun("in"); # total thickness of the plate
                L = Span_to_thickness*T;
                h = 1.0/Refinement*phun("in");  # depth of the plate
                q0 = 1*phun("PSI")
                CTE1 =  CTE2 =  CTE3 = 0.0

                # Here we define the layout and the thicknesses of the layers.
                angles = vec([0.0 90.0 0.0]);
                nLayers = length(angles)
                ts = T/nLayers * ones(nLayers); # layer thicknesses

                tolerance = 0.0001*T

                # Select how fine the mesh should be
                nL, nh = Refinement * 2 * 6, 1;
                nts= Refinement * 2 * ones(Int, nLayers);# number of elements per layer

                xs = collect(linearspace(0.0, L, nL+1))
                ys = collect(linearspace(0.0, h, nh+1))
                fens,fes = H8layeredplatex(xs, ys, ts, nts)

                println("Mesh: nL, nh, nts = $nL, $nh, $nts")
                println("count(fens) = $(count(fens))")

                # This is the material  model
                MR = DeforModelRed3D
                skinmaterial = MatDeforElastOrtho(MR,
                0.0, E1, E2, E3,
                nu12, nu13, nu23,
                G12, G13, G23,
                CTE1, CTE2, CTE3)

                # The material coordinate system function is defined as:
                function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
                    rotmat3!(csmatout, angles[fe_label]/180.0*pi* [0.0; 0.0; 1.0]);
                end

                # The vvolume integrals are evaluated using this rule
                gr = GaussRule(3, 2)

                # We will create 3 regions, one for each of the layers
                regions = FDataDict[]
                for layer = 1:nLayers
                    rls = selectelem(fens, fes, label =  layer)
                    push!(regions, FDataDict("femm"=>FEMMDeforLinearMSH8(MR, IntegDomain(subset(fes, rls), gr), CSys(3, 3, updatecs!), skinmaterial)))
                end

                # The essential boundary conditions are applied to enforce the plane strain constraint.
                ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
                lyh = selectnode(fens, box=[-Inf Inf h h -Inf Inf], inflate=tolerance)
                ey = FDataDict("displacement"=>  0.0, "component"=> 2, "node_list"=>vcat(ly0, lyh))
                # The transverse displacement is fixed at the two ends.
                lz0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
                lzL = selectnode(fens, box=[L L -Inf Inf -Inf Inf], inflate=tolerance)
                ez = FDataDict("displacement"=>  0.0, "component"=> 3, "node_list"=>vcat(lz0, lzL))
                ex = FDataDict("displacement"=>  0.0, "component"=> 1, "node_list"=>[1])

                # The traction boundary condition is applied at the top of the plate.
                bfes = meshboundary(fes)
                function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                    forceout[1] = 0.0
                    forceout[2] = 0.0
                    forceout[3] = -q0*sin(pi*XYZ[1]/L)
                    return forceout
                end
                # From  the entire boundary we select those quadrilaterals that lie on the plane
                # Z = thickness
                tl = selectelem(fens, bfes, box = [-Inf Inf -Inf Inf T T], inflate=tolerance)
                Trac = FDataDict("traction_vector"=>pfun, "femm"=>FEMMBase(IntegDomain(subset(bfes, tl), GaussRule(2, 2))))

                modeldata = FDataDict("fens"=>fens, "regions"=>regions, "essential_bcs"=>[ex, ey, ez], "traction_bcs"=> [Trac])
                modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

                # modeldata["postprocessing"] = FDataDict("file"=>filebase * "-u")
                # modeldata = AlgoDeforLinearModule.exportdeformation(modeldata)

                u = modeldata["u"]
                geom = modeldata["geom"]

                # The results of the displacement and stresses will be reported at
                # nodes located at the appropriate points.
                ntopcenter = selectnode(fens, box=[L/2 L/2 0.0 h T T], inflate=tolerance)
                ncenterline = selectnode(fens, box=[L/2 L/2 0.0 0.0 0.0 T], inflate=tolerance)
                nx0line = selectnode(fens, box=[0.0 0.0 0.0 0.0 0.0 T], inflate=tolerance)

                zclo = sortperm(vec(geom.values[ncenterline, 3]))
                ncenterline = ncenterline[zclo]
                centerz = geom.values[ncenterline, 3]

                println("Top Center deflection: $(mean(u.values[ntopcenter, 3], dims = 1)/phun("in")) [in]")

                #  Compute  all stresses
                modeldata["postprocessing"] = FDataDict("file"=>filebase * "-s", "quantity"=>:Cauchy, "component"=>collect(1:6), "outputcsys"=>CSys(3), "nodevalmethod"=>nodevalmeth, "reportat"=>extrap)
                modeldata = AlgoDeforLinearModule.exportstress(modeldata)

                modeldata["elementsize"] = T/Refinement
                modeldata["geometricaltolerance"] = tolerance
                push!(modeldatasequence, modeldata)
            end # for refinement

            println("")
            println("Stress RMS error")
            for md = modeldatasequence
                md["targetfields"] = [e["field"] for e in md["postprocessing"]["exported"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Stress" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            println("")
            println("Displacement RMS error")
            for md = modeldatasequence
                md["targetfields"] = [md["u"] for r in md["regions"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Displ" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            # @async run(`"paraview.exe" $csvFile`)
        end # Span_to_thickness
    end # extrap
    println("Done")

end # Pagano_3lay_cyl_bend_MSH8_conv

function Pagano_3lay_cyl_bend_T10_conv()
    elementtag = "T10"
    println("""
    Pagano 3layer cylindrical bending: $(elementtag)
    """)

    # This example provides three-dimensional finite element model for  the
    # transverse shear stress calculations. The problem consists of a one-, two- or
    # three-layer plate subjected to a sinusoidal distributed load, as
    # described by Pagano (1969). The resulting transverse shear and axial
    # stresses through the thickness of the plate are compared to two existing
    # analytical solutions by Pagano (1969). The first solution is derived from
    # classical laminated plate theory (CPT), while the second is an exact
    # solution from linear elasticity theory.

    for (extrap, nodevalmeth) = zip([:default], [:invdistance])
        for Span_to_thickness = [4, 50]

            filebase = "Pagano_3lay_cyl_bend_conv_$(elementtag)_$(Span_to_thickness)_$(extrap)"
            modeldatasequence = FDataDict[]
            for Refinement = [1, 2, 4, 8]

                # Orthotropic material for the 3 layers
                E1 = 25e6*phun("PSI"); E2 = 1e6*phun("PSI"); E3 = E2;
                G12 = 0.5e6*phun("PSI");  G13 = G12; G23 = 0.2e6*phun("PSI")
                nu12 =  0.25; nu13 =  0.25; nu23 =  0.25;
                T = 2.5*phun("in"); # total thickness of the plate
                L = Span_to_thickness*T;
                h = 1.0/Refinement*phun("in");  # depth of the plate
                q0 = 1*phun("PSI")
                CTE1 =  CTE2 =  CTE3 = 0.0

                # Here we define the layout and the thicknesses of the layers.
                angles = vec([0.0 90.0 0.0]);
                nLayers = length(angles)
                ts = T/nLayers * ones(nLayers); # layer thicknesses

                tolerance = 0.0001*T

                # Select how fine the mesh should be
                nL, nh = Refinement * 2 * 6, 1;
                nts= Refinement * 2 * ones(Int, nLayers);# number of elements per layer

                xs = collect(linearspace(0.0, L, nL+1))
                ys = collect(linearspace(0.0, h, nh+1))
                fens,fes = T10layeredplatex(xs, ys, ts, nts)

                println("Mesh: nL, nh, nts = $nL, $nh, $nts")
                println("count(fens) = $(count(fens))")

                # This is the material  model
                MR = DeforModelRed3D
                skinmaterial = MatDeforElastOrtho(MR,
                0.0, E1, E2, E3,
                nu12, nu13, nu23,
                G12, G13, G23,
                CTE1, CTE2, CTE3)

                # The material coordinate system function is defined as:
                function updatecs!(csmatout::FFltMat, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt)
                    rotmat3!(csmatout, angles[fe_label]/180.0*pi* [0.0; 0.0; 1.0]);
                end

                # The vvolume integrals are evaluated using this rule
                gr = SimplexRule(3, 4)

                # We will create 3 regions, one for each of the layers
                regions = FDataDict[]
                for layer = 1:nLayers
                    rls = selectelem(fens, fes, label =  layer)
                    push!(regions, FDataDict("femm"=>FEMMDeforLinear(MR, IntegDomain(subset(fes, rls), gr), CSys(3, 3, updatecs!), skinmaterial)))
                end

                # The essential boundary conditions are applied to enforce the plane strain constraint.
                ly0 = selectnode(fens, box=[-Inf Inf 0.0 0.0 -Inf Inf], inflate=tolerance)
                lyh = selectnode(fens, box=[-Inf Inf h h -Inf Inf], inflate=tolerance)
                ey = FDataDict("displacement"=>  0.0, "component"=> 2, "node_list"=>vcat(ly0, lyh))
                # The transverse displacement is fixed at the two ends.
                lz0 = selectnode(fens, box=[0.0 0.0 -Inf Inf -Inf Inf], inflate=tolerance)
                lzL = selectnode(fens, box=[L L -Inf Inf -Inf Inf], inflate=tolerance)
                ez = FDataDict("displacement"=>  0.0, "component"=> 3, "node_list"=>vcat(lz0, lzL))
                ex = FDataDict("displacement"=>  0.0, "component"=> 1, "node_list"=>[1])

                # The traction boundary condition is applied at the top of the plate.
                bfes = meshboundary(fes)
                function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                    forceout[1] = 0.0
                    forceout[2] = 0.0
                    forceout[3] = -q0*sin(pi*XYZ[1]/L)
                    return forceout
                end
                # From  the entire boundary we select those quadrilaterals that lie on the plane
                # Z = thickness
                tl = selectelem(fens, bfes, box = [-Inf Inf -Inf Inf T T], inflate=tolerance)
                Trac = FDataDict("traction_vector"=>pfun,
                "femm"=>FEMMBase(IntegDomain(subset(bfes, tl), SimplexRule(2, 3))))

                modeldata = FDataDict("fens"=>fens,
                "regions"=>regions,
                "essential_bcs"=>[ex, ey, ez],
                "traction_bcs"=> [Trac]
                )
                modeldata = AlgoDeforLinearModule.linearstatics(modeldata)

                # modeldata["postprocessing"] = FDataDict("file"=>filebase * "-u")
                # modeldata = AlgoDeforLinearModule.exportdeformation(modeldata)

                u = modeldata["u"]
                geom = modeldata["geom"]

                # The results of the displacement and stresses will be reported at
                # nodes located at the appropriate points.
                ntopcenter = selectnode(fens, box=[L/2 L/2 0.0 h T T], inflate=tolerance)
                ncenterline = selectnode(fens, box=[L/2 L/2 0.0 0.0 0.0 T], inflate=tolerance)
                nx0line = selectnode(fens, box=[0.0 0.0 0.0 0.0 0.0 T], inflate=tolerance)

                zclo = sortperm(vec(geom.values[ncenterline, 3]))
                ncenterline = ncenterline[zclo]
                centerz = geom.values[ncenterline, 3]

                println("Top Center deflection: $(mean(u.values[ntopcenter, 3], dims = 1)/phun("in")) [in]")

                #  Compute  all stresses
                modeldata["postprocessing"] = FDataDict("file"=>filebase * "-s",  "quantity"=>:Cauchy, "component"=>collect(1:6), "outputcsys"=>CSys(3), "nodevalmethod"=>nodevalmeth, "reportat"=>extrap)
                modeldata = AlgoDeforLinearModule.exportstress(modeldata)

                modeldata["elementsize"] = T/Refinement
                modeldata["geometricaltolerance"] = tolerance
                push!(modeldatasequence, modeldata)
            end # for refinement

            println("")
            println("Stress RMS error")
            for md = modeldatasequence
                md["targetfields"] = [e["field"] for e in md["postprocessing"]["exported"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Stress" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            println("")
            println("Displacement RMS error")
            for md = modeldatasequence
                md["targetfields"] = [md["u"] for r in md["regions"]]
            end
            elementsizes, errornorms, p = AlgoBaseModule.evalconvergencestudy(modeldatasequence)

            println("Normalized Approximate Error = $(errornorms)")

            f = log.(vec(errornorms))
            A = hcat(log.(vec(elementsizes[1:end-1])), ones(size(f)))
            p = A \ f
            println("Linear log-log fit: p = $(p)")

            csvFile = filebase * "_Displ" * ".CSV"
            savecsv(csvFile,
            elementsizes=vec(elementsizes[1:end-1]),
            elementsizes2=vec(elementsizes[1:end-1].^2),
            elementsizes3=vec(elementsizes[1:end-1].^3),
            errornorms=vec(errornorms)
            )
            println("Wrote $csvFile")

            # @async run(`"paraview.exe" $csvFile`)
        end # Span_to_thickness
    end # extrap
    println("Done")

end # Pagano_3lay_cyl_bend_T10_conv

function allrun()
    println("#####################################################")
    println("# Pagano_3lay_cyl_bend_MST10_conv ")
    Pagano_3lay_cyl_bend_MST10_conv()
    println("#####################################################")
    println("# Pagano_3lay_cyl_bend_MSH8_conv ")
    Pagano_3lay_cyl_bend_MSH8_conv()
    println("#####################################################")
    println("# Pagano_3lay_cyl_bend_H8_conv ")
    Pagano_3lay_cyl_bend_H8_conv()
    println("#####################################################")
    println("# Pagano_3lay_cyl_bend_MSH8_conv ")
    Pagano_3lay_cyl_bend_MSH8_conv()
    println("#####################################################")
    println("# Pagano_3lay_cyl_bend_T10_conv ")
    Pagano_3lay_cyl_bend_T10_conv()
    return true
end # function allrun

end # module Pagano_examples


@testset "Linear programs ($T)" for T in [Float32, Float64]

    using ProximalOperators
    using ProximalAlgorithms
    using LinearAlgebra

    # Solving LP with AFBA
    #
    #   minimize    c'x             -> f = <c,.>
    #   subject to  Ax = b          -> h = ind{b}, L = A
    #               x >= 0          -> g = ind{>=0}
    #
    # Dual LP
    #
    #   maximize    b'y
    #   subject to  A'y <= c
    #
    # Optimality conditions
    #
    #   x >= 0              [primal feasibility 1]
    #   Ax = b              [primal feasibility 2]
    #   A'y <= c            [dual feasibility]
    #   X'(c - A'y) = 0     [complementarity slackness]
    #

    function assert_lp_solution(c, A, b, x, y, tol)
        # Check and print solution quality measures (for some reason the
        # returned dual iterate is the negative of the dual LP variable y)

        nonneg = -minimum(min.(0.0, x))
        @test nonneg <= tol

        primal_feasibility = norm(A*x - b)
        @test primal_feasibility <= tol

        dual_feasibility = maximum(max.(0.0, -A'*y - c))
        @test dual_feasibility <= tol

        complementarity = abs(dot(c + A'*y, x))
        @test complementarity <= tol
    end

    n = 100 # primal dimension
    m = 80 # dual dimension (i.e. number of linear equalities)
    k = 50 # number of active dual constraints (must be 0 <= k <= n)
    x_star = vcat(rand(T, k), zeros(T, n-k)) # primal optimal point
    s_star = vcat(zeros(T, k), rand(T, n-k)) # dual optimal slack variable
    y_star = randn(T, m) # dual optimal point

    A = randn(T, m, n)
    b = A*x_star
    c = A'*y_star + s_star

    tol = 100*eps(T)
    maxit = 10_000

    @testset "AFBA" begin

        f = Linear(c)
        g = IndNonnegative()
        h = IndPoint(b)

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

        solver = ProximalAlgorithms.AFBA{T}(tol=tol, maxit=maxit)
        x, y, it = solver(x0, y0, f=f, g=g, h=h, L=A)

        @test eltype(x) == T
        @test eltype(y) == T

        @test it <= maxit

        assert_lp_solution(c, A, b, x, y, 1e2*tol)

    end

    @testset "VuCondat" begin

        f = Linear(c)
        g = IndNonnegative()
        h = IndPoint(b)

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

        solver = ProximalAlgorithms.VuCondat(T, tol=tol, maxit=maxit)
        x, y, it = solver(x0, y0, f=f, g=g, h=h, L=A)

        @test eltype(x) == T
        @test eltype(y) == T

        @test it <= maxit

        assert_lp_solution(c, A, b, x, y, 1e2*tol)

    end

    # TODO: add Chambolle-Pock (using separable sum of IndPoint and IndNonnegative)

    @testset "DavisYin" begin

        f = IndAffine(A, b)
        g = IndNonnegative()
        h = Linear(c)

        x0 = zeros(T, n)

        solver = ProximalAlgorithms.DavisYin{T}(gamma=T(1), tol=tol, maxit=maxit)
        xf, xg, it = solver(x0, f=f, g=g, h=h)

        @test eltype(xf) == T
        @test eltype(xg) == T

        @test it <= maxit

        @assert norm(xf - x_star) <= 1e2*tol
        @assert norm(xg - x_star) <= 1e2*tol

    end

end


using Test
using SparseArrays

import sbp
using sbp.Operator: read_operator, to_matrix
using sbp.Grid:grid_xp
using sbp.Test:test_first_derivative, test_quadrature, test_first_derivative_sbp
include("operators.jl")

st = sbp.Strand1994

m = 10

B = spzeros(m, m)
B[1,1] = -1
B[end,end] = 1

@test_throws UndefVarError st.load_descrption(order=10)
desc_D21, desc_H21 = st.load_description(order=2)
desc_D42, desc_H42 = st.load_description(order=4)

@testset "D_21, H_21" begin
        x, h, D, H = st.build_operators(desc_D21, desc_H21, m)
        ml = size(desc_D21.left, 1)
        mr = size(desc_D21.right, 1)
        test_first_derivative(D, x, x, 2, 1, ml, mr)
        test_quadrature(H, x, 1)
        test_first_derivative_sbp(H, D, B)
end

@testset "D_42, H_42" begin
        x, h, D, H = st.build_operators(desc_D42, desc_H42, m)
        ml = size(desc_D42.left, 1)
        mr = size(desc_D42.right, 1)
        test_first_derivative(D, x, x, 4, 2, ml, mr)
        test_quadrature(H, x, 3)
        test_first_derivative_sbp(H, D, B)
end


# call add_procs(n) to add extra processors if desired
# load packages
using KOSHELPER
@everywhere using KOS
using KOS_FIELD_SOLVE
#
# other Julia packages
@everywhere using Interpolations
using PyPlot
#
ion()
# distance function
@everywhere function d_ball_0_1( x::Array{Float64,1} )
  # out=d_ball_0_1(x)
  #
  # Distance of points to the domain boundary
  # Domain is a ball of radius 1 centred at (0,0)
  # x: point
  R=1.
  return R-sqrt(sum(x.^2))
end
#
@everywhere function vec_d_ball_0_1( xs )
  # out=d_ball_0_1(x)
  # same as above
  # points are rows of xs
  R=1.
  return (R-sqrt.(sum(xs.^2,length(size(xs)))))[:]
end
# Zero functions
@everywhere function fn_zero( x::Array{Float64,2})
  #
  return 0.
end
#
@everywhere function fn_zero( x::Array{Float64,1} )
  # Right-hand side designed to expose slow coupling rates.  #
  # x: point(s) as columns for evaluation
  return zeros(size(x)[1])
end
##########################################
function mwe2(alpha)
  # Main routine for computing eigenvalues on unit ball
  # Inputs: alpha, parameter for fractional Laplacian in (0,2)
  # lam_tol, tolerance for the eigenvalue
  lam_tol=5e-2
  # no_its, number of Arnoldi iterations
  no_its=5
  # relax_flag, use variable accuracy if True
  relax_flag=true
  #
  # Ouputs:lam, computed eignvalue
  # el_time, elapsed time in seconds
  # eig_res, eigenvalue residual
  println("\n==================\nGet smallest eigenvalue.\n")
  #
  # domain is set here (fn_zero functions don't do much)
  p=KOS.Params(fn_zero,fn_zero,vec_d_ball_0_1,alpha)
  #
  # set the parameters for the method
  dict=Dict("max_samples"=>Int32(1e5), "report"=>true,
    #
    "no_levels"=>7, "l0"=>5,  "solve_method"=>"ml",
    "lam_tol"=>lam_tol, # tolerance for eigenvalue solve
    "no_arnoldi_its"=>no_its,
    #
    "relax"=>relax_flag, # for inexact Arnolid
    "B"=>3,#see paper for B (large B smaller WOS tolerance)
   )
  println("Variable accuracy=", dict["relax"],
          ", no_levels=",dict["no_levels"], ", lam_tol=",dict["lam_tol"])
  # set up the mesh
  G=KOSHELPER.get_tri_mesh([-1.,1.,-1.,1.],
                            dict["no_levels"],p.d)
  # first Arnoldi run
  # initial vector for eigenvector
  Y=ones(G.n_int);
  for i=1:G.n_int
    Y[i]=1-(1-p.d(G.get_pt(i))[1])^2
  end
  arnoldi=Construct_Arnoldi(Y)
  #########################################
  lam,evec,delta_lam,arnoldi,el_time,eig_res=wos_ev(p,dict,G,arnoldi,1.)
  dyda_compare(alpha,  lam)
  println("-----------------------------------\n\n")
  #
  println("Variable accuracy=",dict["relax"],
          ", no_levels=",dict["no_levels"], ", lam_tol=",dict["lam_tol"])
  out=G.reshape(evec,KOS.vec_fn_zero)
  Z=G.move_to_TriMesh(out)
  #
  do_plot(G,Z,"evec", "evec.png")
  return lam, el_time, eig_res
end
#######################################
################ end eg_ev.jl
####################################


module Life

import Random

w = 37;
h = 37;
steps = 10
# Set up the board: one board for current state, one for next state
# Begin the game by populating the array cells with random bits.
status = Random.bitrand(Random.MersenneTwister(45565), w, h);
nexts = falses(w, h); # Space-efficient Julia BitArray: of 0 or 1

# Mask the boundaries with "dead" cells
for ic=1:w, ir=1:h
    if ( 1==ic || w==ic || 1==ir || h==ir)
        status[ir, ic] = false
    end
end

# NB: In Julia, try to loop through arrays in a manner that takes advantage
# of how arrays are stored in memory (inner loops should loop over rows):
# https://en.wikibooks.org/wiki/Introducing_Julia/Controlling_the_flow#Optimizing_nested_loops
for t=0:steps
    # Show state
    print("t = ", t, '\n')
    display(Int8.(status))
    print('\n')

    # Only update cells that are not on the border of the region for now
    # This avoids out of bounds errors
    for ic=2:w-1, ir=2:h-1
        # Neighborhood population:
        # ------------------------
        # 0 1 1
        # 0 X 1 ==> pop @ X is 4
        # 0 1 0
        # ------------------------
        pop = status[ir-1, ic-1] + status[ir-1, ic] + status[ir-1, ic+1]
            + status[ir, ic-1] + status[ir, ic+1]
            + status[ir+1, ic-1] + status[ir+1, ic] + status[ir+1, ic+1]
        # Rules:
        cell = status[ir, ic]
        # 1. If a cell is alive, and has 1 neighbor, or > 4 neighbors, it dies.
        if ( (cell) && (pop != 2) && (pop != 3) )
            nexts[ir, ic] = false
        # 2. If a cell is dead, and has 3 neighbors, it becomes alive.
        elseif ( !cell && (pop == 3) )
            nexts[ir, ic] = true
        # 3. Otherwise, no change.
        else
            nexts[ir, ic] = cell
        end
    end
    # Update the system state all at once
    for ic=1:w-1, ir=1:h-1
        status[ir, ic] = nexts[ir, ic]
    end

end

end


# Band-pass wavelet
# Author: Gabrio Rizzuti; Ziyi Yin, ziyi.yin@gatech.edu
# Date: August 2020

export cfreqfilt, cfreq_wavelet

###############################################################################
# Generate band-pass source wavelet

function cfreqfilt(nt::Int64, dt::Float32, cfreqs::NTuple{4, Float32}; type::String = "bandpass", edge = hamming, flag_fft::Bool = true)
	# Initialization
	Filter = zeros(ComplexF32, nt)
	# Index corresponding to corner frequencies
	i1 = Int64(ceil(cfreqs[1]*nt*dt)+1)
	i2 = Int64(ceil(cfreqs[2]*nt*dt)+1)
	i3 = Int64(floor(cfreqs[3]*nt*dt)+1)
	i4 = Int64(floor(cfreqs[4]*nt*dt)+1)
	if type == "lowpass"
		i1 = 1
		i2 = 1
	elseif type == "highpass"
		i3 = nt
		i4 = nt
	end
	# Populating filter in the frequency domain
	Filter[1:i1-1] .= 0
	k = collect(i1:i2-1); Filter[k] .= (k.-i1)./(i2-i1)
	Filter[i2:i3] .= 1
	k = collect(i3+1:i4); Filter[k] .= Float32(1).-(k.-i3)./(i4-i3)
	Filter[i4+1:end] .= 0
	SL = Int64((nt-1)/2)
	Filter[SL+2:end] = Filter[SL+1:-1:2]
	# Zeroing edge values in time domain
	filter = ifftshift(Float32.(edge(length(Filter))).*fftshift(real.(ifft(Filter))))
	# Final Fourier transform
	if flag_fft
		return fft(filter)
	else
		return filter
	end
end

function cfreq_wavelet(SL::Int64, nt::Int64, dt::Float32, cfreqs::NTuple{4, Float32}; type::String = "bandpass", edge = hamming, flag_fft::Bool = true)
	return [ifftshift(cfreqfilt(2*SL+1, dt, cfreqs; type = type, edge = edge, flag_fft = false)); zeros(Float32, nt-(2*SL+1))]
end


#L2MarginLoss

using LaTeXStrings, LossFunctions, Measures, Latexify

Plots.plot(L2MarginLoss(), leg=:topright, c=:olive, linewidth=2.5, annotation=(1,5,L"L\left( a \right) = (1 - a)^2", 16))


###########################################################################################

# Regular grid for 1D array
function simps(y::AbstractVector{<:Number},dx::Real=1;even="avg")

	if iseven(size(y,1))
		if !(even ∈ ("avg","first","last"))
			error("even has to be one of avg, first or last."*
				"Specified even is $(even)")
		end

		T = promote_type(eltype(y), Float64)
		result = zero(T)
		val = zero(T)
		first_ind = first(axes(y,1))
		last_ind = last(axes(y,1))

		if even ∈ ["avg","first"]
			result += simpsRegular1D(view(y,first_ind:last_ind-1),dx)
			val += trapzRegular1D(view(y,last_ind-1:last_ind),dx)
		end
		if even ∈ ["avg","last"]
			result += simpsRegular1D(view(y,first_ind+1:last_ind),dx)
			val += trapzRegular1D(view(y,first_ind:first_ind+1),dx)
		end
		if even == "avg"
			val /= 2
			result /=2
		end
		result += val
	else
		result = simpsRegular1D(y,dx)
	end
	return result
end

# Regular grid for nD array
function simps(y::AbstractArray{<:Number},dx::Real=1;even="avg",axis::Integer=1)

	T = promote_type(eltype(y), Float64)

	if axis != 1
		perm = [axis]
		append!(perm,[i for i in 1:axis-1])
		append!(perm,[i for i in axis+1:ndims(y)])
		y = permutedims(y,perm)
	end

	trailing_axes,inds = trailing_indices(y)
	result = zeros(T,inds)

	if iseven(size(y,1))
		if !(even ∈ ("avg","first","last"))
			error("even has to be one of avg, first or last."*
				"Specified even is $(even)")
		end

		val = zeros(T,inds)
		first_ind = first(axes(y,1))
		last_ind = last(axes(y,1))

		if even ∈ ["avg","first"]
			simpsRegular1DAdd!(view(y,first_ind:last_ind-1,trailing_axes),
						result,dx)
			trapzRegular1DAdd!(view(y,last_ind-1:last_ind,trailing_axes),
						val,dx)
		end
		if even ∈ ["avg","last"]
			simpsRegular1DAdd!(view(y,first_ind+1:last_ind,trailing_axes),
						result,dx)
			trapzRegular1DAdd!(view(y,first_ind:first_ind+1,trailing_axes),
						val,dx)
		end
		if even == "avg"
			val ./= 2
			result ./=2
		end
		result .+= val
	else
		simpsRegular1D!(y,result,dx)
	end
	return result
end

# Irregular grid for 1D array
function simps(y::AbstractVector{<:Number},x::AbstractVector{<:Real};even="avg")

	if iseven(size(y,1))
		if !(even ∈ ("avg","first","last"))
			error("even has to be one of avg, first or last."*
				"Specified even is $(even)")
		end

		T = promote_type(eltype(y), Float64)
		result = zero(T)
		val = zero(T)
		first_ind = first(axes(y,1))
		last_ind = last(axes(y,1))
		first_ind_x = first(axes(x,1))
		last_ind_x = last(axes(x,1))

		if even ∈ ["avg","first"]
			result += simpsIrregular1D(view(y,first_ind:last_ind-1),
				view(x,first_ind_x:last_ind_x-1))
			val += trapzRegular1D(view(y,last_ind-1:last_ind),
				x[last_ind_x]-x[last_ind_x-1])
		end
		if even ∈ ["avg","last"]
			result += simpsIrregular1D(view(y,first_ind+1:last_ind),
				view(x,first_ind_x+1:last_ind_x))
			val += trapzRegular1D(view(y,first_ind:first_ind+1),
				x[first_ind_x+1]-x[first_ind_x])
		end
		if even == "avg"
			val /= 2.
			result /=2.
		end
		result += val
	else
		result = simpsIrregular1D(y,x)
	end
	return result
end

# Irregular grid for nD array
function simps(y::AbstractArray{<:Number},x::AbstractVector{<:Real};even="avg",axis::Integer=1)

	T = promote_type(eltype(y), Float64)

	if axis != 1
		perm = [axis]
		append!(perm,[i for i in 1:axis-1])
		append!(perm,[i for i in axis+1:ndims(y)])
		y = permutedims(y,perm)
	end

	trailing_axes,inds = trailing_indices(y)
	result = zeros(T,inds)

	if iseven(size(y,1))
		if !(even ∈ ("avg","first","last"))
			error("even has to be one of avg, first or last."*
				"Specified even is $(even)")
		end

		val = zeros(T,inds)

		first_ind = first(axes(y,1))
		last_ind = last(axes(y,1))

		first_ind_x = first(axes(x,1))
		last_ind_x = last(axes(x,1))

		if even ∈ ["avg","first"]
			simpsIrregular1DAdd!(view(y,first_ind:last_ind-1,trailing_axes),
				result,view(x,first_ind_x:last_ind_x-1))

			trapzRegular1DAdd!(view(y,last_ind-1:last_ind,trailing_axes),
				val,x[last_ind_x]-x[last_ind_x-1])
		end
		if even ∈ ["avg","last"]
			simpsIrregular1DAdd!(view(y,first_ind+1:last_ind,trailing_axes),
				result,view(x,first_ind_x+1:last_ind_x))

			trapzRegular1DAdd!(view(y,first_ind:first_ind+1,trailing_axes),
				val,x[first_ind_x+1]-x[first_ind_x])
		end
		if even == "avg"
			val ./= 2.
			result ./=2.
		end
		result .+= val
	else
		simpsIrregular1D!(y,result,x)
	end
	return result
end

#############################################################################

function simpsweight(Nelems,index)
	@assert(1<=index<=Nelems,"index not in range")
	if index==1 || index==Nelems
		return 1
	elseif iseven(index)
		return 4
	end
	return 2
end

function quadratic_fit_integrate!(coeffs,y,x)
	# Find the lagrange polynomial fit to y(x) (quadratic)
	# integrate this between first(x) and last(x)
	@assert(length(y)==length(x)==length(coeffs)==3)

	coeffs[1] = -(x[1]-x[3])*(2x[1]-3x[2]+x[3])/(6*(x[1]-x[2]))

	coeffs[2] = -(x[1]-x[3])^3/(6*(x[1]-x[2])*(x[2]-x[3]))

	coeffs[3] = (x[1]-x[3])*(x[1]-3x[2]+2x[3])/(6*(x[2]-x[3]))

	dot(coeffs,y)
end

#############################################################################
## Simpson's 1/3 rule
#############################################################################

# 1D array with uniform grid spacing for an odd number of points
function simpsRegular1D(y::AbstractVector{<:Number},dx::Real=1)

	@assert(isodd(length(y)),"Number of elements must be odd to apply Simpson's rule")

	N = size(y,1)
	sum(simpsweight(N,ind)*yi for (ind,yi) in enumerate(y)) *dx/3
end

function simpsRegular1D!(y::AbstractVector{<:Number},
	int_y::AbstractArray{T},dx::Real=1) where {T<:Number}

	@assert(isodd(length(y)),"Number of elements must be odd to apply Simpson's rule")

	N = size(y,1)
	int_y .= sum(simpsweight(N,ind)*yi for (ind,yi) in enumerate(y)) *dx/3

	return first(int_y)
end

# UnitRanges can be integrated exactly
function simpsRegular1D(y::AbstractUnitRange{<:Real},dx::Real=1)
	(last(y)^2 - first(y)^2) * dx/2
end

# N dimensional array, integrate along the first axis
function simpsRegular1D(y::AbstractArray{<:Number},dx::Real=1)

	T = promote_type(eltype(y),Float64)
	trailing_axes = trailing_indices(y)
	int_y = zeros(T,inds)

	for ind_t in trailing_axes
		int_y[ind_t] = simpsRegular1D(view(y,:,ind_t),dx)
	end
	return int_y
end

function simpsRegular1D!(y::AbstractArray{<:Number},
	int_y::AbstractArray{<:Number},dx::Real=1)

	trailing_axes,_ = trailing_indices(y)

	for ind_t in trailing_axes
		int_y[ind_t] = simpsRegular1D(view(y,:,ind_t),dx)
	end

	return int_y
end

function simpsRegular1DAdd!(y::AbstractArray{<:Number},
	int_y::AbstractArray{<:Number},dx::Real=1)

	trailing_axes,_ = trailing_indices(y)

	for ind_t in trailing_axes
		int_y[ind_t] += simpsRegular1D(view(y,:,ind_t),dx)
	end

	return int_y
end

#############################################################################
## Simpson's 1/3 rule for irregular grids
#############################################################################

# 1D array with non-uniform grid spacing, integral along one axis
function simpsIrregular1D(y::AbstractVector{TY},x::AbstractVector{TX}) where {TY,TX}

	@assert(isodd(length(y)),"The array should have an odd number of elements")
	@assert(length(x)==length(y),"y and x need to have the same number of elements")

	T = promote_type(TY, TX)
	int_y = zero(T)

	y_no_offset = OffsetArrays.no_offset_view(y)
	x_no_offset = OffsetArrays.no_offset_view(x)

	# The strategy is to fit a quadratic to three points, and compute its integral
	coeffs = zeros(T,3)

	for ind in 1:2:length(x)-2

		int_y += quadratic_fit_integrate!(coeffs,view(y_no_offset,ind:ind+2),
						view(x_no_offset,ind:ind+2))
	end

	return int_y
end

function simpsIrregular1D!(y::AbstractVector,
	int_y::AbstractArray{<:Any,0},x::AbstractVector)

	int_y[] = simpsIrregular1D(y,x)

	return first(int_y)
end

# N dimensional array, integrate along the first axis
function simpsIrregular1D(y::AbstractArray{TY},x::AbstractVector{TX}) where {TY,TX}

	T = promote_type(TY,TX)
	trailing_axes,inds = trailing_indices(y)
	int_y = zeros(T,inds)

	for ind_t in trailing_axes
		int_y[ind_t] = simpsIrregular1D(view(y,:,ind_t),x)
	end

	return int_y
end

function simpsIrregular1D!(y::AbstractArray{<:Number},
	int_y::AbstractArray{T},x::AbstractVector{<:Real}) where {T<:Number}

	trailing_axes,_ = trailing_indices(y)

	for ind_t in trailing_axes
		int_y[ind_t] = simpsIrregular1D(view(y,:,ind_t),x)
	end

	return int_y
end

function simpsIrregular1DAdd!(y::AbstractArray{<:Number},
	int_y::AbstractArray{T},x::AbstractVector{<:Real}) where {T<:Number}

	trailing_axes,_ = trailing_indices(y)

	for ind_t in trailing_axes
		int_y[ind_t] += simpsIrregular1D(view(y,:,ind_t),x)
	end

	return int_y
end

function simpsCircle(y::AbstractVector{<:Number},
	ϕ::AbstractArray{<:Real,1}=LinRange(0,2π,size(y,1));
	even::String="avg",ϕ2π::Bool=true)::Array{<:Number,0}

	if ϕ2π
		z = simps(y,x=ϕ,even=even)
	else

		info("Assuming that 2π is not included,"*
			" will append it to the array before integrating.\n"*
			"If 2π is included set ϕ2π=true in the function call")

		check_last_value(ϕ,2π) # check if the last value of ϕ is 2π

		push!(y,y[1])

		z = simps(y,x=push!(ϕ,2π),even=even)
	end

	return z
end

function simpsSpherical2D(y::AbstractArray{<:Number},
	θ::AbstractArray{<:Real,1}=LinRange(0,π,size(y,1)),
	ϕ::AbstractArray{<:Real,1}=LinRange(0,2π,size(y,2));
	even="avg",θint="clenshaw_quadrature",GLweights=nothing,
	ϕ2π=true)::Array{<:Number,0}

	if θint=="gauss_quadrature"

		if GLweights != nothing
			z = gauss_quadrature(y,GLweights)
		end

	elseif θint=="clenshaw_quadrature"

		z = clenshaw_curtis_quadrature(y)

	else
		z = simps(sin.(θ).*y,x=θ,even=even)
	end

	simpsCircle(z,ϕ,even=even,ϕ2π=ϕ2π)
end

function simpsSpherical3D(y::AbstractArray{<:Number},
	r::Union{Real,AbstractArray{<:Real,1}}=1,
	θ::AbstractArray{<:Real,1}=LinRange(0,π,size(y,2)),
	ϕ::AbstractArray{<:Real,1}=LinRange(0,π,size(y,3));
	even="avg",θint="clenshaw_quadrature",GLweights = nothing,
	ϕ2π=true)

	if typeof(r) <: AbstractArray
		z = simps(r.^2 .* y,x=r,even=even)
	else
		dr = r
		z = dr^2*simps(@. ((1:size(y,1))^2 * y),dx=dr,even=even)
	end

	simpsSpherical2D(z,θ,ϕ,θint=θint,
		GLweights=GLweights,even=even,ϕ2π=ϕ2π)
end