using Test
using ClimateMachine
using ClimateMachine.Mesh.Grids
using ClimateMachine.Mesh.Topologies
using ClimateMachine.MPIStateArrays
using Impero, Printf, MPI, LinearAlgebra, Statistics, GaussQuadrature
using ImperoHooks
import ImperoHooks: launch_volume_divergence!, launch_interface_divergence!

ClimateMachine.init()
const ArrayType = ClimateMachine.array_type()
const mpicomm = MPI.COMM_WORLD
const FT = Float64
Ω = Circle(-1,1) × Circle(-1,1) × Circle(-1,1)
dims = ndims(Ω)

ClimateMachine.gpu_allowscalar(true)

# Define Grid: might want to loop over element sizes and polynomial orders
grid = DiscontinuousSpectralElementGrid(Ω, elements = (10,10,10), polynomialorder = (4,4,4), array = ArrayType)

x, y, z = coordinates(grid)
device = array_device(x)
dim = ndims(Ω)
N = round(Int, size(x)[1]^(1/dim)) - 1
dependencies = nothing

# Initialize State s
F1 = MPIStateArray{FT}(mpicomm, ArrayType, size(x)[1], size(x)[2], 1)
F2 = MPIStateArray{FT}(mpicomm, ArrayType, size(x)[1], size(x)[2], 1)
F3 = MPIStateArray{FT}(mpicomm, ArrayType, size(x)[1], size(x)[2], 1)
flux = MPIStateArray{FT}(mpicomm, ArrayType, size(x)[1], size(x)[2] , 3)

v_flux_divergence = F1 .* 0
s_flux_divergence = F1 .* 0

allindices = collect(1:length(s_flux_divergence))
interior   = setdiff(1:length(s_flux_divergence), grid.vmap⁻)

## Test Block 1: Incompressible Flow Field
# Impero should make this nicer
@. F1 = z
@. F2 = x
@. F3 = y + x

flux[:,:,1:1] .= F1
flux[:,:,2:2] .= F2
flux[:,:,3:3] .= F3
analytic_flux_divergence = @. 0 * x

event = launch_volume_divergence!(v_flux_divergence, flux, grid)
wait(event)
event = launch_interface_divergence!(s_flux_divergence, flux, grid)
wait(event)

tol = eps(1e6)
L∞(x) = maximum(abs.(x))
# The divergence operation is : -v_flux_divergence + s_flux_divergence (note the sign convention)
@testset "Incompressible Flow Field" begin
    computed_divergence = -v_flux_divergence + s_flux_divergence
    @test L∞(computed_divergence - analytic_flux_divergence ) < tol
    @test L∞(v_flux_divergence - s_flux_divergence) < tol
    # test to see if only the exterior terms were affected
    @test L∞(s_flux_divergence[interior]) < eps(1.0)
end

## Test Block 2: Compressible Flow Field
@. F1 =  sin(π*x)
@. F2 =  sin(π*z)
@. F3 =  cos(π*y)
ClimateMachine.gpu_allowscalar(true)
flux[:,:,1:1] .= F1
flux[:,:,2:2] .= F2
flux[:,:,3:3] .= F3
analytic_flux_divergence = @. π*cos(π*x)

event = launch_volume_divergence!(v_flux_divergence, flux, grid)
wait(event)
event = launch_interface_divergence!(s_flux_divergence, flux, grid)
wait(event)

tol = 1e-3 # for this test should be a function of polynomial order / element size
# The divergence operation is : -v_flux_divergence + s_flux_divergence (note the sign convention)
@testset "Compressible Flow Field" begin
    computed_divergence = -v_flux_divergence + s_flux_divergence
    @test L∞(computed_divergence - analytic_flux_divergence ) < tol
    # test to see if only the exterior terms were affected
    @test L∞(s_flux_divergence[interior]) < eps(1.0)
end

## Test Block 3: Compressible Flow Field
a = 1
b = 1
c = 1
@. F1 =  a * sin(π*x)
@. F2 =  b * sin(π*y)
@. F3 =  c * cos(π*z)
ClimateMachine.gpu_allowscalar(true)
flux[:,:,1:1] .= F1
flux[:,:,2:2] .= F2
flux[:,:,3:3] .= F3
analytic_flux_divergence = @. a * π*cos(π*x) + b*π*cos(π*y) - c*π*sin(π*z)

event = launch_volume_divergence!(v_flux_divergence, flux, grid)
wait(event)
event = launch_interface_divergence!(s_flux_divergence, flux, grid)
wait(event)

tol = 1e-3 # for this test should be a function of polynomial order / element size
# The divergence operation is : -v_flux_divergence + s_flux_divergence (note the sign convention)
@testset "Compressible Flow Field" begin
    computed_divergence = -v_flux_divergence + s_flux_divergence
    @test L∞(computed_divergence - analytic_flux_divergence ) < tol
    # test to see if only the exterior terms were affected
    @test L∞(s_flux_divergence[interior]) < eps(1.0)
end


using LyapunovExponents

function get_angles(solver)
    return [acos(abs(dot(C[:, 1], C[:, 2]))) * 2 / π for C
            in backward_dynamics!(solver; progress=1)]
end

henon_demo = LyapunovExponents.henon_map(t_attr=1000000)
henon_demo.prob :: LEProblem
henon_prob = CLVProblem(henon_demo.prob)  # convert it to CLVProblem
henon_angles = @time get_angles(init(henon_prob))

lozi_prob = CLVProblem(LyapunovExponents.lozi_map(t_attr=1000000).prob)
lozi_angles = @time get_angles(init(lozi_prob))

using Plots
plt = plot(xlabel="Angle [pi/2 rad]", ylabel="Density", legend=:topleft)
stephist!(plt, henon_angles,
          bins=1000, normalize=true, linecolor=1, label="Henon")
stephist!(twinx(plt), lozi_angles,
          bins=1000, normalize=true, linecolor=2, label="Lozi")
plt


using TensorOperations: @tensor

function check_value_and_type(x, y, z)
    @test x ≈ y ≈ z
    @test typeof(x) == typeof(y)
end

@testset "Einstein summation" begin
    @testset "no dummy indices" begin
        x = rand(Tensorial.tensortype(Space(4, Symmetry(2, 2))))

        # check type instability
        f(x) = @einsum (1,2,3) -> x[3,2,1]
        @inferred(f(x))

        # permutedims
        check_value_and_type((@einsum (1,2,3) -> x[1,2,3]), permutedims(x, Val((1,2,3))), (@tensor t[1,2,3] := Array(x)[1,2,3]))
        check_value_and_type((@einsum (1,3,2) -> x[1,2,3]), permutedims(x, Val((1,3,2))), (@tensor t[1,3,2] := Array(x)[1,2,3]))
        check_value_and_type((@einsum (2,1,3) -> x[1,2,3]), permutedims(x, Val((2,1,3))), (@tensor t[2,1,3] := Array(x)[1,2,3]))
        check_value_and_type((@einsum (2,3,1) -> x[1,2,3]), permutedims(x, Val((2,3,1))), (@tensor t[2,3,1] := Array(x)[1,2,3]))
        check_value_and_type((@einsum (3,1,2) -> x[1,2,3]), permutedims(x, Val((3,1,2))), (@tensor t[3,1,2] := Array(x)[1,2,3]))
        check_value_and_type((@einsum (3,2,1) -> x[1,2,3]), permutedims(x, Val((3,2,1))), (@tensor t[3,2,1] := Array(x)[1,2,3]))

        v1 = rand(Vec{3})
        v2 = rand(Vec{3})
        check_value_and_type((@einsum (i,j) -> v1[i] * v2[j]), v1 ⊗ v2, (@tensor t[i,j] := Array(v1)[i] * Array(v2)[j]))
        check_value_and_type((@einsum (j,i) -> v1[i] * v2[j]), v2 ⊗ v1, (@tensor t[j,i] := Array(v1)[i] * Array(v2)[j]))
        check_value_and_type((@einsum v1[i] * v2[j]), v1 ⊗ v2, (@tensor t[i,j] := Array(v1)[i] * Array(v2)[j]))

        S1 = rand(SymmetricSecondOrderTensor{3})
        S2 = rand(SecondOrderTensor{3})
        check_value_and_type((@einsum (i,j,k,l) -> S1[i,j] * S2[k,l]), S1 ⊗ S2, (@tensor t[i,j,k,l] := Array(S1)[i,j] * Array(S2)[k,l]))
        check_value_and_type((@einsum (i,k,j,l) -> S1[i,j] * S2[k,l]), permutedims(S1 ⊗ S2, Val((1,3,2,4))), (@tensor t[i,k,j,l] := Array(S1)[i,j] * Array(S2)[k,l]))
        check_value_and_type((@einsum S1[i,j] * S2[k,l]), S1 ⊗ S2, (@tensor t[i,j,k,l] := Array(S1)[i,j] * Array(S2)[k,l]))
    end
    @testset "no free indices" begin
        v1 = rand(Vec{3})
        v2 = rand(Vec{3})
        check_value_and_type((@einsum () -> v1[i] * v2[i]), v1 ⋅ v2, only(@tensor t[] := Array(v1)[i] * Array(v2)[i]))
        check_value_and_type((@einsum v1[i] * v2[i]), v1 ⋅ v2, only(@tensor t[] := Array(v1)[i] * Array(v2)[i]))

        S1 = rand(SymmetricSecondOrderTensor{3})
        S2 = rand(SecondOrderTensor{3})
        check_value_and_type((@einsum () -> S1[i,j] * S2[i,j]), S1 ⊡ S2, only(@tensor t[] := Array(S1)[i,j] * Array(S2)[i,j]))
        check_value_and_type((@einsum () -> S1[i,j] * S2[j,i]), S1 ⊡ S2', only(@tensor t[] := Array(S1)[i,j] * Array(S2)[j,i]))
        check_value_and_type((@einsum S1[i,j] * S2[i,j]), S1 ⊡ S2, only(@tensor t[] := Array(S1)[i,j] * Array(S2)[i,j]))
        check_value_and_type((@einsum S1[i,j] * S2[j,i]), S1 ⊡ S2', only(@tensor t[] := Array(S1)[i,j] * Array(S2)[j,i]))
        check_value_and_type((@einsum S1[i,i]), tr(S1), only(@tensor t[] := Array(S1)[i,i]))
        check_value_and_type((@einsum S1[i,i]/3), mean(S1), only(@tensor t[] := Array(S1)[i,i]/3))
        check_value_and_type((@einsum S1[i,i]/S2[j,j]), tr(S1)/tr(S2), tr(Array(S1))/tr(Array(S2))) # not allowed in TensorOperations
    end
    @testset "mixed" begin
        S1 = rand(SymmetricSecondOrderTensor{3})
        S2 = rand(SecondOrderTensor{3})
        check_value_and_type((@einsum (i,j) -> S1[i,k] * S2[k,j]), S1 ⋅ S2, (@tensor t[i,j] := Array(S1)[i,k] * Array(S2)[k,j]))
        check_value_and_type((@einsum (i,j) -> S1[i,k] * S2[j,k]), S1 ⋅ S2', (@tensor t[i,j] := Array(S1)[i,k] * Array(S2)[j,k]))
        check_value_and_type((@einsum S1[i,k] * S2[k,j]), S1 ⋅ S2, (@tensor t[i,j] := Array(S1)[i,k] * Array(S2)[k,j]))
        check_value_and_type((@einsum (i,j) -> S1[i,k] * S2[j,k] + S1[j,k] * S2[i,k]), S1 ⋅ S2' + S2 ⋅ S1', (@tensor t[i,j] := Array(S1)[i,k] * Array(S2)[j,k] + Array(S1)[j,k] * Array(S2)[i,k]))
        check_value_and_type((@einsum (i,j) -> S1[i,k] * (S2[j,k] + S1[k,j] * S2[i,i])), S1 ⋅ (S2' + S1 * tr(S2)), (@tensor t[i,j] := Array(S1)[i,k] * (Array(S2)[j,k] + Array(S1)[k,j] * Array(S2)[i,i])))

        S3 = rand(Tensor{Tuple{@Symmetry{3,3,3}}})
        v1 = rand(Vec{3})
        check_value_and_type((@einsum (i,j) -> S3[j,k,i] * v1[k]), permutedims(S3, Val((3,1,2))) ⋅ v1, (@tensor t[i,j] := Array(S3)[j,k,i] * Array(v1)[k]))
        check_value_and_type((@einsum (j) -> v1[i] * S3[j,k,i] * v1[k]), (S3 ⋅ v1) ⋅ v1, (@tensor t[j] := Array(v1)[i] * Array(S3)[j,k,i] * Array(v1)[k]))
        check_value_and_type((@einsum v1[i] * S3[j,k,i] * v1[k]), (S3 ⋅ v1) ⋅ v1, (@tensor t[j] := Array(v1)[i] * Array(S3)[j,k,i] * Array(v1)[k]))
    end
    @testset "errors" begin
        S1 = rand(SymmetricSecondOrderTensor{3})
        v = rand(Vec{2})
        @test_throws Exception (@einsum (i) -> S1[i,j] * v[j])
        # @test_throws Exception (@einsum (i,j) -> S1[i,j] + S1[i,j])
        # @test_throws Exception (@einsum (i,j) -> S1[i,j] - S1[i,j])
        # @test_throws Exception (@einsum (i,j) -> S1[i,j] * S1[i,j])
        # @test_throws Exception (@einsum (k) -> S1[i,j] * S1[i,j])
        # @test_throws Exception (@einsum (j) -> S1[i,i] * S1[i,j])
        # @test_throws Exception (@einsum S1[i,i] * S1[i,j])
    end
end


using JuMP
using Gurobi
using DataFrames
using CSV

##### Problem 2 code skeleton

### Solution

# Note: @__DIR__ points to the directory which contains this script file
# Read data
cd(string(@__DIR__, "/Data"))
sessions = convert(Matrix,CSV.File("Pb2_sessions.csv"; header=true) |> DataFrame!);
courses = convert(Matrix,CSV.File("Pb2_courses.csv"; header=true) |> DataFrame!);
rooms = convert(Matrix,CSV.File("Pb2_rooms.csv"; header=true) |> DataFrame!);
preferences = convert(Matrix,CSV.File("Pb2_preferences.csv"; header=true) |> DataFrame!);
cohorts = convert(Matrix,CSV.File("Pb2_cohorts.csv"; header=true) |> DataFrame!);

cd(@__DIR__)

# Initialize sets and parameters
J = size(sessions,1); #number of sessions
C = size(courses,1); #number of courses
R = size(rooms,1); #number of classrooms
S = size(cohorts,1); #number of student cohorts
D = 2; #number of days
T = 6; #number of time blocks
Q = rooms[:,2]; #classroom capacities
U = courses[:,2]; #course units
N = cohorts[:,2]; #number of students per cohort
ms = cohorts[:,3]; #minimum number of units per cohort
Ms = cohorts[:,4]; #maximum number of units per cohort

mandatory = zeros(S,C); #mandatory courses per cohort
mandatory[1:6,1:5] .= 1;
mandatory[7:13,6:9] .= 1;
mandatory[14:15,10:11] .= 1;
mandatory[16:17,12:13] .= 1;

# Start building model
timelimit = 10*60;
model = Model(Gurobi.Optimizer)
set_optimizer_attribute(model, "TimeLimit", timelimit)

# VARIABLES
@variable(model,z[1:S,1:J,1:D,1:T],Bin);
@variable(model,x[1:J,1:D,1:T,1:R],Bin);

# CONSTRAINTS
@constraint(
    model, [s in 1:S, j in 1:J, d in 1:D, t in 1:T],
    z[s,j,d,t] <= sum(x[j,d,t,r] for r in 1:R)
)

@constraint(
    model,[s in 1:S, d in 1:D, t in 1:T],
    sum(z[s,j,d,t] for j in 1:J) <= 1
)

for c in 1:C
    @constraint(model,
        [s in 1:S],
        sum(z[s,j,d,t]
            for d in 1:D,
                t in 1:T,
                j in sessions[findall(x -> x==c,sessions[:,2]),1])
            <= 1);
end

for s in 1:S
    mandatory_s = findall(x -> x==1,mandatory[s,:]);
    for c in mandatory_s
        @constraint(model,
            sum(z[s,j,d,t]
            for d in 1:D,
                t in 1:T,
                j in sessions[findall(x -> x==c,sessions[:,2]),1])
            >= 1);
    end
end

@constraint(model,[r in 1:R, d in 1:D, t in 1:T],sum(x[j,d,t,r] for j in 1:J) <= 1);

@constraint(model,[j in 1:J],sum(x[j,d,t,r] for r in 1:R, d in 1:D, t in 1:T) == 1);

@constraint(model,[j in 1:J, d in 1:D, t in 1:T],
            sum(N[s]*z[s,j,d,t] for s in 1:S) <= sum(Q[r]*x[j,d,t,r] for r in 1:R))


using ADCME
using LinearAlgebra
using AdFem
using DelimitedFiles

A = readdlm("fenics/A.txt")
mesh = Mesh(8, 8, 1/8)
K = zeros(get_ngauss(mesh), 3, 3)
for i = 1:get_ngauss(mesh)
    K[i, :, :] = [1.0 0.0 0.0
                  0.0 1.0 0.0
                  0.0 0.0 0.5]
end
B1 = compute_fem_stiffness_matrix(K, mesh)

K = constant(K)
B = compute_fem_stiffness_matrix(K, mesh)

sess = Session(); init(sess)
B0 = run(sess, B)
B0 = Array(B0)
@info norm(B0-B1)

DOF = zeros(Int64, 2*mesh.ndof)
for i = 1:mesh.ndof
    DOF[2*i-1] = i
    DOF[2*i] = i + mesh.ndof
end
B0 = B0[DOF, DOF]

@show norm(A - B0)

A = readdlm("fenics/A2.txt")
mesh = Mesh(8, 8, 1.0 / 8,degree=2)
K = zeros(get_ngauss(mesh), 3, 3)
for i = 1:get_ngauss(mesh)
    K[i, :, :] = [1.0 0.0 0.0
                  0.0 1.0 0.0
                  0.0 0.0 0.5]
end
K = constant(K)
B = compute_fem_stiffness_matrix(K, mesh)
sess = Session(); init(sess)
B0 = run(sess, B)
B0 = Array(B0)

E = Int64.(readdlm("fenics/edges.txt"))
Edof = get_edge_dof(E, mesh)
DOF = [1:mesh.nnode; Edof .+ mesh.nnode]
DOF = [DOF; DOF .+ mesh.ndof]
B0 = B0[DOF, DOF]

@show norm(A - B0)


function convergenceratio(z, alpha, W, intervals, nexPercentage)

convergence = map(intervals) do interval

    P = x->phi(z,alpha,interval,x)

    mask = interval[1] .<= W .<= interval[2];
    W_inside  = W[mask]  |> P |> x->abs.(x) ;
    W_outside = W[.!mask] |> P |> x->abs.(x) |> x-> sort(x, order=Base.Order.Reverse);

    nev = length(W_inside)

    minval = minimum( W_inside )
    maxval = W_outside                  |>
        x-> Iterators.drop(x,ceil(Integer,nev*nexPercentage)) |>
        x-> Iterators.take(x,1)         |>
        collect

    if( length(maxval) == 0 )
        @printf("error\n");
        return NaN
    else
        return maxval[1] / minval
    end
end

end


#Photosynthesis model for given total conductance (stomatal+mesophyll), gₜ, irradiance incident on the leaf (Iᵢ),
#and Jₘₐₓ
function Farquhar(gₜ::T, Iᵢ::T, Jₘₐₓ::T,photo::PhotoPar,env::EnvironmentStruct) where {T<:Float64}
    Pₜₒₜ,cₐ = env.P,env.Cₐ

    gₜₚ = gₜ/Pₜₒₜ

    J = (photo.α*Iᵢ+Jₘₐₓ-sqrt(photo.α^2*Iᵢ^2+2*photo.α*Iᵢ*Jₘₐₓ*(1-2*photo.θ)+Jₘₐₓ^2))/(2*photo.θ)
    Aⱼ = J/4

    #Intercellular carbon dioxide concentration
    cᵢ = (gₜₚ*(cₐ-2*photo.Γ)-Aⱼ*(1-photo.b_r)+sqrt((gₜₚ*(cₐ-2*photo.Γ)-Aⱼ*(1-photo.b_r))^2+4*Aⱼ*gₜₚ*(photo.Γ+photo.b_r*photo.K)+8*gₜₚ^2*cₐ*photo.Γ))/(2*gₜₚ)

    #Carbon assimilation rate
    A = gₜₚ*(cₐ-cᵢ)

    return A, cᵢ
end

#Calculate the C assimilation (kg C year⁻¹ m⁻² leaf area)
function C_assimilation(gₜ::T,Iᵢ::T,Jmax::T,growthlength::T,model::CCPHStruct) where {T<:Float64}
    A = model.treepar.Xₜ*model.cons.M_C*Farquhar(gₜ,Iᵢ,Jmax,model.photopar,model.env)[1]*growthlength

    return A
end

#Calculate per tree canopy gross primary production
GPP(gₜ::T,Iᵢ::T,Jmax::T,LAI::T,growthlength::T,model::CCPHStruct) where {T<:Float64} =
C_assimilation(gₜ,Iᵢ,Jmax,growthlength,model)*(1-exp(-model.treepar.k*LAI))/(model.treesize.N*model.treepar.k)

#Calcualte leaf performance at crown base
function Calc_Δ_leaf(gₜ::T,Iᵢ::T,LAI::T,growthlength::T,Nₘ_f::T,Jmax::T,model::CCPHStruct) where {T<:Float64}
    Iᵢ_b = Iᵢ*exp(-model.treepar.k*LAI)
    Jmax_b = Jmax*exp(-model.treepar.k*LAI)
    A_b = C_assimilation(gₜ,Iᵢ_b,Jmax_b,growthlength,model)
    Δ_leaf = model.treepar.y*(A_b-model.treepar.rₘ*Nₘ_f*model.treepar.LMA)-model.treepar.LMA/model.treepar.Tf #Bottom leaf performance
    return Δ_leaf
end


using Distributions

Sigma = [1. 0.; 0. 1.];

mu1 = [1., -1.];
x1 = rand(MvNormal(mu1, Sigma), 200);

mu2 = [5.5, -4.5];
x2 = rand(MvNormal(mu2, Sigma), 200);

mu3 = [1., 4.];
x3 = rand(MvNormal(mu3, Sigma), 200);

mu4 = [6., 4.5];
x4 = rand(MvNormal(mu4, Sigma), 200);

mu5 = [9., 0.0];
x5 = rand(MvNormal(mu5, Sigma), 200);

x = [x1 x2 x3 x4 x5]

println(repr(x))


# Estimates the basic Nerlove Cobb-Douglas model by MLE
# this is intended to show that MLE with normality is the
# ML estimator (for the b"s, not for sig^2)
using Econometrics, DelimitedFiles
data = readdlm("../Data/nerlove.data")
data = data[:,2:end]
data = log.(data)
n = size(data,1)
y = data[:,1]
x = data[:,2:end]
x = [ones(n,1) x]
names = ["constant", "output", "labor", "fuel", "capital", "sig"]
theta = [zeros(size(x,2)); 1.0] # start values for estimation
model = theta -> normal(theta, y, x)
thetahat, objvalue, V, converged = mleresults(model, theta, "estimate Nerlove model by MLE", names)
nothing


using JuMP, EAGO

                     m = Model()

                     EAGO.register_eago_operators!(m)

                     @variable(m, -1 <= x[i=1:3] <= 1)

                     @variable(m, -8.232182974914569 <= q <= 6.070970235703473)

                     add_NL_constraint(m, :(1/(1 + exp(-(-0.17705543456567296 + 0.5634748900178774*1/(1 + exp(-(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])))) + 0.6106029608527352*1/(1 + exp(-(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])))) + 0.5384388086098779*1/(1 + exp(-(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))))))) + 1/(1 + exp(-(0.08053767290922664 + 0.911477397877237*1/(1 + exp(-(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])))) + 0.7134526118957916*1/(1 + exp(-(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])))) + -0.34097520379571256*1/(1 + exp(-(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))))))) + 1/(1 + exp(-(0.5992621446321471 + 0.15304126986204158*1/(1 + exp(-(-0.9628319588335761 + 0.5826659544327617*$(x[1]) + -0.6099922924008303*$(x[2]) + 0.9101904633653426*$(x[3])))) + -0.37134199071135*1/(1 + exp(-(0.06629760484384972 + -0.7340864295918403*$(x[1]) + 0.2309908240081593*$(x[2]) + 0.7867070707759196*$(x[3])))) + 0.8747239793986465*1/(1 + exp(-(-0.07370832879656497 + -0.32073040453400337*$(x[1]) + -0.07649911788086428*$(x[2]) + 0.03499506906851524*$(x[3]))))))) - $q <= 0.0))

                     @objective(m, Min, q)

                     return m


using JuMP;
using Gurobi;

function SolveQpJump(mP, vQ, mA, vL, vU; hOptFun = nothing, verbosFlag = false)
    #TODO: See https://discourse.julialang.org/t/67214/5
    # Check if it gives speed improvements.

    numElements = size(mP, 1);

    if (isnothing(hOptFun))
        hOptFun = Gurobi.Optimizer;
    end

    jumpModel = Model(hOptFun);
    if (verbosFlag)
        unset_silent(jumpModel);
    else
        set_silent(jumpModel);
    end
    @variable(jumpModel, vT[1:numElements]);
    @objective(jumpModel, Min, 0.5 * (vT' * mP * vT) + (vT' * vQ));
    vV = vL .≠ -Inf;
    @constraint(jumpModel, vL[vV] .<= mA[vV, :] * vT);
    vV .= vU .≠ Inf;
    @constraint(jumpModel, mA[vV, :] * vT .<= vU[vV]);
    optimize!(jumpModel);

    return value.(vT);

end


include("findLargestInCol.jl")
include("swapRowsV2.jl")

# we write another function that composes A into A = P^{-1}LU where P is stored as a vector and swapRows is more efficient
function PALUdecompV2(A)

    # we get the size of A
    m, n = size(A)

    # we check that A is indeed a square matrix
    if m != n
        error("Input must be a square matrix")
    end

    # we initialize L and U to zero, P to {1,2, ..., n}
    L = zeros(n, n)
    U = zeros(n, n)
    p = collect(1:n)

    # we make a copy of A to work with
    B = copy(A)

    for k = 1:n

        # we find the pivot
        maxk = findLargestInCol(B, k)
        if maxk != k
            swapRowsV2!(B, maxk, k)
            swapRowsV2!(L, maxk, k)

            # we swap two elements of P instead of two rows
            temp = p[maxk]
            p[maxk] = p[k]
            p[k] = temp
        end

        # we proceed with the normal LU decomposition algorithm
        for j = k:n
            U[k,j] = B[k,j]
        end

        # we check that we don't divide by zero
        if U[k,k] == 0
            error("** A^(k-1)_{k,k} == 0 in PALU decomp")
        end

        for i = k:n
           L[i,k] = B[i,k] / U[k,k]
        end

        for i = k:n
            for j = k:n
                B[i,j] -= L[i,k] * U[k,j];
            end
        end
    end

    P = zeros(n,n)
    for i = 1:n
        P[i,p[i]] = 1
    end

    # we return P, L and U
    return P, L, U
end


module single_hex_examples
using FinEtools
using FinEtoolsDeforLinear
using FinEtoolsDeforLinear.AlgoDeforLinearModule
using FinEtools.AlgoBaseModule: evalconvergencestudy
using FinEtoolsDeforLinear.AlgoDeforLinearModule: linearstatics, exportstresselementwise, exportstress
using Statistics: mean
using LinearAlgebra: Symmetric, cholesky, eigen

# Isotropic material
E = 1.0;
nu = 0.3
# julia> rand(8,3) .* 2 .- 1.0
xyzperturbation = [
0.0767656  -0.983206    -0.14343
0.45767     0.981479     0.450997
-0.295854    0.542922     0.321333
-0.85204    -0.97824     -0.772874
-0.0539756   0.994907     0.822798
0.447173    0.528742     0.0445352
-0.468417    0.00673427   0.953151
-0.898513   -0.915871     0.933237  ] ./ 5.0
# Lambda1 = vec([-1.0 1.0 1.0 -1.0 -1.0 1.0 1.0 -1.0])
# Lambda2 = vec([-1.0 -1.0 1.0 1.0 -1.0 -1.0 1.0 1.0])
# Gamma1 = vec([1.0 1.0 -1.0 -1.0 -1.0 -1.0 1.0 1.0])
# xyzperturbation = zeros((8,3))
# xyzperturbation[:, 1] .= Lambda2 ./ 10.0
# xyzperturbation[:, 2] .= Lambda1 ./ 10.0
# xyzperturbation[:, 3] .= Gamma1 ./ 20.0

mesh() = (FinEtools.FENodeSetModule.FENodeSet([
	0.0 0.0 0.0;
	1.0 0.0 0.0;
	1.0 1.0 0.0;
	0.0 1.0 0.0;
	0.0 0.0 1.0;
	1.0 0.0 1.0;
	1.0 1.0 1.0
	0.0 1.0 1.0;
	]), FinEtools.FESetModule.FESetH8(reshape([1, 2, 3, 4, 5, 6, 7, 8], 1, 8)))

function single_hex_perfect_cube()
	fens,fes = mesh()

	File =  "single_hex_perfect_cube.vtk"
	vtkexportmesh(File, fens, fes)
	@async run(`"paraview.exe" $File`)

	true
end # single_hex_perfect_cube

function single_hex_full()
   	fens,fes = mesh()
    fens.xyz +=  xyzperturbation

    MR = DeforModelRed3D
    material = MatDeforElastIso(MR, E, nu)

    geom = NodalField(fens.xyz)
    u = NodalField(zeros(size(fens.xyz,1), 3)) # displacement field
	applyebc!(u)
    numberdofs!(u)

    femm = FEMMDeforLinear(MR, IntegDomain(fes, GaussRule(3, 2)), material)

    @show vol = integratefunction(femm, geom, x -> 1.0, 3)

    associategeometry!(femm, geom)
    K = stiffness(femm, geom, u)

    D = eigen(Matrix(K))

    File =  "single_hex_full.vtk"
    vectors = [("ev_$(idx)_$(round(D.values[idx] * 10000) / 10000)", deepcopy(scattersysvec!(u, D.vectors[:,idx]).values)) for idx in 1:length(D.values)]
    vtkexportmesh(File, fens, fes;  vectors=vectors)
    @async run(`"paraview.exe" $File`)

    true

end # single_hex_full

function single_hex_underintegrated()
   	fens,fes = mesh()
    fens.xyz +=  xyzperturbation

    MR = DeforModelRed3D
    material = MatDeforElastIso(MR, E, nu)

    geom = NodalField(fens.xyz)
    u = NodalField(zeros(size(fens.xyz,1), 3)) # displacement field
	applyebc!(u)
    numberdofs!(u)

    femm = FEMMDeforLinear(MR, IntegDomain(fes, GaussRule(3, 1)), material)

    @show vol = integratefunction(femm, geom, x -> 1.0, 3)

    associategeometry!(femm, geom)
    K = stiffness(femm, geom, u)

    D = eigen(Matrix(K))

    File =  "single_hex_underintegrated.vtk"
    vectors = [("ev_$(idx)_$(round(D.values[idx] * 10000) / 10000)", deepcopy(scattersysvec!(u, D.vectors[:,idx]).values)) for idx in 1:length(D.values)]
    vtkexportmesh(File, fens, fes;  vectors=vectors)
    @async run(`"paraview.exe" $File`)

    true

end # single_hex_underintegrated

function single_hex_ms()
   	fens,fes = mesh()
    fens.xyz +=  xyzperturbation

    MR = DeforModelRed3D
    material = MatDeforElastIso(MR, E, nu)

    geom = NodalField(fens.xyz)
    u = NodalField(zeros(size(fens.xyz,1), 3)) # displacement field
	applyebc!(u)
    numberdofs!(u)

    femm = FEMMDeforLinearMSH8(MR, IntegDomain(fes, GaussRule(3, 2)), material)

    @show vol = integratefunction(femm, geom, x -> 1.0, 3)

    associategeometry!(femm, geom)
    fill!(femm.phis, 0.0)
    K = stiffness(femm, geom, u)

    D = eigen(Matrix(K))

    File =  "single_hex_ms.vtk"
    vectors = [("ev_$(idx)_$(round(D.values[idx] * 10000) / 10000)", deepcopy(scattersysvec!(u, D.vectors[:,idx]).values)) for idx in 1:length(D.values)]
    vtkexportmesh(File, fens, fes;  vectors=vectors)
    @async run(`"paraview.exe" $File`)

    true

end # single_hex_ms

function allrun()
	println("#####################################################")
	println("# single_hex_perfect_cube ")
	single_hex_perfect_cube()
    println("#####################################################")
    println("# single_hex_full ")
    single_hex_full()
    println("#####################################################")
    println("# single_hex_underintegrated ")
    single_hex_underintegrated()
    println("#####################################################")
    println("# single_hex_ms ")
    single_hex_ms()
    return true
end # function allrun

end # module single_hex_examples


export UpdateElo

"""
    UpdateElo <: UpdateRule

 Update using standard Elo where K is constant, but distribution can be set (with constant parameters)
     Elo2 has logistic distribution, but allows varying K

## Parameters
* `r0::Float64`: default rating
* `K::Float64`: the value of the gain parameter (use Elo2 for variable K)
* `dist::ContinuousUnivariateDistribution`: the model to use for performance as a function of rating, e.g, Normal or Logistic
* the distribution can have its own parameters, e.g., `β::Float64`, the spread parameter for the Logistic

```
"""
struct UpdateElo <: UpdateRule
    r0::Float64
    K::Float64
    dist::ContinuousUnivariateDistribution
    function UpdateElo(r0, K, dist)
        @check_args(UpdateElo, K >= zero(K))
        @check_args(UpdateElo, r0 >= zero(r0))
        new(r0, K, dist)
    end
end
# defaults imply the distribution is  Logistic(μ=0.0, θ=100.0)
#   https://en.wikipedia.org/wiki/Logistic_distribution
#   https://juliastats.github.io/Distributions.jl/latest/univariate.html#Continuous-Distributions-1
#      mean=mu=0.0, sdev=π θ / √3
default_θ = 400 / log(10) # this results in the standard values used for Elo in Chess and many other cases
UpdateElo(;r0=1500.0, K=32.0, θ=default_θ) = UpdateElo( r0, K, Logistic(0.0, θ) )

function update_info( rule::UpdateElo )
    info = Dict(
                :name => "Elo",
                :reference => "\"Whos's #1\", Langville and Meyer, p.?? and ??",
                :computation => "simultaneous",
                :state_model => "recursive",
                :input => "outcome",
                :output => "probabilistic",
                :model => "single",
                :ties => true,
                :factors => true,
                :parameters => ["r0, default rating", "K, gain", "dist(), performance model"],
                :record => false
                )
    return info
end
update_info( ::Type{UpdateElo} ) =  update_info( UpdateElo() )

function update_ratings( rule::UpdateElo,
                         input_ratings::RatingsList,
                         input_competitions::DataFrame)
    n = size(input_competitions,1)
    m = length( input_ratings.players )
    I = player_indexes( input_ratings.players )

    # set initial ratings if they aren't already
    old_r = copy( input_ratings.ratings )
    for p in input_ratings.players
        if !haskey( old_r, p )
            old_r[p] = rule.r0
        end
    end
    new_r = copy(old_r) # start with the old ratings, in case one isn't modified at all

    # perform update for each outcome
    d = input_competitions # just an abbreviation
    for i=1:n
        pA = d[i,PlayerA]
        pB = d[i,PlayerB]
        old_rA = old_r[ pA ]
        old_rB = old_r[ pB ]
        (ΔrA, ΔrB) = update( rule, old_rA, old_rB, d[i,Outcome], missing, missing)
        new_r[ pA ] += ΔrA
        new_r[ pB ] += ΔrB
    end

    # output ratings list
    output_ratings = RatingsList(input_ratings.players, new_r )
    return output_ratings
end

function update(rule::Union{UpdateElo,UpdateEloF},
                ratingA::Real, ratingB::Real, outcome::Real,
                factorA::Union{Missing,Real}, factorB::Union{Missing,Real})
    (expectedA, expectedB, tie) = predict_outcome(rule,  ratingA, ratingB, factorA, factorB)
    outcomeA = (sign( outcome) + 1)/2
    outcomeB = (sign(-outcome) + 1)/2
    ΔrA = rule.K*( outcomeA - expectedA )
    ΔrB = rule.K*( outcomeB - expectedB )
    return ΔrA, ΔrB
end

function predict_outcome(rule::Union{UpdateElo,UpdateEloF},
                         ratingA::Real, ratingB::Real,
                         factorA::Union{Missing,Real}, factorB::Union{Missing,Real})
    rating_diff = ratingA - ratingB
    factorA = coalesce(factorA, 0.0) # replace missing values with 0.0
    factorB = coalesce(factorB, 0.0) # replace missing values with 0.0
    factors = (factorA - factorB)
    expectedA = cdf( rule.dist,  rating_diff + factors )  # do these separately in case asymmetric distribution is chosen
    expectedB = cdf( rule.dist, -rating_diff - factors )  # do these separately in case asymmetric distribution is chosen
    # at present no model for ties
    return (expectedA, expectedB, 0.0 )
end

# function simulate_outcome(rule::Union{UpdateElo,UpdateEloF},
#                           ratingA::Real, ratingB::Real,
#                           factorA::Union{Missing,Real}, factorB::Union{Missing,Real})
#     (expectedA, expectedB, tie ) = predict_outcome(rule, ratingA, ratingB, factorA, factorB)
# end


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# ╔═╡ 3b4135be-6682-11eb-1766-e772017ddc9b
begin
	using Plots
	using SpecialFunctions
	using LaTeXStrings
end

# ╔═╡ 88a17f66-667b-11eb-1d4d-57d100ff452e
md"
# Question:
Fluid bounded by two parallel plates extended to infinity. The lower wall is suddenly accelerated in the x-direction while the upper wall is stationary.

Governing Equation:

$\frac{\partial u}{\partial t} = \nu \frac{\partial^2 u}{\partial y^2}$

Initial condition:\
t = 0; u = 0     for 0 < y ≤ h \
t = 0; u = $U_0$ for y = 0

Boundary condition: \
t ≥ 0; u = $U_0$ for y = 0 \
t ≥ 0; u = 0 for y = h

"

# ╔═╡ 291e4152-667e-11eb-3d13-031ad9572916
# Code for solving the problem
begin
	ν = 0.000217
	h = 0.04
	u_b = 40.0
	n = 41
    δx = h/(n-1)

	function makegrid(h,n,δx)
		for i in 1:n
			x[i] = 0.0 + (i-1)*δx
		end
		return x
	end

	function init(u,n)
		u      = zeros(n)
		u[1]   = u_b
		return u
	end

	function exact_sol(x,n,t_eval)
		@info(t_eval)
		uex = zeros(n)
		for i in 1:n
			η = x[i]/(2*sqrt(ν*t_eval))
			η1= h/(2*sqrt(ν*t_eval))
			uex[i] = u_b* (erfc(η) - erfc(2*η1 - η) + erfc(2*η1 + η)  - erfc(4*η1 - η) + erfc(4*η1 + η) )
		end
		return uex
	end

	#FTCS Time stepping
	function FTCS(δt,tmax,δx,u,n,ν,u_b)
		u_new = zeros(n)

	    #BC's
		u_new[1] = u_b
		u_new[n] = 0.0
		#Update
		t = 0.0
		nt= floor(Int,tmax/δt)
		for i in 1:nt
			for j in 2:n-1
				u_new[j] = u[j] + ν*(δt/(δx)^2) * (u[j+1] - 2*u[j] + u[j-1])
			end
			t = t + δt
			u = copy(u_new)
		end
		return u
	end

	#Dufort-Frankel Timestepping
	function DFF(δt,tmax,δx,u,n,ν,u_b)
		u_new = zeros(n)
		u_old = zeros(n)

	    #BC's
		u_new[1] = u_b
		u_new[n] = 0.0
		#Update
		t = 0.0
		nt= floor(Int,tmax/δt)
		d = ν*(δt/(δx)^2)

		u_old = copy(u)
		for i in 1:nt
			for j in 2:n-1
				u_new[j] = ((1.0-2.0*d)*u_old[j] + 2*d*(u[j+1] + u[j-1]))/(1.0+2.0*d)
			end
			t = t + δt
			u_old = copy(u)
			u = copy(u_new)
		end
		return u
	end

	function TriD(A,B,C,D,n)
		#/// n is only "n-2" in global
		a = copy(A)
		b = copy(B)
		c = copy(C)
		d = copy(D)
		u = zeros(n)
		for i in 2:n
			w = a[i]/b[i-1]
			b[i] = b[i] - w*c[i-1]
			d[i] = d[i] - w*d[i-1]
		end

		#Back solve
		u[n] = d[n]/b[n]
		for i in n-1:-1:1
			u[i] = (d[i] - c[i]*u[i+1])/b[i]
		end
		#@info(u)
		return u
	end

	#Laason implicit solve
	function LAimpl(δt,tmax,δx,u,n,ν,u_b)
		u_new = zeros(n)
		u_old = zeros(n)

	    #BC's
		u_new[1] = u_b
		u_new[n] = 0.0
		#Update
		t = 0.0
		nt= floor(Int,tmax/δt)
		d = ν*(δt/(δx)^2)

		A = zeros(n-2)
		B = zeros(n-2)
		C = zeros(n-2)
		D = zeros(n-2)
		for i in 1:nt
			A  .= -d
			B  .= 1+2*d
			C  .= -d
			D  .= copy(u[2:n-1])
			D[1]  = D[1]   + d*u_b
			D[n-2]= D[n-2] + d*0.0

			u_new[2:n-1] = TriD(A,B,C,D,n-2)

			u_new[1] = u_b
			u_new[n] = 0.0
			u = copy(u_new)
		end
		return u
	end

	#Crank-Nicolson implicit solve
	function CNimpl(δt,tmax,δx,u,n,ν,u_b)
		u_new = zeros(n)
		u_old = zeros(n)

	    #BC's
		u_new[1] = u_b
		u_new[n] = 0.0

		u[1] = u_b
		u[n] = 0.0
		#Update
		t = 0.0
		nt= floor(Int,tmax/δt)
		d = ν*(δt/(δx)^2)

		A = zeros(n-2)
		B = zeros(n-2)
		C = zeros(n-2)
		D = zeros(n-2)
		for i in 1:nt
			A  .= -0.5*d
			B  .= 1+1*d
			C  .= -0.5*d
			D  .= copy(u[2:n-1]) + 0.5*d*(u[3:n]-2.0*u[2:n-1]+u[1:n-2])

			D[1]  = D[1]   + 0.5*d*u_b
			D[n-2]= D[n-2] + 0.5*d*0.0

			u_new[2:n-1] = TriD(A,B,C,D,n-2)

			u_new[1] = u_b
			u_new[n] = 0.0
			u = copy(u_new)
		end
		return u
	end

	x = zeros(n)
	u = zeros(n)
	uex= zeros(n)

	#Run necessary stuff
	x = makegrid(h,n,δx)
	u = init(u,n)

end

# ╔═╡ 2e306e02-6719-11eb-2755-e364e8d201cc
md"# Velocity Profiles across schemes"

# ╔═╡ 270a7404-6713-11eb-2cf1-8b17120d4405
md"# Velocity Profiles for FTCS"

# ╔═╡ 1a86f16c-6713-11eb-2f1e-53e78d8e6a15
begin
	#Velocity Profiles for FTCS explicit method
	δt = 0.002
	tmax= [0.0,0.18,0.36,0.54,0.72,0.90,1.08]

	u_1 = FTCS(δt,tmax[1],δx,u,n,ν,u_b)
	u_2 = FTCS(δt,tmax[2],δx,u,n,ν,u_b)
	u_3 = FTCS(δt,tmax[3],δx,u,n,ν,u_b)
	u_4 = FTCS(δt,tmax[4],δx,u,n,ν,u_b)
	u_5 = FTCS(δt,tmax[5],δx,u,n,ν,u_b)
	u_6 = FTCS(δt,tmax[6],δx,u,n,ν,u_b)
	u_7 = FTCS(δt,tmax[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.002, d = 0.434")
	plot!(xlabel="u",ylabel="y")
	plot!(u_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u_2,x,markershape = :ltriangle,label="t=0.18sec")
	plot!(u_3,x,markershape = :ltriangle,label="t=0.36sec")
	plot!(u_4,x,markershape = :ltriangle,label="t=0.54sec")
	plot!(u_5,x,markershape = :ltriangle,label="t=0.72sec")
	plot!(u_6,x,markershape = :ltriangle,label="t=0.90sec")
	plot!(u_7,x,markershape = :ltriangle,label="t=1.08sec")

end

# ╔═╡ fb155b98-6715-11eb-0231-2bc9fde178fa
begin
	#Velocity Profiles for FTCS explicit method
	δt1 = 0.00232
	tmax1= [0.0,0.21,0.42,0.63,0.84,1.04,1.25]

	u1_1 = FTCS(δt1,tmax1[1],δx,u,n,ν,u_b)
	u1_2 = FTCS(δt1,tmax1[2],δx,u,n,ν,u_b)
	u1_3 = FTCS(δt1,tmax1[3],δx,u,n,ν,u_b)
	u1_4 = FTCS(δt1,tmax1[4],δx,u,n,ν,u_b)
	u1_5 = FTCS(δt1,tmax1[5],δx,u,n,ν,u_b)
	u1_6 = FTCS(δt1,tmax1[6],δx,u,n,ν,u_b)
	u1_7 = FTCS(δt1,tmax1[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.00232, d = 0.50344")
	plot!(xlabel="u",ylabel="y")
	plot!(u1_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u1_2,x,markershape = :ltriangle,label="t=0.21sec")
	plot!(u1_3,x,markershape = :ltriangle,label="t=0.42sec")
	plot!(u1_4,x,markershape = :ltriangle,label="t=0.63sec")
	plot!(u1_5,x,markershape = :ltriangle,label="t=0.84sec")
	plot!(u1_6,x,markershape = :ltriangle,label="t=1.04sec")
	plot!(u1_7,x,markershape = :ltriangle,label="t=1.25sec")

end

# ╔═╡ c3e4e836-6716-11eb-1fe8-4b01a8f19efe
md"# Velocity Profiles for Dufort-Frankel"

# ╔═╡ 800e1682-6716-11eb-2968-1f922c7f834c
begin
	#Velocity Profiles for DFF explicit method
	δt2 = 0.002
	tmax2= [0.0,0.18,0.36,0.54,0.72,0.90,1.08]

	u2_1 = DFF(δt2,tmax2[1],δx,u,n,ν,u_b)
	u2_2 = DFF(δt2,tmax2[2],δx,u,n,ν,u_b)
	u2_3 = DFF(δt2,tmax2[3],δx,u,n,ν,u_b)
	u2_4 = DFF(δt2,tmax2[4],δx,u,n,ν,u_b)
	u2_5 = DFF(δt2,tmax2[5],δx,u,n,ν,u_b)
	u2_6 = DFF(δt2,tmax2[6],δx,u,n,ν,u_b)
	u2_7 = DFF(δt2,tmax2[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.002, d = 0.434")
	plot!(xlabel="u",ylabel="y")
	plot!(u2_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u2_2,x,markershape = :ltriangle,label="t=0.18sec")
	plot!(u2_3,x,markershape = :ltriangle,label="t=0.36sec")
	plot!(u2_4,x,markershape = :ltriangle,label="t=0.54sec")
	plot!(u2_5,x,markershape = :ltriangle,label="t=0.72sec")
	plot!(u2_6,x,markershape = :ltriangle,label="t=0.90sec")
	plot!(u2_7,x,markershape = :ltriangle,label="t=1.08sec")

end

# ╔═╡ 39b664cc-6717-11eb-11b1-b74350c4341d
begin
	#Velocity Profiles for FTCS explicit method
	δt3 = 0.003
	tmax3= [0.0,0.21,0.42,0.63,0.84,1.04,1.25]

	u3_1 = DFF(δt3,tmax3[1],δx,u,n,ν,u_b)
	u3_2 = DFF(δt3,tmax3[2],δx,u,n,ν,u_b)
	u3_3 = DFF(δt3,tmax3[3],δx,u,n,ν,u_b)
	u3_4 = DFF(δt3,tmax3[4],δx,u,n,ν,u_b)
	u3_5 = DFF(δt3,tmax3[5],δx,u,n,ν,u_b)
	u3_6 = DFF(δt3,tmax3[6],δx,u,n,ν,u_b)
	u3_7 = DFF(δt3,tmax3[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.003, d = 0.651")
	plot!(xlabel="u",ylabel="y")
	plot!(u3_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u3_2,x,markershape = :ltriangle,label="t=0.21sec")
	plot!(u3_3,x,markershape = :ltriangle,label="t=0.42sec")
	plot!(u3_4,x,markershape = :ltriangle,label="t=0.63sec")
	plot!(u3_5,x,markershape = :ltriangle,label="t=0.84sec")
	plot!(u3_6,x,markershape = :ltriangle,label="t=1.04sec")
	plot!(u3_7,x,markershape = :ltriangle,label="t=1.25sec")

end

# ╔═╡ 99b7da0e-6717-11eb-061d-ef8e525fa7a6
md"# Velocity Profiles for Laason Implicit"

# ╔═╡ c2e5d336-6717-11eb-04f4-d9506627a208
begin
	#Velocity Profiles for LAimplicit method
	δt4 = 0.002
	tmax4= [0.0,0.18,0.36,0.54,0.72,0.90,1.08]

	u4_1 = LAimpl(δt4,tmax4[1],δx,u,n,ν,u_b)
	u4_2 = LAimpl(δt4,tmax4[2],δx,u,n,ν,u_b)
	u4_3 = LAimpl(δt4,tmax4[3],δx,u,n,ν,u_b)
	u4_4 = LAimpl(δt4,tmax4[4],δx,u,n,ν,u_b)
	u4_5 = LAimpl(δt4,tmax4[5],δx,u,n,ν,u_b)
	u4_6 = LAimpl(δt4,tmax4[6],δx,u,n,ν,u_b)
	u4_7 = LAimpl(δt4,tmax4[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.002, d = 0.434")
	plot!(xlabel="u",ylabel="y")
	plot!(u4_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u4_2,x,markershape = :ltriangle,label="t=0.18sec")
	plot!(u4_3,x,markershape = :ltriangle,label="t=0.36sec")
	plot!(u4_4,x,markershape = :ltriangle,label="t=0.54sec")
	plot!(u4_5,x,markershape = :ltriangle,label="t=0.72sec")
	plot!(u4_6,x,markershape = :ltriangle,label="t=0.90sec")
	plot!(u4_7,x,markershape = :ltriangle,label="t=1.08sec")
		plot!(exact_sol(x,n,0.18),x)
end

# ╔═╡ 0ce28b94-6718-11eb-1793-c5f06bfe5686
begin
	#Velocity Profiles for LaasonImplicit method
	δt5 = 0.01
	tmax5= [0.0,0.21,0.42,0.63,0.84,1.04,1.25]

	u5_1 = LAimpl(δt5,tmax5[1],δx,u,n,ν,u_b)
	u5_2 = LAimpl(δt5,tmax5[2],δx,u,n,ν,u_b)
	u5_3 = LAimpl(δt5,tmax5[3],δx,u,n,ν,u_b)
	u5_4 = LAimpl(δt5,tmax5[4],δx,u,n,ν,u_b)
	u5_5 = LAimpl(δt5,tmax5[5],δx,u,n,ν,u_b)
	u5_6 = LAimpl(δt5,tmax5[6],δx,u,n,ν,u_b)
	u5_7 = LAimpl(δt5,tmax5[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.01, d = 2.17")
	plot!(xlabel="u",ylabel="y")
	plot!(u3_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u3_2,x,markershape = :ltriangle,label="t=0.21sec")
	plot!(u3_3,x,markershape = :ltriangle,label="t=0.42sec")
	plot!(u3_4,x,markershape = :ltriangle,label="t=0.63sec")
	plot!(u3_5,x,markershape = :ltriangle,label="t=0.84sec")
	plot!(u3_6,x,markershape = :ltriangle,label="t=1.04sec")
	plot!(u3_7,x,markershape = :ltriangle,label="t=1.25sec")

end

# ╔═╡ 1eb83680-6719-11eb-16c6-89de2999c61c
md"# Velocity Profiles for Crank-Nicolson Implicit"

# ╔═╡ 1d27d028-6719-11eb-3ac6-7147ea8109ef
begin
	#Velocity Profiles for CNimplicit method
	δt6 = 0.002
	tmax6= [0.0,0.18,0.36,0.54,0.72,0.90,1.08]

	u6_1 = CNimpl(δt6,tmax6[1],δx,u,n,ν,u_b)
	u6_2 = CNimpl(δt6,tmax6[2],δx,u,n,ν,u_b)
	u6_3 = CNimpl(δt6,tmax6[3],δx,u,n,ν,u_b)
	u6_4 = CNimpl(δt6,tmax6[4],δx,u,n,ν,u_b)
	u6_5 = CNimpl(δt6,tmax6[5],δx,u,n,ν,u_b)
	u6_6 = CNimpl(δt6,tmax6[6],δx,u,n,ν,u_b)
	u6_7 = CNimpl(δt6,tmax6[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.002, d = 0.434")
	plot!(xlabel="u",ylabel="y")
	plot!(u6_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u6_2,x,markershape = :ltriangle,label="t=0.18sec")
	plot!(u6_3,x,markershape = :ltriangle,label="t=0.36sec")
	plot!(u6_4,x,markershape = :ltriangle,label="t=0.54sec")
	plot!(u6_5,x,markershape = :ltriangle,label="t=0.72sec")
	plot!(u6_6,x,markershape = :ltriangle,label="t=0.90sec")
	plot!(u6_7,x,markershape = :ltriangle,label="t=1.08sec")
    plot!(exact_sol(x,n,0.18),x)
end

# ╔═╡ 04589ef0-671a-11eb-3b04-4dc8b7a0fd43
begin
	#Velocity Profiles for CNImplicit method
	δt7 = 0.01
	tmax7= [0.0,0.21,0.42,0.63,0.84,1.04,1.25]

	u7_1 = CNimpl(δt7,tmax5[1],δx,u,n,ν,u_b)
	u7_2 = CNimpl(δt7,tmax5[2],δx,u,n,ν,u_b)
	u7_3 = CNimpl(δt7,tmax5[3],δx,u,n,ν,u_b)
	u7_4 = CNimpl(δt7,tmax5[4],δx,u,n,ν,u_b)
	u7_5 = CNimpl(δt7,tmax5[5],δx,u,n,ν,u_b)
	u7_6 = CNimpl(δt7,tmax5[6],δx,u,n,ν,u_b)
	u7_7 = CNimpl(δt7,tmax5[7],δx,u,n,ν,u_b)

	gr()
	plot(title="Velocity Profile for dy = 0.001, dt = 0.01, d = 2.17")
	plot!(xlabel="u",ylabel="y")
	plot!(u7_1,x,markershape = :ltriangle,label="t=0.0sec")
	plot!(u7_2,x,markershape = :ltriangle,label="t=0.21sec")
	plot!(u7_3,x,markershape = :ltriangle,label="t=0.42sec")
	plot!(u7_4,x,markershape = :ltriangle,label="t=0.63sec")
	plot!(u7_5,x,markershape = :ltriangle,label="t=0.84sec")
	plot!(u7_6,x,markershape = :ltriangle,label="t=1.04sec")
	plot!(u7_7,x,markershape = :ltriangle,label="t=1.25sec")
	plot!(exact_sol(x,n,0.21),x)

end

# ╔═╡ 08bc0ee8-671d-11eb-1e49-e392f826eba5
md"# Error Distribution for t = 0.18 sec, 1.08sec"

# ╔═╡ bb3fd91e-671d-11eb-1c84-d518b84da067
#Calculating Error
function err_calc(uexact,ucalc,n)
	err = zeros(n)
	for i in 1:n
		err[i] = (uexact[i] - ucalc[i])
	end
	return err
end

# ╔═╡ 24c371dc-671d-11eb-2611-c39d01c2ecf2
begin
	u_ex = exact_sol(x,n,0.18)
	u_ftcs = FTCS(0.002,0.18,δx,u,n,ν,u_b)
	u_dff  = DFF(0.002,0.18,δx,u,n,ν,u_b)
	u_la   = LAimpl(0.002,0.18,δx,u,n,ν,u_b)
	u_cn   = CNimpl(0.002,0.18,δx,u,n,ν,u_b)

	ftcs_err = err_calc(u_ex,u_ftcs,n)
	dff_err  = err_calc(u_ex,u_dff,n)
	la_err  = err_calc(u_ex,u_la,n)
	cn_err  = err_calc(u_ex,u_cn,n)

	plot(title="Error comparison for various schmes dt = 0.002 at t=0.18")
	plot!(xlabel="Error",ylabel="y",xlim=(-0.11,0.11))
	plot!(ftcs_err,x,markershape = :ltriangle,label="FTCS")
	plot!(dff_err,x,markershape = :ltriangle,label="Dufort-Frankel")
	plot!(la_err,x,markershape = :ltriangle,label="Laasonen-implicit")
	plot!(cn_err,x,markershape = :ltriangle,label="Crank-Nicolson-implicit")

end

# ╔═╡ 615146cc-672c-11eb-097d-41f289d3f896
begin
	u1_ex = exact_sol(x,n,1.08)
	u1_ftcs = FTCS(0.002,1.08,δx,u,n,ν,u_b)
	u1_dff  = DFF(0.002,1.08,δx,u,n,ν,u_b)
	u1_la   = LAimpl(0.002,1.08,δx,u,n,ν,u_b)
	u1_cn   = CNimpl(0.002,1.08,δx,u,n,ν,u_b)

	ftcs1_err = err_calc(u1_ex,u1_ftcs,n)
	dff1_err  = err_calc(u1_ex,u1_dff,n)
	la1_err  = err_calc(u1_ex,u1_la,n)
	cn1_err  = err_calc(u1_ex,u1_cn,n)

	plot(title="Error comparison for various schmes dt = 0.002 and t=1.08")
	plot!(xlabel="Error",ylabel="y",xlim=(-0.05,0.05))
	plot!(ftcs1_err,x,markershape = :ltriangle,label="FTCS")
	plot!(dff1_err,x,markershape = :ltriangle,label="Dufort-Frankel")
	plot!(la1_err,x,markershape = :ltriangle,label="Laasonen-implicit")
	plot!(cn1_err,x,markershape = :ltriangle,label="Crank-Nicolson-implicit")

end

# ╔═╡ dc70983e-672d-11eb-101b-47d6c05f02e2
md"# Error Distribution for different time-steps at t = 1.0sec for Lassonen"

# ╔═╡ 251d0ac8-672d-11eb-1d64-8daa66f59ffa
begin
	u2_ex = exact_sol(x,n,1.0)
	ula_1 = LAimpl(0.005,1.0,δx,u,n,ν,u_b)
	ula_2 = LAimpl(0.01,1.0,δx,u,n,ν,u_b)
	ula_3 = LAimpl(0.1,1.0,δx,u,n,ν,u_b)
	ula_4 = LAimpl(0.2,1.0,δx,u,n,ν,u_b)

	ula1_err  = err_calc(u2_ex,ula_1,n)
	ula2_err  = err_calc(u2_ex,ula_2,n)
	ula3_err  = err_calc(u2_ex,ula_3,n)
	ula4_err  = err_calc(u2_ex,ula_4,n)

	plot(title="Error comparison for different timesteps at t=1.0
		for Lassonen")
	plot!(xlabel="Error",ylabel="y")
	plot!(ula1_err,x,markershape = :ltriangle,label="dt=0.005")
	plot!(ula2_err,x,markershape = :ltriangle,label="dt=0.01")
	plot!(ula3_err,x,markershape = :ltriangle,label="dt=0.1")
	plot!(ula4_err,x,markershape = :ltriangle,label="dt=0.2")

end

# ╔═╡ Cell order:
# ╟─88a17f66-667b-11eb-1d4d-57d100ff452e
# ╠═3b4135be-6682-11eb-1766-e772017ddc9b
# ╠═291e4152-667e-11eb-3d13-031ad9572916
# ╟─2e306e02-6719-11eb-2755-e364e8d201cc
# ╟─270a7404-6713-11eb-2cf1-8b17120d4405
# ╟─1a86f16c-6713-11eb-2f1e-53e78d8e6a15
# ╟─fb155b98-6715-11eb-0231-2bc9fde178fa
# ╟─c3e4e836-6716-11eb-1fe8-4b01a8f19efe
# ╟─800e1682-6716-11eb-2968-1f922c7f834c
# ╟─39b664cc-6717-11eb-11b1-b74350c4341d
# ╟─99b7da0e-6717-11eb-061d-ef8e525fa7a6
# ╟─c2e5d336-6717-11eb-04f4-d9506627a208
# ╟─0ce28b94-6718-11eb-1793-c5f06bfe5686
# ╟─1eb83680-6719-11eb-16c6-89de2999c61c
# ╟─1d27d028-6719-11eb-3ac6-7147ea8109ef
# ╟─04589ef0-671a-11eb-3b04-4dc8b7a0fd43
# ╟─08bc0ee8-671d-11eb-1e49-e392f826eba5
# ╠═bb3fd91e-671d-11eb-1c84-d518b84da067
# ╟─24c371dc-671d-11eb-2611-c39d01c2ecf2
# ╟─615146cc-672c-11eb-097d-41f289d3f896
# ╟─dc70983e-672d-11eb-101b-47d6c05f02e2
# ╟─251d0ac8-672d-11eb-1d64-8daa66f59ffa


using Test, NaturallyUnitful
using NaturallyUnitful: c0, ħc0, k

@testset "Tests" begin
    @test natural(1u"m") ≈ uconvert(u"eV^-1", 1u"m" / ħc0)

    @test unnatural(u"m", uconvert(u"eV^-1", 1u"m" / ħc0)) ≈ 1.0u"m"

    @test natural(1e8u"m/s") ≈ convert(Float64, 1e8u"m/s" / c0)

    @test unnatural(u"m/s", convert(Float64, 1e8u"m/s" / c0)) ≈ 1e8u"m/s"

    @test natural(1u"kg") ≈ uconvert(u"GeV", 1u"kg" * c0^2)

    @test natural(1u"m"; base=u"GeV") ≈ uconvert(u"GeV^-1", 1u"m" / ħc0)

    @test unnatural(u"K", 1u"eV") ≈ uconvert(u"K", 1u"eV" / k)

    @test unnatural(u"(m/s)^(3/2)", 1) ≈ c0^(3 / 2)
end


using Plots
using Statistics
using ProgressBars
L=101
function BoundaryCondition(i)
    if i==L+1
        return 1
    elseif i==0
        return L
    else
        return i
    end
end
function height_cal(i,arr) #calculating the height of the layer in a column
    i=BoundaryCondition(i)
    row=L #starting from the top layer
    height=L
    for j in 1:L
        if arr[row,i]==0.0 #when the entry is empty
            height-=1 #decreasing height by one
            row-=1
        else
            break #exit the loop for the next column
        end
    end
    return height
end
function neighbor_checking(arr, col)
    h=[]
    for i in (col-1):1:(col+1)
        height= height_cal(i,arr)
        if i==col
            push!(h, (height+1))
        else
            push!(h, height)
        end
    end
    return maximum(h)
end
function width_cal(arr, L)
    firstx=0
    firsty=0
    col=1 #starting from the beggining
    for i in 1:L
        for row in 1:L
            if arr[row, col]!=0.0
                firstx=col
                firsty=row
                break
            end
        end
        if firstx!=0 && firsty!=0
            break
        else
            col+=1
        end
    end
    lastx=0
    lasty=0
    col=L #starting from the end
    for i in 1:L
        for row in 1:L
            if arr[row, col]!=0.0
                lastx=col
                lasty=row
                break
            end
        end
        if lastx!=0 && lasty!=0
            break
        else
            col-=1
        end
    end
    d=sqrt((firstx-lastx)^2 + (firsty-lasty)^2)
    return d
end
function deposing(L,n)
    WidthList=[]
    color=1
    arr=zeros((L,L))
    meanList=[]
    stdList=[]
    finalP=n
    anim = Animation()
    for particle in ProgressBar(1:n)
        col=rand(1:L)
        height=neighbor_checking(arr, col)
        if height>=L
            finalP=particle
            break
        end
        if height!=1 || col==ceil(Int, L/2)
            arr[height, col]=color
        end
        if particle%(10*L*color)==0
            color+=1
        end
        WidthNum=width_cal(arr, L)
        push!(WidthList, WidthNum)
        p = heatmap(hcat(arr), c=cgrad(:roma, 10, categorical = true, scale = :exp), xlabel="L")
        frame(anim, p)
    end
    gif(anim,"C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter3\\Fig\\3.4.gif",fps=50)

    return arr, WidthList, finalP
end
arr, WidthList, finalP=deposing(L,15000)
heatmap(hcat(arr), c=cgrad(:roma, 10, categorical = true, scale = :exp), xlabel="L")
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter3\\Fig\\3.4.png")
scatter(1:finalP, WidthList, xlabel="time",ylabel="width of the shrub" , markersize=0.001)
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter3\\Fig\\3.4_width.png")


# include("../setup.jl")
include("../all_functions.jl")

# process for se_kernel_base
# @vars σ δ λ
# kernel_coder(σ * exp(-δ * δ / (2 * λ * λ)), "se")
@vars δ λ
kernel_coder(se_kernel_base(λ, δ), "se")

#process for matern52_kernel_base
@vars δ λ
kernel_coder(matern52_kernel_base(λ, δ), "m52")

@vars δ, λ
kernel_coder(pp_kernel_base(λ, δ), "pp"; cutoff_var="λ")

@vars δ λ1 λ2 sratio
kernel_coder(matern52_kernel_base(λ1, δ) + sratio * sratio * matern52_kernel_base(λ2, δ), "m52_m52")

@vars δ λ1 λ2 sratio
kernel_coder(se_kernel_base(λ1, δ) + sratio * sratio * se_kernel_base(λ2, δ), "se_se")

# process for periodic_kernel_base
# @vars δ se_P λ
# kernel_coder(periodic_kernel_base([se_P, λ], δ), "periodic")
# @vars δ λ
# kernel_coder(se_kernel_base(λ, δ), "se"; periodic_var="δ")

# process for quasi_periodic_kernel_base
# @vars δ se_λ qp_P p_λ
# kernel_coder(quasi_periodic_kernel_base([se_λ, qp_P, p_λ], δ), "quasi_periodic")
@vars δ δp se_λ p_amp
kernel_coder(se_kernel_base(se_λ, δ) * se_kernel_base(1 / p_amp, δp), "qp"; periodic_var="δp")

#process for rq_kernel_base

# @vars δ k θ
# kernel_coder(rq_kernel_base([k, θ], δ), "rq_kernel")
# @vars δ α β
# kernel_coder(rq_kernel_base([α, β], δ), "rq_kernel")
@vars δ α μ
kernel_coder(rq_kernel_base([α, μ], δ), "rq")

@vars δ α μ
kernel_coder(rm52_kernel_base([α, μ], δ), "rm52")

# @vars δ P λ α
# kernel_coder(periodic_rq_kernel_base([P, λ, α], δ), "periodic_rq_kernel")
@vars δ λ α
kernel_coder(rq_kernel_base([α, λ], δ), "rq"; periodic_var="δ")

@vars δ λ
kernel_coder(cosine_kernel_base(λ, δ), "cos")


"""
    consecutive(k::Integer, sepp::SEPP)::AbstractVector{<:Real}

Compute `p_1,...,p_k` where `p_i` is the probability of an event on `i` knowing there has been events on `1,...,i-1`.
"""
function consecutive end

function consecutive(k::Integer, sepp::SEPPExpKern)::AbstractVector{<:Real}

    μ = sepp.μ
    ϕ = sepp.ϕ
    γ = sepp.γ

    times = 1:k
    ts = TimeSeries(times)

    vol = volfunc(times, ts, γ)
    intens = μ .+ ϕ .* vol
    prob = 1 .- exp.(-intens )

    return prob
end

#= TODO consecutive qui prend en compte une fonction impact pour les marques
function consecutive(k::Int, sempp::SEMPPExpKern)::AbstractVector{<:Real}

    μ = sempp.μ
    ϕ = sempp.ϕ
    γ = sempp.γ
    δ = sempp.δ
    markdens = sempp.markdens
    ξ = sempp.ξ
    α = sempp.α
    β = sempp.β
    κ = sempp.κ

    times = 1:k
    ts = TimeSeries(times)

    vol = volfunc(times, ts, γ)
    intens = μ .+ ϕ .* vol
    prob = 1 .- exp.(-intens )

    return prob
end
=#

"""
    markov_return_period(sepp::SEPP, r::Integer)::Real

Compute the expected return period of a run of r consecutive events for the model based on a markov chain.

See Li2021 4.2.
"""
function markov_return_period(sepp::SEPP, r::Integer)::Real # seulement pour SEPPExpKern pour l'instant
    p = consecutive(r+1,sepp)
    return (1 + sum(cumprod(p)[1:(r-1)]) + prod(p[1:r]) / (1 - p[r+1])) / (1 - sum((1 .- p[1:r]) .* cumprod(vcat([1], p[1:r-1]))))
end


# ------------------
# # Juliaを使ったParticle Swarm Optimization(PSO)アルゴリズムの実装
#
# - Rosenbrock functionの最適化
# - 多目的最適化にも拡張可能なように解および問題を定義
# ------------------

# Definition of a continuous optimization problem-------------------------
# 連続値最適化問題----------------------------------------------------------
"""
Abstract type of optimization problem.
最適化問題の抽象型
"""
abstract type AbstractProblem{T <: Number} end

"""
Concrete type of continuous optimization problem .
連続値最適化問題の具体型

# Fields
`objfunc`: julia function object of the objective function, 目的関数のオブジェクト
`numobjs`: number of objectives, 目的数
`numvars`: number of variables, 変数の数
`confunc`: julia function object of the constraint function, 制約計算する関数のオブジェクト
`numcons`: number of constraints, 制約の数
`limit`: upper and lower limit of the objective values, 目的関数の上下限値
`bound`: upper and lower bound of the variable values, 変数の上下限値
"""
mutable struct DoubleProblem{T} <: AbstractProblem{T}
    objfunc
    numobjs
    numvars
    confunc
    numcons
    limit::Matrix{Float64}
    bound::Matrix{T}
end
function DoubleProblem(objfunc; numvars=1, numobjs=1, confunc=NaN, numcons=0, limit=fill(NaN, 1, 1), bound=repeat([0.0 1.0], numvars))
    DoubleProblem(objfunc, numobjs, numvars, confunc, numcons, limit, bound)
end

"""
    norm(value, min, max)

Normalize value to 0-1.
`value`の値を0-1の範囲に正規化する
"""
norm(value, min, max) = (value - min) ./ (max - min)

"""
    denorm(value, min, max)

Denormalize value from 0-1 to min-max.
0-1に正規化された`value`の値をmin-maxの範囲に非正規化する
"""
denorm(value, min, max) = value .* (max - min) + min

"""
    evaluate!(problem, solutions)

Evaluate the objective value of each solution.
各解候補を評価し目的関数値を計算する
"""
function evaluate!(problem::DoubleProblem, solutions::Vector{<:AbstractSolution})
    for s in solutions
        vars = denorm(s.variables, problem.bound[:,1], problem.bound[:,2])
        objs = problem.objfunc(vars)
        if (isnan(problem.limit[1,1]))
            s.objectives = objs
        else
            s.objectives = norm(objs, problem.limit[:,1], problem.limit[:,2])
        end
    end
end

# Algorithm definition of the Particle Swarm Optimization---------------------
# Particle Swarm Optimizationの解とアルゴリズム定義-----------------------------
"""
Abstract type of solution.
最適化問題の解の抽象型
"""
abstract type AbstractSolution{T <: Number} end
"""
Concrete type of particle in PSO algorithm.
粒子群最適化における粒子(解)の具体型
"""
mutable struct Particle{T} <: AbstractSolution{T}
    objectives::Vector{Float64}
    variables::Vector{T}
    velocities::Vector{T}
    pbestvars::Vector{T}
    pbestobjs::Vector{Float64}
    constraints::Vector{T}
end

"""
    Particle(numvars[, numobjs, numcons]) -> Particle

Outer constractor of `Particle`.
`Particle`型の外部コンストラクタ

# Arguments 引数
- `numvars`: dimention (number of variables), 次元(変数の数)
- `numobjs = 1`: number of objectives, 目的数
- `numcons = 0`: number of constraints, 制約数
"""
function Particle(numvars, numobjs=1, numcons=0)
    objectives = ones(numobjs) .* Inf
    variables  = rand(numvars)
    velocities = zeros(numvars)
    pbestvars  = copy(variables)
    pbestobjs = ones(numobjs) .* Inf
    constraints = zeros(numcons)
    return Particle(objectives, variables, velocities, pbestvars, pbestobjs, constraints)
end

"""
    updateswarm!(swarm, gbest)

Update the position and velocity of the particles using the global best.
その世代の粒子群のうち最も良い解(gbest)を用いて，粒子の位置と速度を更新する
"""
function updateswarm!(swarm::Vector{Particle{Float64}}, gbest::Particle, c1=NaN, c2=NaN, w=NaN, vecr=false)
    for p in swarm
        if(vecr)
            r1 = rand(length(p.variables))
            r2 = rand(length(p.variables))
        else
            r1 = rand()
            r2 = rand()
        end
        if(isnan(c1)) c1 = rand() * 0.5 + 1.5 end
        if(isnan(c2)) c2 = rand() * 0.5 + 1.5 end
        if(isnan(w))  w = rand() * 0.4 + 0.1 end

        # 位置と速度の更新
        p.velocities = w * p.velocities + c1 * r1 * (p.pbestvars - p.variables) + c2 * r2 * (gbest.variables - p.variables)
        p.variables += p.velocities

        # 境界チェック
        for i = 1:length(p.variables)
            if (p.variables[i] > 1.0)
                p.variables[i] = 1.0
                p.velocities[i] *= -1.0
            elseif (p.variables[i] < 0.0)
                p.variables[i] = 0.0
                p.velocities[i] *= -1.0
            end
        end
    end
end

"""
    perturbation!(swarm)

Apply a mutation to each particles.
粒子位置に突然変異を適用する
"""
function perturbation!(swarm::Vector{Particle{Float64}})

end

"""
    updatepbest!(swarm)

Update the personal best of each particles.
その粒子がこれまで通った最も良い位置(pbest)を更新する
"""
function updatepbest!(swarm::Vector{Particle{Float64}})
    for p in swarm
        if (p.objectives < p.pbestobjs)
            p.pbestvars = copy(p.variables)
            p.pbestobjs = copy(p.objectives)
        end
    end
end

"""
    updategbest!(swarm, gbest)

Update the global best particle.
その世代の粒子群のうち最も良い解(gbest)を更新する
"""
function updategbest!(swarm::Vector{Particle{Float64}}, gbest::Particle{Float64})
    for p in swarm
        if (p.objectives < gbest.objectives)
            gbest.variables = copy(p.variables)
            gbest.objectives = copy(p.objectives)
        end
    end
end

"""
    pso(problem, iter) -> Particle

Algorithm of the Particle Swarm Optimization.
粒子群最適化アルゴリズムを実行しgbestを返す

# Arguments 引数
- `problem`: 問題の型(DoubleProblem型)
- `iter`: iterations, 繰り返し回数
- `nump`: number of particles, 粒子群サイズ．指定しなければ変数の数×10
- `c1`,`c2`,`w`: the parameters of PSO.
- `vecr`: use vectorized `r1`, `r2`. `r1`, `r2`をベクトル化するか否か(t/f)
"""
function pso(problem::DoubleProblem; iter, nump=problem.numvars*10, c1=NaN, c2=NaN, w=NaN, vecr=false)
    # 粒子群の初期化
    swarm = [Particle(problem.numvars) for i = 1:nump]
    gbest = Particle(problem.numvars)
    evaluate!(problem, swarm)
    updategbest!(swarm, gbest)
    updatepbest!(swarm)

    # 最適化の実行
    while (iter > 0)
        updateswarm!(swarm, gbest, c1, c2, w, vecr)
        perturbation!(swarm)
        evaluate!(problem, swarm)
        updategbest!(swarm, gbest)
        updatepbest!(swarm)
        iter -= 1
    end
    return gbest
end

"""
    rosenbrock(x) -> Float64

Function formulation of the Rosenbrock function.
See also: [Rosenbrock function - Wikipedia](https://en.wikipedia.org/wiki/Rosenbrock_function)
ローゼンブロック関数の定義
"""
function rosenbrock(x)
    [sum([ 100(x[i + 1] - x[i]^2)^2 + (x[i] - 1)^2 for i in 1:length(x) - 1])]
end

"""
Main function.
"""
function main()
    # 最適化問題の定義
    numvars = 3
    bound = [[-5.0 5.0];[-5.0 5.0];[-5.0 5.0]]
    problem = DoubleProblem(rosenbrock, numvars=numvars, bound=bound)

    # 最適化の実行
    res = pso(problem, iter=1000, nump=100)

    # 結果の表示
    vars = denorm(res.variables, bound[:,1], bound[:,2])
    objs = res.objectives
    println("objectives: " * string(objs))
    println("variables: " * string(vars))
end

@time main()


func(x) = [0.5*sqrt(abs(x[1] + x[2])), 1.5*x[1] + 0.5*x[2]]
Inputs = [0.3,900.0]
fp_simple   = fixed_point(func, Inputs; Algorithm = :Simple)
fp_simple.Convergence_ < 1e-10
fp_anderson = fixed_point(func, Inputs; Algorithm = :Anderson)
fp_anderson.Convergence_ < 1e-10
fp_aitken   = fixed_point(func, Inputs; Algorithm = :Aitken)
fp_aitken.Convergence_ < 1e-10
fp_newton   = fixed_point(func, Inputs; Algorithm = :Newton)
fp_newton.Convergence_ < 1e-10
fp_VEA      = fixed_point(func, Inputs; Algorithm = :VEA)
fp_VEA.Convergence_ < 1e-10
fp_SEA      = fixed_point(func, Inputs; Algorithm = :SEA)
fp_SEA.Convergence_ < 1e-10
fp_MPE      = fixed_point(func, Inputs; Algorithm = :MPE)
fp_MPE.Convergence_ < 1e-10
fp_RRE      = fixed_point(func, Inputs; Algorithm = :RRE)
fp_RRE.Convergence_ < 1e-10

# Now trying a function with a typeswitch from Int to Float
Inputs = [1,4]
fp_simple   = fixed_point(func, Inputs; Algorithm = :Simple)
fp_simple.Convergence_ < 1e-10
fp_anderson = fixed_point(func, Inputs; Algorithm = :Anderson)
fp_anderson.Convergence_ < 1e-10

# And finally printing a couple of them to test printing.
fp_anderson2 = fixed_point(func, Inputs; Algorithm = :Anderson, PrintReports = true)
fp_anderson2.Convergence_ < 1e-10
fp_simple2   = fixed_point(func, Inputs; Algorithm = :Simple, PrintReports = true)
fp_simple2.Convergence_ < 1e-10

# Testing function that returns a missing
f(x) = [missing, missing]
fp_simple3   = fixed_point(f, Inputs; Algorithm = :Simple, PrintReports = true)
fp_simple3.TerminationCondition_ == :InvalidInputOrOutputOfIteration
f(x) = [1, missing]
fp_simple4   = fixed_point(f, Inputs; Algorithm = :Simple, PrintReports = true)
fp_simple4.TerminationCondition_ == :InvalidInputOrOutputOfIteration

# Testing the outputting of a tuple
f(x) = (a = [1.0, 1.0], b = :goodProgressInFunction)
@test_throws ErrorException("The Fixedpoint function can only return a vector or a tuple  of which the first entry is the vector for which a fixedpoint is sought and the second is a namedtuple (the contents of which are output for the user but are not used in fixed point acceleration).") fixed_point(f, Inputs; Algorithm = :Simple, PrintReports = true)
# And doing side effects properly.
f(x) = ([1.0, 1.0], (b = :goodProgressInFunction,))
fp_simple5 = fixed_point(f, Inputs; Algorithm = :Simple, PrintReports = true)
fp_simple5.Convergence_ < 1e-10


#---------------------------------------------------------------------#
#This routine advances the solution in time using SSP Osher-Shu RK2 or RK3
#Written by F.X. Giraldo / P.R. Mugg on 9/20/19
#           Department of Applied Mathematics
#           Naval Postgraduate School
#           Monterey, CA 93943-5216
#---------------------------------------------------------------------#
function ssp_rk(q0,c,kstages,Δt,time)

   qp = q0
   q1 = q0
   for ik = 1:kstages
      if (kstages == 2)     #RK2
         if (ik == 1)
            α0 = 1.0; α1 = 0.0; β = 1.0
            t = time
         elseif (ik == 2)
            α0 = 0.5; α1 = 0.5; β = 0.5
            t = time + Δt
         end # ik
      elseif (kstages == 3)  #RK3
         if (ik == 1)
            α0 = 1.0; α1 = 0.0; β = 1.0
            t = time
         elseif (ik == 2)
            α0 = 3.0/4.0; α1 = 1.0/4.0; β = 1.0/4.0
            t = time + Δt
         elseif (ik == 3)
            α0 = 1.0/3.0; α1 = 2.0/3.0; β = 2.0/3.0
            t = time + (Δt/2)
         end # ik
      end #kstages

      #Construct RHS
      (rhs,f,g) = rhs_function(qp,c,t)

      #Update
      qp = α0*q0 + α1*q1 + Δt*β*rhs
      q1 = qp
   end #for ik

   return qp
end #end function


using PtFEM

data = Dict(
  # Beam(ndim, nst, nxe, nip, direction, finite_element(nod, nodof), axisymmet
  :struc_el => Beam(2, 1, 4, 1, :x, Line(2, 2), false),
  :properties => [4.0e4; 2.0e4],
  :etype => [1, 1, 2, 2],
  :x_coords => [0.0, 2.5, 5.0, 8.0, 10.0],
  :support => [
      (1, [0 1]),
      (4, [0 1])
    ],
  :loaded_nodes => [
      (2, [-20.0 0.0]),
      (3, [-6.0 -3.0]),
      (4, [-8.8 2.2]),
      (5, [-1.2 0.5333])
    ],
  :fixed_freedoms => [
      (1, 2, -0.001),
      (3, 1, -0.005)
    ],
  :penalty => 1e19,
  :eq_nodal_forces_and_moments => [
    (3, [-6.0 -3.0 -6.0 3.0]),
    (4, [-2.8 -0.8 -1.2  0.5333])
  ]
)

data |> display
println()

@time fem, dis_df, fm_df = p43(data)
println()

display(dis_df)
println()
display(fm_df)


import Base: convert, promote_rule, show, real, imag, conj, abs, abs2, inv, +, -, /, *

immutable Quaternion{T<:Real} <: Number
    q0::T
    q1::T
    q2::T
    q3::T
end

Quaternion(q0::Real,q1::Real,q2::Real,q3::Real) = Quaternion(promote(q0,q1,q2,q3)...)

convert{T}(::Type{Quaternion{T}}, x::Real) =
    Quaternion(convert(T,x), zero(T), zero(T), zero(T))
convert{T}(::Type{Quaternion{T}}, z::Complex) =
    Quaternion(convert(T,real(z)), convert(T,imag(z)), zero(T), zero(T))
convert{T}(::Type{Quaternion{T}}, z::Quaternion) =
    Quaternion(convert(T,z.q0), convert(T,z.q1), convert(T,z.q2), convert(T,z.q3))

promote_rule{T,S}(::Type{Complex{T}}, ::Type{Quaternion{S}}) = Quaternion{promote_type(T,S)}
promote_rule{S}(::Type{Bool}, ::Type{Quaternion{S}}) = Quaternion{S}
promote_rule{T<:Real,S}(::Type{T}, ::Type{Quaternion{S}}) = Quaternion{promote_type(T,S)}
promote_rule{T,S}(::Type{Quaternion{T}}, ::Type{Quaternion{S}}) = Quaternion{promote_type(T,S)}

function show(io::IO, z::Quaternion)
    pm(x) = x < 0 ? " - $(-x)" : " + $x"
    print(io, z.q0, pm(z.q1), "i", pm(z.q2), "j", pm(z.q3), "k")
end

real(z::Quaternion) = z.q0
imag(z::Quaternion) = z.q1

conj(z::Quaternion) = Quaternion(z.q0, -z.q1, -z.q2, -z.q3)
abs(z::Quaternion) = sqrt(z.q0*z.q0 + z.q1*z.q1 + z.q2*z.q2 + z.q3*z.q3)
abs2(z::Quaternion) = z.q0*z.q0 + z.q1*z.q1 + z.q2*z.q2 + z.q3*z.q3
inv(z::Quaternion) = conj(z)/abs2(z)

(-)(z::Quaternion) = Quaternion(-z.q0, -z.q1, -z.q2, -z.q3)

(/)(z::Quaternion, x::Real) = Quaternion(z.q0/x, z.q1/x, z.q2/x, z.q3/x)

(+)(z::Quaternion, w::Quaternion) = Quaternion(z.q0 + w.q0, z.q1 + w.q1,
                                               z.q2 + w.q2, z.q3 + w.q3)
(-)(z::Quaternion, w::Quaternion) = Quaternion(z.q0 - w.q0, z.q1 - w.q1,
                                               z.q2 - w.q2, z.q3 - w.q3)
(*)(z::Quaternion, w::Quaternion) = Quaternion(z.q0*w.q0 - z.q1*w.q1 - z.q2*w.q2 - z.q3*w.q3,
                                               z.q0*w.q1 + z.q1*w.q0 + z.q2*w.q3 - z.q3*w.q2,
                                               z.q0*w.q2 - z.q1*w.q3 + z.q2*w.q0 + z.q3*w.q1,
                                               z.q0*w.q3 + z.q1*w.q2 - z.q2*w.q1 + z.q3*w.q0)
(/)(z::Quaternion, w::Quaternion) = z*inv(w)

q = Quaternion(1,0,0,0)
x = Quaternion(0,1,1,1)

println("q = $q")
println("q*2.0+2 = $(q*2.0+2)")
println("abs((-q+x*2)/4) = ", abs((-q+x*2)/4))


using Plots
using Printf
# %%
#f = (a::Float64,b::Float64,n::Int64) -> n==1 ? (b+a/b)/2 : (f(a,b,n-1) + a/f(a,b,n-1))/2

function f(a::Float64, b::Float64, n::Int64)
    if n == 1
        return (b+a/b)/2
    else
        return (f(a, b, n-1) + a/f(a, b, n-1))/2
    end
end

function nohint_f(a, b, n)
    if n == 1
        return (b+a/b)/2
    else
        return (f(a, b, n-1) + a/f(a, b, n-1))/2
    end
end

g = n -> f(3.0,3.0,n)
nohint_g = n -> nohint_f(3.0,3.0,n)

for n in 1:20
    @printf("n=%d g(n)=%f\n",n, g(n))
end

#%%
anim = @animate for n=1:20
    seq = collect(1:n)
    plot(seq,g.(seq))
end
gif(anim, "anim_fps10.gif", fps = 10)


using RecordedArrays
using Random
using EvolutionaryModelingTools
using EvolutionaryModelingTools.Scalar

# parameters
const T = 10.0 # max time
const r = 0.5   # growth rate
const d = 0.1   # death rate
const K = 100   # carrying capacity
const n = 10    # initial population size

@cfunc growth_c(r, n) = r * n
@ufunc growth_u!(ind, n) = n[ind] += 1

@cfunc death_c(d, n) = d * n
@ufunc death_u!(ind, n) = n[ind] -= 1

@cfunc comp_c(r, K, n) = r * n * n' / K
@ufunc comp_u!(ind::CartesianIndex{0}, n) = n[ind] -= 1

# the ind must be CartesianIndex{0}, this is just for test
@ufunc check_extinction(ind::CartesianIndex{0}, n) = n[ind] == 0 ? :extinct : :finnish

growth = Reaction(growth_c, growth_u!)
death = Reaction(death_c, death_u!)
competition = Reaction(comp_c, comp_u!)

_maybe_scalar(n::Number) = scalar(n)
_maybe_scalar(n) = n

function run_gillespie(rng=Random.GLOBAL_RNG, T=T, r=r, d=d, K=K, n=n)
    ps = (; r, d, K, n=_maybe_scalar(n))
    rs = (growth, death, competition)
    return gillespie(check_extinction, rng, T, ps, rs)
end

function run_gillespie_record(rng=Random.GLOBAL_RNG, T=T, r=r, d=d, K=K, n=n)
    clock = ContinuousClock(T)
    ps = (; r, d, K, n= recorded(DynamicEntry, clock, n))
    rs = (growth, death, competition)
    return gillespie(check_extinction, rng, clock, ps, rs)
end

function run_manually(rng=Random.GLOBAL_RNG, T=T, r=r, d=d, K=K, n=n)
    t = 0
    while t <= T
        # calculate rate
        growth_rate = r * n              # intrinsic growth
        death_rate = d * n               # intrinsic death
        competition_rate = r * n * n / K # resource competition
        summed = growth_rate + death_rate + competition_rate  # sum a_i
        # break if summed is zero
        summed == 0 && break
        # update current time
        t += -log(rand(rng)) / summed
        # sample a reaction and adjust population size
        rn = rand(rng) * summed
        if rn < growth_rate
            n += 1
        else
            n -= 1
        end
    end
    return n
end

function run_manually_record(rng=Random.GLOBAL_RNG, T=T, r=r, d=d, K=K, n=n)
    clock = ContinuousClock(T)
    n = recorded(DynamicEntry, clock, n)
    for _ in clock
        # calculate rate
        growth_rate = r * n              # intrinsic growth
        death_rate = d * n               # intrinsic death
        competition_rate = r * n * n / K # resource competition
        summed = growth_rate + death_rate + competition_rate  # sum a_i
        # break if summed is zero
        summed == 0 && break
        # update current time
        increase!(clock, -log(rand(rng)) / summed)
        # sample a reaction and adjust population size
        rn = rand(rng) * summed
        if rn < growth_rate
            n[] += 1
        else
            n[] -= 1
        end
    end
    return n
end


# Define a function
f(x,y) = exp(-(x-y)^2)

# Evaluate the function
f(0.2, 1) # Returns 0.5272924240430484

# Evaluate with a Float32
f(0.2f0, 1) #Returns 0.5272924f0

@code_typed f(0.2, 1)

f(im*0.2, 1f0) # 0.3526677021528701 + 0.1491055129382033im

# Infer which function is called
@which exp(0.2f0)
@which exp(im*0.2f0)

# Simple definition of a data structure
using Base: Number
struct MyNumber <: Number end

# Dispatch on the definition
Base.exp(t::MyNumber) = println("Foo")

# Execute
a = MyNumber()
exp(a) # Returns "Foo"

# Import Linear Algebra
using LinearAlgebra

# Add a function dispatch on two vectors
f(x::AbstractVector, y::AbstractVector) = exp.(-(x-y).^2)

# Define two random vectors
x = randn(10); y = randn(10);

f(x,y) # Returns a vector

methods(f) # Show the available methods corresponding to f


# UT

function datetime2gpsws(dt::DateTime)
    d = diff2UTC(dt, tstGPS)
    jd = datetime2julian(dt - d)
    jd -= GPS_EPOCH_JD
    w = floor(Int64, jd) ÷ 7
    s = round(Int64, (jd - w * 7) * 86400)
    return w, s
end

function gpsws2datetime(w::Int64, s::Union{Int64,Float64})
    dow =  s ÷ 86400
    jd = w * 7 + dow + GPS_EPOCH_JD +
            (s - dow * 86400) / 86400.0
    dt = julian2datetime(jd)
    dt + diff2UTC(dt, tstGPS)
end


# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

function _add_production_piecewise_linear_eqs!(
    model::JuMP.Model,
    g::Unit,
    formulation_prod_vars::Gar1962.ProdVars,
    formulation_pwl_costs::KnuOstWat2018.PwlCosts,
    formulation_status_vars::Gar1962.StatusVars,
)::Nothing
    eq_prod_above_def = _init(model, :eq_prod_above_def)
    eq_segprod_limit_a = _init(model, :eq_segprod_limit_a)
    eq_segprod_limit_b = _init(model, :eq_segprod_limit_b)
    eq_segprod_limit_c = _init(model, :eq_segprod_limit_c)
    segprod = model[:segprod]
    gn = g.name
    K = length(g.cost_segments)
    T = model[:instance].time

    # Gar1962.ProdVars
    prod_above = model[:prod_above]

    # Gar1962.StatusVars
    is_on = model[:is_on]
    switch_on = model[:switch_on]
    switch_off = model[:switch_off]

    for t in 1:T
        for k in 1:K
            # Pbar^{k-1)
            Pbar0 =
                g.min_power[t] +
                (k > 1 ? sum(g.cost_segments[ell].mw[t] for ell in 1:k-1) : 0.0)
            # Pbar^k
            Pbar1 = g.cost_segments[k].mw[t] + Pbar0

            Cv = 0.0
            SU = g.startup_limit   # startup rate
            if Pbar1 <= SU
                Cv = 0.0
            elseif Pbar0 < SU # && Pbar1 > SU
                Cv = Pbar1 - SU
            else # Pbar0 >= SU
                # this will imply that we cannot produce along this segment if
                # switch_on = 1
                Cv = g.cost_segments[k].mw[t]
            end
            Cw = 0.0
            SD = g.shutdown_limit  # shutdown rate
            if Pbar1 <= SD
                Cw = 0.0
            elseif Pbar0 < SD # && Pbar1 > SD
                Cw = Pbar1 - SD
            else # Pbar0 >= SD
                Cw = g.cost_segments[k].mw[t]
            end

            if g.min_uptime > 1
                # Equation (46) in Kneuven et al. (2020)
                eq_segprod_limit_a[gn, t, k] = @constraint(
                    model,
                    segprod[gn, t, k] <=
                    g.cost_segments[k].mw[t] * is_on[gn, t] -
                    Cv * switch_on[gn, t] -
                    (t < T ? Cw * switch_off[gn, t+1] : 0.0)
                )
            else
                # Equation (47a)/(48a) in Kneuven et al. (2020)
                eq_segprod_limit_b[gn, t, k] = @constraint(
                    model,
                    segprod[gn, t, k] <=
                    g.cost_segments[k].mw[t] * is_on[gn, t] -
                    Cv * switch_on[gn, t] -
                    (t < T ? max(0, Cv - Cw) * switch_off[gn, t+1] : 0.0)
                )

                # Equation (47b)/(48b) in Kneuven et al. (2020)
                eq_segprod_limit_c[gn, t, k] = @constraint(
                    model,
                    segprod[gn, t, k] <=
                    g.cost_segments[k].mw[t] * is_on[gn, t] -
                    max(0, Cw - Cv) * switch_on[gn, t] -
                    (t < T ? Cw * switch_off[gn, t+1] : 0.0)
                )
            end

            # Definition of production
            # Equation (43) in Kneuven et al. (2020)
            eq_prod_above_def[gn, t] = @constraint(
                model,
                prod_above[gn, t] == sum(segprod[gn, t, k] for k in 1:K)
            )

            # Objective function
            # Equation (44) in Kneuven et al. (2020)
            add_to_expression!(
                model[:obj],
                segprod[gn, t, k],
                g.cost_segments[k].cost[t],
            )

            # Also add an explicit upper bound on segprod to make the solver's
            # work a bit easier
            set_upper_bound(segprod[gn, t, k], g.cost_segments[k].mw[t])
        end
    end
end


using MomentDistances
using Statistics: mean
using Test

@testset "distance checks" begin
    s1 = rand() * 2
    s2 = rand() * 2

    A1 = rand(4, 4)
    A2 = rand(4, 4)

    for (ms, d) in ((AbsoluteRelative(), abs(s1 - s2)),
                    (AbsoluteRelative(; relative_adjustment = Inf),
                     abs(s1 - s2) / max(abs(s1), abs(s2))),
                    (AbsoluteRelative(; relative_adjustment = 0.3),
                     abs(s1 - s2) / max(1, 0.3 * max(abs(s1), abs(s2)))))
        @test iszero(@inferred(distance(ms, s1, s1)))
        ds = @inferred(distance(ms, s1, s2))
        @test ds ≈ d

        mA = ElementwiseMean(ms)
        @test iszero(@inferred(distance(mA, A1, A1)))
        dA = @inferred distance(mA, A1, A2)
        @test dA ≈ mean(distance.(Ref(ms), A1, A2))
        @test_throws DimensionMismatch distance(mA, A1, ones(3, 7))

        w = rand()
        mN = NamedSum((s = ms, A = Weighted(mA, w)))
        nt = (s = s1, A = A1)
        @test iszero(@inferred(distance(mN, nt, nt)))
        @test @inferred(distance(mN, nt, (s = s2, A = A2))) ≈
            distance(ms, s1, s2) + distance(mA, A1, A2) * w
    end
end

@testset "inference checks" begin
    @test @inferred(distance(AbsoluteRelative(), 1.0, 1.0)) == 0
    @test @inferred(distance(Weighted(AbsoluteRelative(), 1.0), 1.0, 1.0)) == 0
    @test @inferred(distance(ElementwiseMean(AbsoluteRelative()), ones(3), ones(3))) == 0
    metric = NamedSum((a = AbsoluteRelative(), b = Weighted(AbsoluteRelative(), 2.0),
                       c = AbsoluteRelative(), d = AbsoluteRelative()))
    nt = (a = 1, b = 2, c = 3.0, d = 4.0)
    @test @inferred(distance(metric, nt, nt)) == 0
end

@testset "constructor checks" begin
    @test_throws ArgumentError Weighted(AbsoluteRelative(), -9)
end

@testset "text summaries" begin
    s1 = 1
    s2 = 2
    ms = AbsoluteRelative()
    A1 = ones(2, 2)
    A2 = reshape(0:3, 2, 2)
    mA = ElementwiseMean(AbsoluteRelative())

    @test MomentDistances.summary(ms, s1, s2) == "‹1.0 ↔ 2.0: 1.0›"
    @test MomentDistances.summary(mA, A1, A2) ==
        """
        elementwise mean distance: 1.0
          [1,1]  ‹1.0 ↔ 0.0: 1.0›
          [2,1]  ‹1.0 ↔ 1.0: 0.0›
          [1,2]  ‹1.0 ↔ 2.0: 1.0›
          [2,2]  ‹1.0 ↔ 3.0: 2.0›"""
    @test MomentDistances.summary(Weighted(mA, 0.7), A1, A2) ==
        """
        weighted: 0.7
          elementwise mean distance: 1.0
            [1,1]  ‹1.0 ↔ 0.0: 1.0›
            [2,1]  ‹1.0 ↔ 1.0: 0.0›
            [1,2]  ‹1.0 ↔ 2.0: 1.0›
            [2,2]  ‹1.0 ↔ 3.0: 2.0›"""
    @test MomentDistances.summary(NamedSum((s = ms, A = Weighted(mA, 0.7))),
                                  (s = s1, A = A1), (s = s2, A = A2)) ==
        """
        total: 1.7
          from s:
            ‹1.0 ↔ 2.0: 1.0›
          from A:
            weighted: 0.7
              elementwise mean distance: 1.0
                [1,1]  ‹1.0 ↔ 0.0: 1.0›
                [2,1]  ‹1.0 ↔ 1.0: 0.0›
                [1,2]  ‹1.0 ↔ 2.0: 1.0›
                [2,2]  ‹1.0 ↔ 3.0: 2.0›"""

    @test sprint(summarize, ms, s1, s2) == MomentDistances.summary(ms, s1, s2)
end


# CGT97
# Corless, R. M.; Gianni, P. M. & Trager, B. M.
# A reordered Schur factorization method for zero-dimensional polynomial systems with multiple roots
# Proceedings of the 1997 international symposium on Symbolic and algebraic computation, 1997, 133-140

# MD95
# Manocha, D. & Demmel, J. Algorithms for intersecting parametric and algebraic curves II: multiple intersections
# Graphical Models and Image Processing, Elsevier, 1995, 57, 81-100

using LinearAlgebra # for I
@testset "Section 4.1 MD95" begin
    η = 1e-10
    A = [zeros(10, 1) Matrix(I, 10, 10); zeros(1, 10) 0.5]
    A[10, 11] = 0
    A[10, 1] = η
    @test sort.(SemialgebraicSets.clusterordschur(A, sqrt(eps(Float64)))[2]) == [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11]]
end

@testset "Example 4.1 MD95" begin
    A = [  0  0  0  1  0  0;
           0  0  0  0  1  0;
           0  0  0  0  0  1;
          -1  0  1 -1  0  0;
          -1  0  1 -2 -1  0;
          -1  0  1 -2 -2 -1]
    @test sort.(SemialgebraicSets.clusterordschur(A, sqrt(eps(Float64)))[2]) == [[2], [1, 5, 6]]
end

function testelements(X, Y; atol=Base.rtoldefault(Float64), kwargs...)
    @test length(X) == length(Y)
    for y in Y
        @test any(x -> isapprox(x, y; atol=atol, kwargs...), X)
    end
end
function testelementstypes(X, Y; kwargs...)
    testelements(X, Y; kwargs...)
    for T in [Rational{Int}, Float64]
        if X isa FixedVariablesSet
            U = T
        else
            U = float(T)
        end
        V = MultivariatePolynomials.changecoefficienttype(X, T)
        testelements(V, Y; kwargs...)
        @test eltype(V) == Vector{U}
        @test collect(V) isa Vector{Vector{U}}
    end
end

# We use a fixed RNG in the tests to decrease nondeterminism. There is still nondeterminism in LAPACK though
using Random
solver = ReorderedSchurMultiplicationMatricesSolver(sqrt(eps(Float64)), MersenneTwister(0))

@testset "Zero-dimensional ideal" begin
    Mod.@polyvar x y z
    V = @set x == y
    @test !iszerodimensional(V)
    @static if VERSION >= v"0.7-"
        @test_throws ErrorException iterate(V)
    else
        @test_throws ErrorException start(V)
    end
    @test_throws ErrorException length(V)
    V = @set 4x^2 == -5x && 3x^3 == 0 solver
    @test V.solver.solver === solver
    @test iszerodimensional(V)
    testelementstypes(V, [[0]])
    V = @set y == x^2 && z == x^3 solver
    @test !iszerodimensional(V)
    V = @set x^3 == 2x*y &&  x^2*y == 2y^2 + x solver
    @test iszerodimensional(V)
    testelementstypes(V, [[0, 0]])
    V = @set x == 1 solver
    @test iszerodimensional(V)
    testelementstypes(V, [[1]])
    V = @set x == 1 && y == 2 solver
    @test iszerodimensional(V)
    testelementstypes(V, [[1, 2]])
    V = @set x == 4 && y^2 == x solver
    @test iszerodimensional(V)
    testelementstypes(V, [[4, 2], [4, -2]])
    V = @set x^2 + x == 6 && y == x+1 solver
    @test iszerodimensional(V)
    testelements(V, [[2, 3], [-3, -2]])
    V = @set x^2 + x == 6 && y^2 == x solver
    @test iszerodimensional(V)
    testelements(V, [[2, √2], [2, -√2]])
end

@testset "Projective zero-dimensional ideal" begin
    Mod.@polyvar x y z
    V = projectivealgebraicset([x - y], solver)
    @test iszerodimensional(V)
    testelementstypes(V, [[1, 1]])
    V = @set x + y == z solver
    V.projective = true
    @test !iszerodimensional(V)
    V = @set y == 2x solver
    V.projective = true
    @test iszerodimensional(V)
    testelementstypes(V, [[1, 2]])
    V = @set x + y == y solver
    V.projective = true
    @test iszerodimensional(V)
    testelementstypes(V, [[0, 1]])
    V = projectivealgebraicset([x + y - x])
    @test iszerodimensional(V)
    testelementstypes(V, [[1, 0]])
end

@testset "Example 5.1 of CGT97" begin
    ɛ = 1e-4
    Iɛ = [1 - ɛ 0
          0     1 + ɛ]
    J = [0 1
         1 0]
    Z = zeros(2, 2)
    A = [Iɛ Z
         Z  J]
    B = [J Z
         Z Iɛ]
    α = 0.219
    testelements(SemialgebraicSets._solvemultiplicationmatrices([A, B], [α, 1-α], ReorderedSchurMultiplicationMatricesSolver{Float64}()), [[1.0, -1.0], [1.0, 1.0], [-1.0, 1.0]]; rtol=1e-7)
end

@testset "Example 4.3 of MD95" begin
    Mod.@polyvar x y
    V = @set x^2 + 4y^4 == 4y^2 && 3x^4 + y^2 == 5x^3 solver
    # This test is tricky because in the schur decomposition, the 4 last eigenvalues are e.g. 3.4e-7, -1.7e-7+3e-7im, -1.7e-7-3e-7im, -6e-16
    # the second and third do not seem that close but when the three first are averaged it is very close to zero.
    @test iszerodimensional(V)
    testelementstypes(V, [[0.66209555, 0.935259169], [0.66209555, -0.935259169], [0.0516329456, -0.025825086], [0.0516329456, 0.025825086], [0, 0]])
end

@testset "Example 5.2 of CGT97" begin
    Mod.@polyvar x y z
    V = @set x^2 + y^2 == 1 && x^3 + (2 + z)*x*y + y^3 == 1 && z^2 == 2 solver
    @test iszerodimensional(V)
    iszd, B = monomialbasis(V.I)
    @test iszd
    @test B == [y^3*z, x*y*z, y^3, y^2*z, x*y, x*z, y^2, y*z, x, y, z, 1]
    testelements(V, [[0, 1, √2], [0, 1, -√2], [1, 0, -√2], [1, 0, √2], [-√2/2, -√2/2, √2], [√2/2, √2/2, -√2]])
end

#@testset "Example 4.4 of MD95 and 5.3 of CGT97" begin
#    Mod.@polyvar x y
#    F = -2-7x+14x^3-7x^5+x^7 + (7-42x^2+35x^4-7x^6)*y + (16+42x-70x^3+21x^5)*y^2 + (-14+70x^2-35x^4)*y^3 + (-20-35x+35x^3)*y^4 + (7-21x^2)*y^5 + (8+7x)*y^6 - y^7 - y^8
#    G = differentiate(F, y)
#    @show G
#    V = @set F == 0 && G == 0 solver
#    @test iszerodimensional(V)
#    sols = [[-3.91298, -1.95065], [-3.23984, -1.56367], [-3.21615, -1.41421], [-3.09474, -1.84776], [3.6497, 1.84776], [3.65578, 1.80399], [2.2928, 1.84776], [2.66119, 1.41421], [2.68379, 1.23369], [2.5673, 0.765367], [0.600779, 1.84776], [0.969172, 1.41421], [-1.85925, -1.41422], [-1.40272, -1.84775], [-2.01312, -0.812102], [-2.01235, -0.765367], [1.26106, 0.265359], [-0.481613, 0.765367], [1.03657, -0.765367], [-0.483778, 0.630692], [-0.0458213, -1.84776], [-1.80194, 0.]]
#    @show length(sols)
#    #testelements(V, sols)
#    @show length(V)
#    for v in collect(V)
#        @show v
#        @show F(variables(F) => v)
#        @show G(variables(G) => v)
#    end
#end

@testset "Complex not yet implemented" begin
    Mod.@polyvar x
    V = @set x^2 == im
    @test_throws MethodError collect(V)
end


using OrdinaryDiffEq, Test, Random
Random.seed!(123)

using OrdinaryDiffEq, DiffEqOperators, LinearAlgebra
A = 0.01*rand(3, 3)
rn = (du, u, p, t) -> begin
    mul!(du, A, u)
end
u0 = rand(3)
prob = ODEProblem(ODEFunction(rn, jac_prototype=JacVecOperator{Float64}(rn, u0; autodiff=false)), u0, (0, 10.))
@test_nowarn sol = solve(prob, TRBDF2(autodiff=false));
@test_nowarn sol = solve(prob, TRBDF2(autodiff=false, linsolve=LinSolveGMRES()));
@test_nowarn sol = solve(prob, TRBDF2(autodiff=false, linsolve=LinSolveGMRES(), smooth_est=false));
@test_nowarn sol = solve(prob, TRBDF2(autodiff=false, linsolve=LinSolveGMRES(Pl=lu(A)), smooth_est=false));
@test_nowarn sol = solve(prob, TRBDF2(autodiff=false, linsolve=LinSolveGMRES(Pr=lu(A)), smooth_est=false));
@test_nowarn sol = solve(prob, TRBDF2(autodiff=false, linsolve=LinSolveGMRES(Pl=lu(A), Pr=lu(A)), smooth_est=false));


function perform_stft(x,w,q,n)
    """
        perform_stft - compute a local Fourier transform

        Forward transform:
          MF = perform_stft(M,w,q, options);
        Backward transform:
          M  = perform_stft(MF,w,q, options);

          w is the width of the window used to perform local computation.
          q is the spacing betwen each window.

          MF(:,i) contains the spectrum around point (i-1)*q

          A typical use, for an redundancy of 2 could be w=2*q+1

          options.bound can be either 'per' or 'sym'

          No multichannel, no Gabor stft

          options.normalization can be set to
              'tightframe': tight frame transform, with energy conservation.
              'unit': unit norm basis vectors, usefull to do thresholding

          Copyright (c) 2008 Gabriel Peyre
    """
    if ndims(x) == 1
        dir = 1
    else
        dir = -1
    end

    # perform sampling
    X = Array(1:q:n+2)

    p = length(X)
    eta = 1

    if w%2 == 1
        w = ceil((w-1)/2)*2+1
        w1 = Base.div(w-1,2)
        dX = Array(-w1:w1)
    else
        dX = Array((-Base.div(w,2)+1):Base.div(w,2))
    end

    X1 = repeat(X,outer=(1,w)) + repeat(dX, outer=(1,p))'
    #periodic boundary conditions
    X1 = ((X1-1).%n)+1;

    I = X1'
    I[I.<=0]+=n

    # build a sin weight function
    W = .5 *(1 - cos(2*pi*Array(0:(w-1))./(w-1)))

    #renormalize the windows
    weight = zeros(n)

    for i in 1:p
        weight[I[:,i]] = weight[I[:,i]] + W.^2
    end

    weight = sqrt(weight)
    Weight = repeat(W, outer=(1,p))

    for i in 1:p
        Weight[:,i] = Weight[:,i]./weight[I[:,i]]
    end

    #compute the transform
    if dir == 1
        y = zeros(eta*w,p)
        if w%2 == 1
            m = Base.div(eta*w+1,2)
            w1 = Base.div((w-1),2)
            sel = Array(m-w1:m+w1) - 1
        else
            m = Base.div((eta*w),2)+1
            w1 = Base.div(w,2)
            sel = Array(m-w1:m+w1-1) - 1
        sel[sel.<=0] += eta*w
        y[sel,:] = x[I].*Weight

        #perform the transform
        y = my_transform(y,+1)
        end
    else
        x = my_transform(x,-1)
        x = real.(x.*Weight)
        y = zeros(n)
        for i in 1:p
            y[I[:,i]] = y[I[:,i]] + x[:,i]
        end
    end
    return y
end
########################################################################
function my_transform(x,dir)

    # my_transform - perform either FFT with energy conservation.
    # Works on array of size (w,w,a,b) on the 2 first dimensions.
    w = size(x)[1]
    if dir == 1
        y = fft(x,1)/sqrt(w)
    else
        y = ifft(x*sqrt(w),1)
    end
    return y
end


@doc """
 COST 231- Cost-Waldrosch-Ikegami Model
 freq: signal frequency
 hb: base station's height
 hm: mobile's height
 ws: average width of the street in meters
 bs: average setback of buildings in meters
 hr: mean height of houses in meters
 areaKind: area type (1-rural, 2-suburban e 3-urban).
 cityKind : cyte type(1-small, 2-medium e 3-large).
"""
type Cost231Model<:Model
  freq::Float64
  txH::Float64
  rxH::Float64
  ws::Float64
  bs::Float64
  hr::Float64
  cityKind::Int
  checkFreqRange::Bool
  Cost231Model() = new(800,50.0,1.5,20,10,35,CityKind.Medium,true)
end

"Calculate Path loss for distance dist (km) with COST 231 Model"
function pathloss_op(model::Cost231Model, dist::AbstractFloat)
  #150 a 2000 MHz
  hm::Float64 = model.rxH
  hb::Float64 = model.txH
  hr::Float64 = model.hr
  d::Float64 = dist
  f::Float64 = model.freq
  ws::Float64 = model.ws
  bs::Float64 = model.bs
  # Checking the frequency range
  if (model.checkFreqRange)
    if (f < 150 || f > 2000)
     error("The frequency range for Ecc-33 Model is 150MHz-2000Mhz")
    end
  end

  deltaH::Float64 = hm/hb #relaction between heighths
  Lbsh::Float64 = 18*log(1+deltaH) # Loss due to difference of heights
  Ka::Float64=54.0  #Coefficient of proximity Buildings
  Kd::Float64=18.0  #Coeficiente of proximidade Edifica??es
  Kf::Float64=4.0  #Coeficient of environment(urban or not)

  #Coeficient's calculate
  if (hr > hb)
    Lbsh = 0
  end
  if (hb <= hr && d >= 0.5)
    ka=Ka-0.8*deltaH
  elseif (hb<=hr && d<0.5)
    ka= Ka-0.8*deltaH*(d/0.5)
  end
  if (hb < hr)
    Kd= Kd-15*(hb-hr)/(hr-hm)
  end
  if (model.cityKind==CityKind.Small)
    Kf=Kf+0.7*(f/925-1)
  else
    Kf=Kf+1.5*(f/925-1)
  end
  #path loss's calculate
  Lo = 32.4+20*log10(d)+20*log10(f)                     #free space path loss
  Lrts = 8.2+10*log(ws) + 10*log10(f) + 10*log(deltaH) # roofTop loss
  Lmsd =Lbsh +Ka +Kd*log10(d)+Kf*log(f)-9*log10(bs)    #Multpath loss
  #final path loss
  PL = Lo + Lrts + Lmsd;
  return PL
end