using StochasticDiffEq, Plots, DiffEqDevTools, DiffEqProblemLibrary
using ParameterizedFunctions, DiffEqMonteCarlo, Base.Test, OrdinaryDiffEq
using Plots; pyplot()
using BenchmarkTools
names = ["Euler-Maruyama","RKMil","SRIW1","SRIW2","SOSRI","SOSRI2"]

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

prob = prob_sde_2Dlinear

reltols = 1.0./10.0.^(1:5)
abstols = reltols

setups = [Dict(:alg=>EM(),:dts=>1.0./5.0.^((1:length(reltols)) + 2))
          Dict(:alg=>RKMil(),:dts=>1.0./5.0.^((1:length(reltols)) + 2))
          Dict(:alg=>SRIW1())
          Dict(:alg=>SRIW2())
          Dict(:alg=>SOSRI())
          Dict(:alg=>SOSRI2())
          ]

wp = WorkPrecisionSet(prob,abstols,reltols,setups;
                      numruns=1000,names=names,error_estimate=:l2)

p1 = plot(wp,legend=false,title="Linear Strong Error",
          xtickfont = font(16, "Latin Modern Roman"),
          titlefont = font(20, "Latin Modern Roman"),
          ytickfont = font(16, "Latin Modern Roman"),
          guidefont = font(18, "Latin Modern Roman"))

wp2= WorkPrecisionSet(prob,abstols,reltols,setups;numruns=1000,
                      names=names,error_estimate=:weak_final)

sample_size = Int[10;1e2;1e3;1e4;1e5]
test_dt = 1e-2
appxsol_setup = Dict(:alg=>SOSRA(),:abstol=>1e-4,:reltol=>1e-4)
se1 = get_sample_errors(prob,test_dt,appxsol_setup=appxsol_setup,
          parallel_type = :threads, numruns=sample_size,
          std_estimation_runs = Int(1e3))

p2 = plot(wp2;legend=false,title="Linear Weak Error",
          xtickfont = font(16, "Latin Modern Roman"),
          titlefont = font(20, "Latin Modern Roman"),
          ytickfont = font(16, "Latin Modern Roman"),
          guidefont = font(18, "Latin Modern Roman"),
          plot_sample_error = false)
times = [wp2[i].times for i in 1:length(wp)]
times = [minimum(minimum(t) for t in times),maximum(maximum(t) for t in times)]
plot!(p2,[se1[2];se1[2]],times,color=:red,linestyle=:dash,
      label="Sample Error: 100",lw=3)
plot!(p2,[se1[end];se1[end]],times,color=:orange,
      linestyle=:dash,label="Sample Error: 10000",lw=3)

### Lotka

f = @ode_def LotkaVolterraTest begin
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d

function g(du,u,p,t)
  @. du = 0.01u
end
u0 = [1.0;1.0]
tspan = (0.0,10.0)
p = (1.5,1.0,3.0,1.0)
prob = SDEProblem(f,g,u0,tspan,p);

reltols = 1.0./4.0.^(2:4)
abstols = reltols#[0.0 for i in eachindex(reltols)]
setups = [Dict(:alg=>EM(),:dts=>1.0./12.0.^((1:length(reltols)) + 1.5))
          Dict(:alg=>RKMil(),:dts=>1.0./12.0.^((1:length(reltols)) + 1.5))
          Dict(:alg=>SRIW1())
          Dict(:alg=>SRIW2())
          Dict(:alg=>SOSRI())
          Dict(:alg=>SOSRI2())
          ]
test_dt = 1/10^2
appxsol_setup = Dict(:alg=>SRIW1(),:abstol=>1e-4,:reltol=>1e-4)
wp3 = WorkPrecisionSet(prob,abstols,reltols,setups,test_dt;
                                     names = names,
                                     verbose=false,save_everystep=false,
                                     parallel_type = :threads,
                                     appxsol_setup = appxsol_setup,
                                     numruns_error=100,error_estimate=:final)
plot(wp3)
p3 = plot(wp3,legend=false,title="Lotka-Volterra Strong Error",
          xtickfont = font(16, "Latin Modern Roman"),
          titlefont = font(20, "Latin Modern Roman"),
          ytickfont = font(16, "Latin Modern Roman"),
          guidefont = font(18, "Latin Modern Roman"))

wp4 = WorkPrecisionSet(prob,abstols,reltols,setups,test_dt;
                                     names = names,
                                     verbose=false,save_everystep=false,
                                     parallel_type = :threads,
                                     appxsol_setup = appxsol_setup,
                                     numruns_error=100,error_estimate=:weak_final)
plot(wp4)
sample_size = Int[10;1e2;1e3;1e4;1e5]
test_dt = 1e-2
appxsol_setup = Dict(:alg=>SOSRA(),:abstol=>1e-4,:reltol=>1e-4)
se2 = get_sample_errors(prob,test_dt,appxsol_setup=appxsol_setup,
          parallel_type = :threads, numruns=sample_size,
          std_estimation_runs = Int(1e3))

p4 = plot(wp4;legend=:topleft,title="Lotka-Volterra Weak Error",
          plot_sample_error = false,
          xtickfont = font(16, "Latin Modern Roman"),
          titlefont = font(20, "Latin Modern Roman"),
          ytickfont = font(16, "Latin Modern Roman"),
          guidefont = font(18, "Latin Modern Roman"),
          legendfont = font(14, "Latin Modern Roman"))
times = [wp4[i].times for i in 1:length(wp4)]
times = [minimum(minimum(t) for t in times),maximum(maximum(t) for t in times)]
plot!(p4,[se2[2];se2[2]],times,color=:red,linestyle=:dash,
      label="Sample Error: 100",lw=3)
plot!(p4,[se2[end];se2[end]],times,color=:orange,
      linestyle=:dash,label="Sample Error: 10000",lw=3)

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

### Final Plot

plot(p1,p2,p3,p4,layout=(2,2),size=(1200,800))

savefig("SRI_efficiency.png")
savefig("SRI_efficiency.pdf")


################################################################################
#  Copyright 2020, Tom Van Acker                                               #
################################################################################
# MultiStateSystems.jl                                                         #
# A Julia package to solve multi-state system models.                          #
# See http://github.com/timmyfaraday/MultiStateSystems.jl                      #
################################################################################
"""
Example considering a multi-state Markov model for a coal fired power generating
unit taken from:

> A Multi-State Markov Model for a Short-Term Reliability Analysis of a Power
  Generating Unit by A. Lisnianski, D. Elmakias, and H. Ben Haim (2012)

A unit's available generating capacity can take any real value in [0,gⁿᵒᵐ].
This is approximated by a discrete state continuous time stochastic process
Gₐ(t) as follows:
- Two states to describe the limits of the generating capacity:
    - State 1, where g₁ = 0
    - State N, where gₙ = gⁿᵒᵐ
- N-2 additional states seperated with an 'averaged' output gᵢ within an
  interval Δg = gⁿᵒᵐ/(N-2).

If the random times between transitions are ignored and only the transition time
instances are of interest, the stochastic process Gₐ(t) reduces to a Markov
model Gₐₘ(t), with approximated transition intensities âᵢⱼ from state i to
state j≠i.

The case study in the paper considers a four-state model to represent a coal
fired power generating unit, with the input given in Table 1[^1].

Table 1: States' output and transition intensities.
| g₁  = 0 MW        | g₂  = 247 MW      | g₃  = 482 MW      | g₄  = 576 MW      |
|-------------------|-------------------|-------------------|-------------------|
| â₁₂ ≈ 0.08000 1/h | â₂₁ ≈ 0.02912 1/h | â₃₁ ≈ 0.00000 1/h | â₄₁ ≈ 0.00015 1/h |
| â₁₃ ≈ 0.01333 1/h | â₂₃ ≈ 0.32353 1/h | â₃₂ ≈ 0.02885 1/h | â₄₂ ≈ 0.00010 1/h |
| â₁₄ ≈ 0.00000 1/h | â₂₄ ≈ 0.02912 1/h | â₃₄ ≈ 0.35577 1/h | â₄₃ ≈ 0.00069 1/h |

[^1]: The data in the table is an approximation. Exact results are obtained by
using the fraction of kᵢⱼ over Tᵢ as given in the paper.
"""

# load pkgs
using Unitful
using MultiStateSystems

# initialize the state-transition diagram corresponding to the coal fired power
# generating unit.
stdᶜᶠ = STD()

# add the states to the std
add_states!(stdᶜᶠ, power = [0u"MW", 247u"MW", 482u"MW", 576u"MW"],
                   init  = [0.0, 0.0, 0.0, 1.0])

# add the transitions to the std
add_transitions!(stdᶜᶠ, states = [(1,2),(1,3)],
                        rate = [0.08000u"1/hr",0.01333u"1/hr"])
add_transitions!(stdᶜᶠ, states = [(2,1),(2,3),(2,4)],
                        rate = [0.02941u"1/hr",0.32353u"1/hr",0.02941u"1/hr"])
add_transitions!(stdᶜᶠ, states = [(3,2),(3,4)],
                        rate = [0.02885u"1/hr",0.35577u"1/hr"])
add_transitions!(stdᶜᶠ, states = [(4,1),(4,2),(4,3)],
                        rate = [0.00015u"1/hr",0.00010u"1/hr",0.00069u"1/hr"])

# return the std
return stdᶜᶠ


"""
    safe_div(x, y)

Safely divide `x` by `y`. If `y` is zero, return `x` directly.
"""
safe_div(x, y) = ifelse(iszero(y), x, x/y)

"""
    maximum_dims(dims)

Return the maximum value for each dimension. An array of dimensions `dims` is accepted.
The maximum of each dimension in the element is computed.
"""
maximum_dims(dims::AbstractArray{<:Integer}) = (maximum(dims), )
maximum_dims(dims::AbstractArray{NTuple{N, T}}) where {N,T} = ntuple(i -> maximum(x->x[i], dims), N)
maximum_dims(dims::AbstractArray{CartesianIndex{N}}) where {N} = ntuple(i -> maximum(x->x[i], dims), N)

function reverse_indices(idx::Array{T}) where T
    rev = Dict{T,Vector{CartesianIndex}}()
    for (ind, val) = pairs(idx)
        rev[val] = get(rev, val, CartesianIndex[])
        push!(rev[val], ind)
    end
    rev
end

function count_indices(idx::AbstractArray)
    counts = zero.(idx)
    for i in unique(idx)
        counts += sum(idx .== i) * (idx .== i)
    end
    return counts
end

function divide_by_counts!(xs, idx::AbstractArray, dims)
    colons = Base.ntuple(_->Colon(), dims)
    counts = count_indices(idx)
    for i in CartesianIndices(counts)
        view(xs, colons..., i) ./= counts[i]
    end
    return xs
end


"""
Package: Forecast

    nextodd(x)

Return the smallest odd integer greater than or equal to `x`.
"""
function nextodd(x::Real)::Integer
    cx = Integer(ceil(x))
    mod(cx,2)==0 ? cx+1 : cx
end

"""
Package: Forecast

    drop(M;r,c)

Drop rows and columns from a matrix.
"""
function drop(M::AbstractMatrix; r=[], c=[])
    s = size(M)
    dr = collect(1:s[1])
    dc = collect(1:s[2])
    splice!(dr,r)
    splice!(dc,c)
    M[dr,dc]
end

"""
Package: Forecast

    compact(x)

Standarize input by dropping empty dimensions and returning an Array. If a Number is passed
then a 0-dimensional array is returned. In the case of a DataFrame it removes all non Real columns except if there are columns  with Date type in which case keeps the first one found and places
it as the first column.
"""
compact(x::Any) = x
function compact(x::AbstractArray)
    x = Array(dropdims(x, dims = tuple(findall(size(x) .== 1)...)))
    #ndims(x) == 0 ? [x[1]] : x
    ndims(x) == 0 ? x[1] : x
end

"""
Package: Forecast

    expand(x)

Inverse of compact(x)
"""
expand(x::Any, dims::Tuple) = dims == () ? x : reshape([x], dims)
expand(x::AbstractArray, dims::Tuple) = size(x) == (1,) ? expand(x[1], dims) : reshape(x,dims)

"""
Package: Forecast

    insert_column(M, at, value = 0.0)

Insert a column with specific value at a given position, values are pushed to the right
"""
function insert_column(M::Matrix, at::Integer, value = 0.0)::Matrix
    nr,nc = size(M)
    !(1 <= at <= nc+1) && return(M)

    at == nc+1 && return hcat(M,repeat([value],nr))
    M = hcat(M[:,1:at], M[:,at:nc])
    M[:,at] .= value
    M
end

"""
Package: Forecast

    insert_row(M, at, value = 0.0)

Insert a row with specific value at a given position, values are pushed down
"""
function insert_row(M::Matrix, at::Integer, value = 0.0)::Matrix
    nr,nc = size(M)
    !(1 <= at <= nr+1) && return(M)

    at == nr+1 && return vcat(M,repeat([value],1,nc))
    M = vcat(M[1:at,:], M[at:nr,:])
    M[at,:] .= value
    M
end

"""
Package: Forecast

    insert_row(M, at, value = 0.0)

Insert a row and a column with specific value at a given cross position,
values are pushed right and down.
"""
function insert_cross(M::Matrix, at::Integer, value = 0.0)::Matrix
    insert_row(insert_column(M,at,value),at,value)
end


@testset "basis.jl" begin
    @testset "ZBasis" begin
        @testset "FullZBasis" begin
            @test_throws ArgumentError zbasis(-1)
            @test zbasis(5) isa FullZBasis
            @test SpinSymmetry._indices(zbasis(5)) == 1:2^5

            @test zbasis(5) == FullZBasis(5)
            @test zbasis(6) != FullZBasis(5)
            @test hash(zbasis(5)) == hash(FullZBasis(5))
            @test hash(zbasis(6)) != hash(FullZBasis(5))
        end

        @testset "ZBlockBasis" begin
            @test_throws ArgumentError zbasis(-1, 0)
            @test_throws ArgumentError zbasis(2,-1)
            @test_throws ArgumentError zbasis(2,3)

            @test zbasis(2,1) isa ZBlockBasis

            @test zbasis(5,2) == ZBlockBasis(5,2)
            @test zbasis(6,2) != ZBlockBasis(5,2)
            @test zbasis(6,3) != ZBlockBasis(6,2)
            @test hash(zbasis(5,2)) == hash(ZBlockBasis(5,2))
            @test hash(zbasis(6,2)) != hash(ZBlockBasis(5,2))
            @test hash(zbasis(6,3)) != hash(ZBlockBasis(6,2))

            # small correctness check
            @test SpinSymmetry._indices(zbasis(2,0)) == [1]
            @test SpinSymmetry._indices(zbasis(2,1)) == [2,3]
            @test SpinSymmetry._indices(zbasis(2,2)) == [4]

            digitsum(k) = sum(parse.(Int, [string(k-1; base=2)...]))
            for k in 0:10
                zblock = zbasis(10, k)
                @test all(digitsum.(SpinSymmetry._indices(zblock)) .== k)
            end
        end
    end

    @testset "symmetrized_basis" begin
        # different argument forms
        @test symmetrized_basis(6) == symmetrized_basis(zbasis(6))
        @test symmetrized_basis(6, 2) == symmetrized_basis(zbasis(6, 2))
        @test symmetrized_basis(6) isa SymmetrizedBasis
        @test symmetrized_basis(zbasis(6), Flip(6), 0) == symmetrized_basis(6, Flip(6), 0)
        @test symmetrized_basis(zbasis(6, 2), Flip(6), 0) == symmetrized_basis(6, 2, Flip(6), 0)

        # equality
        @test symmetrized_basis(6, Flip(6), 0, Shift(6), 1) == symmetrized_basis(6, Shift(6), 1, Flip(6), 0)
        @test symmetrized_basis(6, Flip(6), 0, Shift(6, 5), 1) == symmetrized_basis(6, Shift(6, -1), 1, Flip(6), 0)
        @test symmetrized_basis(6, Flip(6), 0, Shift(6), 0) != symmetrized_basis(6, Shift(6), 1, Flip(6), 0)
        @test symmetrized_basis(6, Flip(6), 1, Shift(6), 0) != symmetrized_basis(6, Shift(6), 1, Flip(6), 0)

        let b1 = symmetrized_basis(6, Flip(6), 0, Shift(6), 1),
            b2 = symmetrized_basis(6, Shift(6), 1, Flip(6), 0)
            @test SpinSymmetry._phase_factors(1:2^6, b1.symmetries, b1.sectors)  ==
                SpinSymmetry._phase_factors(1:2^6, b2.symmetries, b2.sectors)
        end
    end

    @testset "basissize" begin
        let basis = zbasis(10)
            @test basissize(basis) == length(SpinSymmetry._indices(basis))
        end

        for k in 0:10
            let basis = zbasis(10, k)
                @test basissize(basis) == length(SpinSymmetry._indices(basis))
            end
        end

        @test basissize(symmetrized_basis(10, Flip(10), 0)) == 2^9
        @test basissize(symmetrized_basis(10, 5,  Flip(10), 0)) == binomial(10, 5)/2
        @test basissize(symmetrized_basis(10, 4,  Flip(10), 0)) == binomial(10, 4)
    end

    @testset "symmetrize stuff" begin
        using LinearAlgebra, Random, SparseArrays
        Random.seed!(5)

        @testset "return types" begin
            N = 11

            real_basis1 = zbasis(N, div(N-1, 2))
            real_basis2 = symmetrized_basis(N, Shift(N), 0)
            complex_basis = symmetrized_basis(N, Shift(N), 1)
            σz = spdiagm([1,-1])
            make_op(op, N) = sum(kron(kron(I(2^(N-i)), op), I(2^(i-1))) for i in 1:N)
            op = make_op(σz, N)

            for (op, should_be_sparse) in ((op, true), (Matrix(op), false))
                let symm = symmetrize_operator(op, real_basis1)
                    @test issparse(symm) == should_be_sparse
                    @test eltype(symm) <: Real
                end
                let symm = symmetrize_operator(op, real_basis2)
                    @test issparse(symm) == should_be_sparse
                    @test eltype(symm) <: Real
                end
                let symm = symmetrize_operator(op, complex_basis)
                    @test issparse(symm) == should_be_sparse
                    @test eltype(symm) <: Complex
                end
            end
        end

        N = 5

        state = normalize!(rand(2^N))
        operator = rand(2^N, 2^N)
        base = symmetrized_basis(N, Flip(N), 0)
        mid = 2^(N-1)
        front = 1:mid
        back = 2^N:-1:mid+1

        @test symmetrize_state(state, symmetrized_basis(N, 0)) ≈ [state[1]]
        @test symmetrize_state(state, zbasis(N, N)) ≈ [state[end]]
        @test symmetrize_operator(operator, symmetrized_basis(N, 0)) ≈ [operator[1,1]]
        @test symmetrize_operator(operator, zbasis(N, N)) ≈ [operator[end, end]]

        symm_state = symmetrize_state(state, base)
        @test symm_state ≈ symmetrize_state(state, N, Flip(N), 0)
        @test symm_state ≈ 1/√2 .* (state[front] .+ state[back])

        asymm_state = symmetrize_state(state, N, Flip(N), 1)
        @test asymm_state ≈ 1/√2 .* (state[front] .- state[back])

        @test_throws ArgumentError symmetrize_state(zeros(2^N+1), base)

        symm_operator = symmetrize_operator(operator, base)
        @test symm_operator ≈ symmetrize_operator(operator, N, Flip(N), 0)
        @test symm_operator ≈ 1/2 .* (operator[front, front] .+ operator[back, back] .+ operator[front, back] .+ operator[back, front])

        asymm_operator = symmetrize_operator(operator, N, Flip(N), 1)
        @test asymm_operator ≈ 1/2 .* (operator[front, front] .+ operator[back, back] .- operator[front, back] .- operator[back, front])

        @test_throws ArgumentError symmetrize_operator(zeros(2^N+1, 2^N+1), base)
        @test_throws ArgumentError symmetrize_operator(zeros(2^(N-1), 2^(N+1)), base)
    end
end


############################
## To be refined....
##
############################
function mdtest(mf::MomentFunction,
                div::Divergence,
                θ₀::Vector,
                lb::Vector, ub::Vector;
                solver=IpoptSolver())

    model = MathProgSolverInterface.model(solver)

    m = mf.nmom
    n = mf.nobs
    k = mf.npar

    u₀ = [ones(n), θ₀]

    gele = int((n+k)*(m+1)-k)
    hele = int(n*k + n + (k+1)*k/2)

    # lb = [-2., -2]
    # ub = [2, 2.]
    g_L = [zeros(m), n];
    g_U = [zeros(m), n];

    u_L = [zeros(n),  lb];
    u_U = [ones(n)*n, ub];
    # l = [1,1,1,1]
    # u = [5,5,5,5]
    # lb = [25, 40]
    # ub = [Inf, 40]

    MdProb = MinimumDivergenceProblem(mf, div, n, m, k, gele, hele, Array(Float64, n), Array(Float64, m+1))

    MathProgSolverInterface.loadnonlinearproblem!(model, n+k, m+1, u_L, u_U, g_L, g_U, :Min, MdProb)

    MathProgSolverInterface.setwarmstart!(model, u₀)

    MathProgSolverInterface.optimize!(model)
    stat = MathProgSolverInterface.status(model)

    # @test stat == :Optimal
    uᵒ = MathProgSolverInterface.getsolution(model)
    Qᵒ = MathProgSolverInterface.getobjval(model)
    (model, uᵒ, Qᵒ)

end


struct SLFN{T <: Number}
    n_features::Int
    n_outputs::Int
    hidden_layer::ImmutableHiddenLayer{T}
    output_weights::Matrix{T}
end

function predict(model::T₁,
                 samples::T₂) where {T₁ <: SLFN,
                                     T₂ <: AbstractMatrix}
    X = samples
    H = project(model.hidden_layer, X)
    Β = model.output_weights
    Y = Β * H
    typeof(Y) <: AbstractMatrix && first(size(Y)) == 1 ? vec(Y) : Y
end

function predict(model::T₁,
                 sample::T₂) where {T₁ <: SLFN,
                                    T₂ <: AbstractVector}
    predict(model, reshape(sample, 1, :))
end


function ueberpruefenaechsterPunkt(Parameter1,Punkt,blick1,blick2,b1,M,A3,b3,nx1,nx2,nx3)

a1 = b1-M[:,1]*Parameter1;

if norm(a1-transpose(Punkt)) <= 0.01
    println("Ihr habt den nächsten Punkt korrekt berechnet! Ihr könnt blick1 und blick2 so verändern, dass man sieht, dass der Punkt in der Ebene liegt.")

    U2,S2,V2 = svd(A3)
    u = U2[:,1];
    v = U2[:,2];
    s = -20*u-20*v;
    a = b3[1];
    b = b3[2];
    c = b3[3];
    d = Punkt[1];
    e = Punkt[2];
    f = Punkt[3];
    ## Berechnung Winkel für blick1
    #x2 = -nx1/nx2;
    #Vec1 = [1; x2; 0];
    #Vec2 = [1; 0; 0];
    #alpha = acosd(transpose(Vec1)*Vec2/(norm(Vec1)*norm(Vec2)));
    #x3 = -nx1/nx3;
    #Vec3 = [1; 0; x3];
    #Vec4 = [0; 0; 1];
    #beta = acosd(transpose(Vec3)*Vec4/(norm(Vec3)*norm(Vec4)));

    plt = surface([(s[1],s[2],s[3]),(30*u[1],30*u[2],30*u[3]),(30*v[1],30*v[2],30*v[3])],alpha=.1, fillalpha=.1)
    plot3d!([a],[b],[c],marker=2,xlim=(-14,14),ylim=(-14,14),zlim=(-14,14), camera = (blick1,blick2),cbar= :none, leg = false,xlabel = "x_1",ylabel = "x_2",zlabel = "x_3")
    plot3d!([d],[e],[f],marker=2)
    display(plt);
else
    println("Überprüft noch einmal, ob ihr die Parameter mit den richtigen Vektoren multipliziert habt.")
end

end


using PyPlot, Revise, transD_GP, DelimitedFiles, Random, BenchmarkTools
## set geometry
rMin = 100 #m
rMax = 20000  #m
nRx = 150
freqs   = [0.1, 0.3, 0.7] #Hz
# receiver geometry
rRx   = collect(LinRange(rMin,rMax,nRx))  # Ranges to receivers   (m)
zRx = 975.     # Depth of Receivers, if this is an array it calls normal FHT without lagged convolution
zTx = 1000.
RxAzim=40.
TxDip=20.
# Note that the receiver depth needs to be in same model layer as transmitter.
# model
zfixed   = [-1e6,    0,      1000,   2000, 2100]
rhofixed = [1e13,    0.3,    1,      100,  1]
## set operator with lagged convolution
F = transD_GP.CSEM1DEr.RadialErLagged(zTx    = [zTx],
                      rRx    = rRx,
                      freqs  = freqs,
                      zRx    = [zRx],
                      RxAzim = RxAzim,
                      TxDip  = TxDip)

## model one wavenumber
krho = 2.0593e-10
rxno = 1
txno = 1
Ek = transD_GP.CSEM1DEr.getCSEM1DKernelsEr!(F, krho, freqs[1], zfixed, rhofixed, rxno, txno)
## timing
@info "Lagged convolution time"
transD_GP.CSEM1DEr.getfield!(F, zfixed, rhofixed)
@btime transD_GP.CSEM1DEr.getfield!($F, $zfixed, $rhofixed)
# Discrete Hankel Tx
G = transD_GP.CSEM1DEr.RadialErDHT(rRx  = F.rRx,
                          freqs  = F.freqs,
                          zRx    = F.zRx[1]*ones(length(F.rRx)),
                          RxAzim = RxAzim*ones(length(F.rRx)),
                          zTx    = F.zTx[1]*ones(length(F.rRx)),
                          TxDip  = TxDip*ones(length(F.rRx))
                          )
@info "Vanilla DHT time"
transD_GP.CSEM1DEr.getfield!(G, zfixed, rhofixed)
@btime transD_GP.CSEM1DEr.getfield!($G, $z, $ρ)
## plot
figure()
subplot(121)
step(log10.(rhofixed[2:end]), zfixed[2:end])
xlim(-1, 2)
ylim(1000, 3700)
grid()
gca().invert_yaxis()
subplot(122)
semilogy(rRx, abs.(F.Er), label="julia lagged HT")
semilogy(rRx, abs.(G.Er), label="julia vanilla DHT")
include("csem_Er_response.jl")
semilogy(rRx, abs.(Erm), "--", label="matlab 2012 code")
grid()
legend()
## random, many layers
Random.seed!(435)
nlayers = 100
z = [-1e6, 0, 1000, 1000 .+ cumsum(50*rand(nlayers-3))...]
ρ = [1e13, 0.3, 1, 10 .^(1 .+ 1.5*rand(nlayers-3))...]
figure()
subplot(121)
step(log10.(ρ[2:end]), z[2:end])
xlim(1,2.5 )
grid(); gca().invert_yaxis()
@info "timing for $nlayers layers"
transD_GP.CSEM1DEr.getfield!(F, z, ρ)
@btime transD_GP.CSEM1DEr.getfield!($F, $z, $ρ)
subplot(122)
semilogy(rRx, abs.(F.Er), label="julia")


# PREAMBLE

# PKG_SETUP

# ## Setup

using DirectTrajectoryOptimization
using LinearAlgebra
using Plots

# ## horizon
T = 51

# ## car
num_state = 3
num_action = 2
num_parameter = 0

function car(x, u, w)
    [u[1] * cos(x[3]); u[1] * sin(x[3]); u[2]]
end

function midpoint_implicit(y, x, u, w)
    h = 0.1 # timestep
    y - (x + h * car(0.5 * (x + y), u, w))
end

# ## model
dt = Dynamics(midpoint_implicit, num_state, num_state, num_action, num_parameter=num_parameter)
dynamics = [dt for t = 1:T-1]

# ## initialization
x1 = [0.0; 0.0; 0.0]
xT = [1.0; 1.0; 0.0]

# ## objective
ot = (x, u, w) -> 0.0 * dot(x - xT, x - xT) + 1.0 * dot(u, u)
oT = (x, u, w) -> 0.0 * dot(x - xT, x - xT)
ct = Cost(ot, num_state, num_action, num_parameter=num_parameter)
cT = Cost(oT, num_state, 0, num_parameter=num_parameter)
objective = [[ct for t = 1:T-1]..., cT]

# ## constraints
action_lower = -0.5 * ones(num_action)
action_upper = 0.5 * ones(num_action)
bnd1 = Bound(num_state, num_action, state_lower=x1, state_upper=x1, action_lower=action_lower, action_upper=action_upper)
bndt = Bound(num_state, num_action, action_lower=action_lower, action_upper=action_upper)
bndT = Bound(num_state, 0, state_lower=xT, state_upper=xT)
bounds = [bnd1, [bndt for t = 2:T-1]..., bndT]

p_obs = [0.5; 0.5]
r_obs = 0.1
function obs(x, u, w)
    e = x[1:2] - p_obs
    return [r_obs^2.0 - dot(e, e)]
end

cont = Constraint(obs, num_state, num_action, num_parameter=num_parameter, indices_inequality=collect(1:1))
conT = Constraint(obs, num_state, 0, num_parameter=num_parameter, indices_inequality=collect(1:1))
constraints = [[cont for t = 1:T-1]..., conT]

# ## problem
p = Solver(dynamics, objective, constraints, bounds)

# ## initialize
x_interpolation = linear_interpolation(x1, xT, T)
u_guess = [0.001 * randn(num_action) for t = 1:T-1]

initialize_states!(p, x_interpolation)
initialize_controls!(p, u_guess)

# ## solve
solve!(p)

# ## solution
x_sol, u_sol = get_trajectory(p)

@show x_sol[1]
@show x_sol[T]

# ## state
plot(hcat(x_sol...)[1, :], hcat(x_sol...)[2, :], label = "", color = :orange, width=2.0)
pts = Plots.partialcircle(0.0, 2.0 * π, 100, r_obs)
cx, cy = Plots.unzip(pts)
plot!(Shape(cx .+ p_obs[1], cy .+ p_obs[2]), color = :black, label = "", linecolor = :black)

# ## control
plot(hcat(u_sol[1:end-1]..., u_sol[end-1])', linetype = :steppost)


module C2_0
function f(q::Vector{T}, q_dot::Vector{T}, xi::T) where T
    l1 = q[1]
    l2 = q[2]
    l3 = q[3]
    l1_dot = q_dot[1]
    l2_dot = q_dot[2]
    l3_dot = q_dot[3]

    -l1_dot.*(3.53674108537383e-11*l1 - 2.16714051041859e-11*l2 - 1.38960161444059e-11*l3 + 3.04085216377704e-14) + l2_dot.*(2.16714051041859e-11*l1 - 2.75081888864459e-11*l2 + 5.69132167691212e-12*l3 + 2.23183844184968e-14) + l3_dot.*(1.38960161444059e-11*l1 + 5.69132167691212e-12*l2 - 1.96418867892903e-11*l3 + 8.44288384244248e-15)
end
end


# Copyright (c) 2018: Matthew Wilhelm & Matthew Stuber.
# This code is licensed under MIT license (see LICENSE.md for full details)
#############################################################################
# Reverse McCormick.jl
# A Reverse McCormick operator library in Julia
# See https://github.com/PSORLab/ReverseMcCormick.jl
#############################################################################
# src/reverse_operators/extrema.jl
# Contains definitions of reverse min and max.
#############################################################################

"""
$(SIGNATURES)

Reverse McCormick operator for `max(::MC, ::MC)`. Note that this function is not
invertible. If `b` dominates `c`, the  `b = b ∩ a`. If `c` dominates `b`,  `c = c ∩ a`.
No update otherwise. TODO: See if constructing an reverse from the identity
`max(x,y) = 0.5*(x + y + abs(x - y))` yields a tigher relaxation in this case.
"""
function max_rev(a::MC, b::MC, c::MC)
    isempty(a) && (return a, a, a)
    if hi(b) <= lo(c)
        c = c ∩ a
    elseif lo(b) >= hi(c)
        b = b ∩ a
    end
    return a, b, c
end

function max_rev(a::MC{N,T}, b::MC{N,T}, c::C) where {N, T<:RelaxTag, C<:NumberNotRelax}
    isempty(a) && (return a, a, a)
    if c < lo(a)
        b = b ∩ a
    end
    a, b, MC{N,T}(c)
end

function max_rev(a::MC{N,T}, c::C, b::MC{N,T}) where {N, T<:RelaxTag, C<:NumberNotRelax}
    isempty(a) && (return a, a, a)
    if c < lo(a)
        b = b ∩ a
    end
    a, MC{N,T}(c), b
end

"""
$(SIGNATURES)

Reverse McCormick operator for `min`. Note that this function is not
invertible. If `b` dominates `c`, the  `b = b ∩ a`. If `c` dominates `b`,  `c = c ∩ a`.
No update otherwise.  TODO: See if constructing an reverse from the identity
`min(x, y) = 0.5*(x + y - abs(x - y))` yields a tigher relaxation in this case.
"""
function min_rev(a::MC, b::MC, c::MC)
    isempty(a) && (return a, a, a)
    if hi(b) <= lo(c)
        b = b ∩ a
    elseif lo(b) >= hi(c)
        c = c ∩ a
    end
    a, b, c
end

function min_rev(a::MC{N,T}, b::MC{N,T}, c::C) where {N, T<:RelaxTag, C<:NumberNotRelax}
    isempty(a) && (return a, a, a)
    if c > hi(a)
        b = b ∩ a
    end
    a, b, MC{N,T}(c)
end

function min_rev(a::MC{N,T}, c::C, b::MC{N,T}) where {N, T<:RelaxTag, C<:NumberNotRelax}
    isempty(a) && (return a, a, a)
    if c > hi(a)
        b = b ∩ a
    end
    a, MC{N,T}(c), b
end


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

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

K1, K2 = 247.0, 247.0    # Pa
mu1, mu2 = 126.0, 126.0   # Pa
lambda1 = lame_lambda(K1, mu1)
lambda2 = lame_lambda(K2, mu2)
stiffness = CutCellDG.HookeStiffness(lambda1, mu1, lambda2, mu2)
penaltyfactor = 1e2
eta = 1

polyorder = 2
numqp = required_quadrature_order(polyorder) + 2
nelmts = 17
basis = TensorProductBasis(2, polyorder)
interface_center = [0.5, 0.5]
interface_radius = 1.0/3.0

distancefunc(x) = -circle_distance_function(x,interface_center,interface_radius)

nodaldisplacement, mergedmesh, cellquads, facequads, interfacequads =
    nodal_displacement(
        distancefunc,
        stiffness,
        nelmts,
        basis,
        numqp,
        penaltyfactor,
        eta,
    )

refseedpoints, spatialseedpoints, seedcellids =
    CutCellDG.seed_zero_levelset_with_interfacequads(interfacequads, mergedmesh)
interfacenormals =
    CutCellDG.collect_interface_normals(interfacequads, mergedmesh)

spatialpoints = spatialseedpoints[1, :, :]
referencepoints = refseedpoints
referencecellids = seedcellids

relspatialpoints = spatialpoints .- interface_center
angularposition = angular_position(relspatialpoints)
sortidx = sortperm(angularposition)

angularposition = angularposition[sortidx]
spatialpoints = spatialpoints[:, sortidx]
referencepoints = referencepoints[:, :, sortidx]
referencecellids = referencecellids[:, sortidx]
interfacenormals = interfacenormals[:, sortidx]

productdisplacement = CutCellDG.displacement_at_reference_points(
    nodaldisplacement,
    basis,
    referencepoints,
    referencecellids,
    +1,
    mergedmesh,
)
parentdisplacement = CutCellDG.displacement_at_reference_points(
    nodaldisplacement,
    basis,
    referencepoints,
    referencecellids,
    -1,
    mergedmesh,
)
# exactdisplacement =
#     mapslices(x -> displacement(0.1, x), spatialpoints, dims = 1)

fig,ax = PyPlot.subplots(2,1)
ax[1].plot(angularposition,productdisplacement[1,:],label="product u1")
ax[1].plot(angularposition,parentdisplacement[1,:],label="parent u1")
# ax[1].plot(angularposition,exactdisplacement[1,:],"--",label="exact")
ax[2].plot(angularposition,productdisplacement[2,:],label="product u1")
ax[2].plot(angularposition,parentdisplacement[2,:],label="parent u1")
# ax[2].plot(angularposition,exactdisplacement[2,:],"--",label="exact")
ax[1].grid()
ax[2].grid()
ax[1].legend()
ax[2].legend()
fig

productdisplacementmagnitude = vec(mapslices(norm,productdisplacement,dims=1))
parentdisplacementmagnitude = vec(mapslices(norm,parentdisplacement,dims=1))

diffproduct = maximum(productdisplacementmagnitude) - minimum(productdisplacementmagnitude)
diffparent = maximum(parentdisplacementmagnitude) - minimum(parentdisplacementmagnitude)

fig,ax = PyPlot.subplots()
ax.plot(angularposition,productdisplacementmagnitude,label="product")
ax.plot(angularposition,parentdisplacementmagnitude,label="parent")
ax.legend()
ax.grid()
fig

productstress = CutCellDG.stress_at_reference_points(
    nodaldisplacement,
    basis,
    stiffness,
    referencepoints,
    referencecellids,
    +1,
    mergedmesh,
)
producttraction = CutCellDG.traction_force_at_points(productstress,interfacenormals)

parentstress = CutCellDG.stress_at_reference_points(
    nodaldisplacement,
    basis,
    stiffness,
    referencepoints,
    referencecellids,
    -1,
    mergedmesh,
)
parenttraction = CutCellDG.traction_force_at_points(parentstress,interfacenormals)
#
# exactstress = mapslices(x->stress_field(lambda1,mu1,0.1,x),spatialpoints,dims=1)
# exacttraction = CutCellDG.traction_force_at_points(exactstress,interfacenormals)
#
#
# fig,ax = PyPlot.subplots(2,1)
#
# ax[1].plot(angularposition,producttraction[1,:],label="product t1")
# ax[1].plot(angularposition,parenttraction[1,:],label="parent t1")
# ax[1].plot(angularposition,exacttraction[1,:],"--",label="exact t1")
# ax[2].plot(angularposition,producttraction[2,:],label="product t2")
# ax[2].plot(angularposition,parenttraction[2,:],label="parent t2")
# ax[2].plot(angularposition,exacttraction[2,:],"--",label="exact t2")
#
# ax[1].grid()
# ax[2].grid()
# ax[1].legend()
# ax[2].legend()
#
# fig

producttractionmagnitude = vec(mapslices(norm,producttraction,dims=1))
parenttractionmagnitude = vec(mapslices(norm,parenttraction,dims=1))

fig,ax = PyPlot.subplots()
ax.plot(angularposition,producttractionmagnitude)
ax.plot(angularposition,parenttractionmagnitude)
ax.grid()
fig


function moora(decisionMat::DataFrame, weights::Array{Float64,1})

    w = unitize(weights)

    nalternatives, ncriteria = size(decisionMat)

    normalizedMat = normalize(decisionMat)
    weightednormalizedMat = w * normalizedMat

    cmaxs = colmaxs(weightednormalizedMat)

    refmat = similar(weightednormalizedMat)

    for rowind in 1:nalternatives
        refmat[rowind, :] .= cmaxs - weightednormalizedMat[rowind, :]
    end

    scores = rmaxs = rowmaxs(refmat)

    bestIndex = sortperm(rmaxs) |> first

    result = MooraResult(
       decisionMat,
       w,
       weightednormalizedMat,
       refmat,
       scores,
       bestIndex
   )

    return result
end


struct PINningSynapseParameter
end

@snn_kw mutable struct PINningSynapse{MFT=Matrix{Float32},VFT=Vector{Float32}}
    param::PINningSynapseParameter = PINningSynapseParameter()
    W::MFT  # synaptic weight
    rI::VFT # postsynaptic rate
    rJ::VFT # presynaptic rate
    g::VFT  # postsynaptic conductance
    P::MFT  # <rᵢrⱼ>⁻¹
    q::VFT  # P * r
    f::VFT  # postsynaptic traget
    records::Dict = Dict()
end

"""
[PINing Sparse Synapse](https://www.ncbi.nlm.nih.gov/pubmed/26971945)
"""
PINningSynapse

function PINningSynapse(pre, post; σ = 1.5, p = 0.0, α = 1, kwargs...)
    rI, rJ, g = post.r, pre.r, post.g
    W = σ * 1 / √pre.N * randn(post.N, pre.N) # normalized recurrent weight
    P = α * I(post.N) # initial inverse of C = <rr'>
    f, q = zeros(post.N), zeros(post.N)
    PINningSynapse(;@symdict(W, rI, rJ, g, P, q, f)..., kwargs...)
end

function forward!(c::PINningSynapse, param::PINningSynapseParameter)
    @unpack W, rI, rJ, g, P, q = c
    mul!(q, P, rJ)
    mul!(g, W, rJ)
end

function plasticity!(c::PINningSynapse, param::PINningSynapseParameter, dt::Float32, t::Float32)
    @unpack W, rI, g, P, q, f = c
    C = 1 / (1 + dot(q, rI))
    BLAS.ger!(C, f - g, q, W)
    BLAS.ger!(-C, q, q, P)
end


@testset "Hilbert symbols" begin
  v = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1,
       -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1,
       -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1 ]

  for i in 1:8
    for j in 1:8
      @test hilbert_symbol(i, j, 2) == v[(i - 1) * 8 + j]
      @test hilbert_symbol(fmpz(i), fmpz(j), 2) == v[(i - 1) * 8 + j]
      @test hilbert_symbol(fmpz(i), fmpz(j), fmpz(2)) == v[(i - 1) * 8 + j]
      @test hilbert_symbol(fmpq(i), fmpq(j), 2) == v[(i - 1) * 8 + j]
      @test hilbert_symbol(fmpq(i), fmpq(j), fmpz(2)) == v[(i - 1) * 8 + j]
    end
  end

  for p in PrimesSet(3, 100)
    for a in 1:100
      for b in 1:00
        h = hilbert_symbol(a, b, p)
        a = fmpz(a)
        b = fmpz(b)
        r = (-1)^(valuation(a, p) * valuation(b, p)) * a^(valuation(b, p)) * b^(valuation(a, p))
        @test h == jacobi_symbol(r, fmpz(p))
      end
    end
  end

  Qx, x = PolynomialRing(FlintQQ, "x")
  K, b = NumberField(x^3-3*x-1, "a")
  OK = maximal_order(K)
  for P in prime_ideals_up_to(OK, 200)
    @test hilbert_symbol(b, -3, P) == 1
  end

  # Test where Magma div(x, y) differs from julia div(x, y) (internally)
  K, a = CyclotomicRealSubfield(8, "a") # x^2 - 2
  z = 9278908160780559301//4*a+6561375391013480455//2
  w = K(-2)
  p = prime_decomposition(maximal_order(K), 2)[1][1]
  @test hilbert_symbol(z, w, p) == 1

  k, a = rationals_as_number_field()
  kt, t = k["t"]
  LS, LSb = number_field(t^2 - 2)
  LNS, LNSb = number_field([t^2 - 2])
  KS, KSb = number_field(x^2 - 2)
  KNS, KNSb = number_field([x^2 - 2])
  p = prime_decomposition(maximal_order(k), 2)[1][1]
  pKS = prime_decomposition(maximal_order(KS), 2)[1][1]
  pKNS = prime_decomposition(maximal_order(KNS), 2)[1][1]
  pLS = prime_decomposition(maximal_order(LS), p)[1][1]
  pLNS = prime_decomposition(maximal_order(LNS), p)[1][1]

  q = prime_decomposition(maximal_order(k), 3)[1][1]
  qKS = prime_decomposition(maximal_order(KS), 3)[1][1]
  qKNS = prime_decomposition(maximal_order(KNS), 3)[1][1]
  qLS = prime_decomposition(maximal_order(LS), q)[1][1]
  qLNS = prime_decomposition(maximal_order(LNS), q)[1][1]

  for i in 1:10
    z = rand(KS, -2:2)
    v = quadratic_defect(z, pKS)
    #@test v == quadratic_defect(KNS(coordinates(z)), pKNS)
    #@test v == quadratic_defect(LS(k.(coordinates(z))), pLS)
    #@test v == quadratic_defect(LNS(k.(coordinates(z))), pLNS)
    v = quadratic_defect(z, qKS)
    #@test v == quadratic_defect(KNS(coordinates(z)), qKNS)
    #@test v == quadratic_defect(LS(k.(coordinates(z))), qLS)
    #@test v == quadratic_defect(LNS(k.(coordinates(z))), qLNS)
  end

  for i in 1:8
    for j in 1:8
      v = hilbert_symbol(i, j, 2)
      @test v == hilbert_symbol(k(i), k(j), p)
      #@test v == hilbert_symbol(KNS(i), KNS(i), pKNS)
      #@test v == hilbert_symbol(LS(i), LS(i), pLS)
      #@test v == hilbert_symbol(LNS(i), LNS(i), pLNS)
    end
  end
end


using OrdinaryDiffEq

f = function (du,u,p,t)
  du[1][1] = 0.85u[1][1]
end

u = [[1]]

prob = ODEProblem(f,u,(0.0,1.0))

sol = solve(prob,dt=1/2^4)


#### Constants
const MODEL_FILENAME = "perceptron_model.jld" # jld filename for storing the model
const MODEL_ID       = "perceptron_model"     # if od the model in the filesystem jld data

using Statistics

#### An abstract perceptron model
abstract type PerceptronModel{T} end

#### Linear Perceptron type
mutable struct LinearPerceptron{T<:AbstractFloat} <: PerceptronModel{T}
   α::T
   Θ::Vector{T}
   shuffle_epoch::Bool
   random_state::Integer
   max_epochs::Integer
   last_epoch::Integer
   history::Vector{Integer}
   mx::Matrix{T}          # mean stat after for z-scoring input data (X)
   sx::Matrix{T}          # standard deviation stat after for z-scoring target data (X)
   centralize::Bool
   nfeatures::Integer
end

function LinearPerceptron(X::AbstractArray{T},
                          alpha,
                          shuffle_epoch,
                          random_state,
                          max_epochs,
                          centralize) where T<:AbstractFloat

   return LinearPerceptron(alpha, # I will refactor to a constructor. Cleaner
                           Vector{T}(undef,1),
                           shuffle_epoch,
                           random_state,
                           max_epochs,
                           0,
                           Vector{Integer}(undef,1),
                           mean(X,dims=1),
                           std(X,dims=1),
                           centralize,
                           size(X,2))

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

#### Linear Perceptron type
mutable struct VotedPerceptron{T<:AbstractFloat} <: PerceptronModel{T}
   α::T
   Θ#::Dict{Integer,Vector{T}}
   c#::Dict{Integer,Integer}
   k::Integer
   shuffle_epoch::Bool
   random_state::Integer
   max_epochs::Integer
   last_epoch::Integer
   history::Vector{Integer}
   mx::Matrix{T}          # mean stat after for z-scoring input data (X)
   sx::Matrix{T}          # standard deviation stat after for z-scoring target data (X)
   centralize::Bool
   nfeatures::Integer
end

function VotedPerceptron(X::AbstractArray{T},
                          alpha,
                          shuffle_epoch,
                          random_state,
                          max_epochs,
                          centralize) where T<:AbstractFloat

   return VotedPerceptron(alpha, # I will refactor to a constructor. Cleaner
                           nothing,
                           nothing,
                           0,
                           shuffle_epoch,
                           random_state,
                           max_epochs,
                           0,
                           Vector{Integer}(undef,1),
                           mean(X,dims=1),
                           std(X,dims=1),
                           centralize,
                           size(X,2))

end

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

#### Kernel Perceptron type
mutable struct KernelPerceptron{T<:AbstractFloat} <: PerceptronModel{T}
   λ::Vector{T} # lagrange vector
   max_epochs::Integer
   last_epoch::Integer
   history::Vector{Integer}
   mx::Matrix{T}          # mean stat after for z-scoring input data (X)
   sx::Matrix{T}          # standard deviation stat after for z-scoring target data (X)
   centralize::Bool
   nfeatures::Integer
   kernel::String
   width::T
   sv_x::Vector{T}
   sv_y::Vector{T}
end

function KernelPerceptron(X::AbstractArray{T},
                          max_epochs,
                          centralize,
                          kernel,
                          width) where T<:AbstractFloat

   return KernelPerceptron(zeros(T,size(X,1)),
                           max_epochs,
                           0,
                           Vector{Integer}(undef,1),
                           mean(X,dims=1),
                           std(X,dims=1),
                           centralize,
                           size(X,2),
                           kernel,
                           width,
                           Vector{T}(undef,1),
                           Vector{T}(undef,1))

end

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

#### Averaged Perceptron type
mutable struct AveragedPerceptron{T<:AbstractFloat} <: PerceptronModel{T}
   α::T
   Θ
   shuffle_epoch::Bool
   random_state::Integer
   max_epochs::Integer
   last_epoch::Integer
   history::Vector{Integer}
   mx::Matrix{T}          # mean stat after for z-scoring input data (X)
   sx::Matrix{T}          # standard deviation stat after for z-scoring target data (X)
   centralize::Bool
   nfeatures::Integer
end

function AveragedPerceptron(X::AbstractArray{T},
                          alpha,
                          shuffle_epoch,
                          random_state,
                          max_epochs,
                          centralize) where T<:AbstractFloat

   return AveragedPerceptron(alpha, # I will refactor to a constructor. Cleaner
                           0,
                           shuffle_epoch,
                           random_state,
                           max_epochs,
                           0,
                           Vector{Integer}(undef,1),
                           mean(X,dims=1),
                           std(X,dims=1),
                           centralize,
                           size(X,2))

end

## choosing types
######################################################################################################
function Model(X,#::AbstractArray{T},
               alpha,
               shuffle_epoch,
               random_state,
               max_epochs,
               centralize,
               kernel,
               width,
               mode) where T<:AbstractFloat

      println("size",size(X))
      if mode == "linear"
         return LinearPerceptron(X,
                                 alpha,
                                 shuffle_epoch,
                                 random_state,
                                 max_epochs,
                                 centralize)
      elseif mode == "kernel"
         return KernelPerceptron(X,
                                 max_epochs,
                                 centralize,
                                 kernel,
                                 width)
      elseif mode == "voted"
      return VotedPerceptron(X,
                           alpha,
                           shuffle_epoch,
                           random_state,
                           max_epochs,
                           centralize)
      elseif mode == "averaged"
      return AveragedPerceptron(X,
                           alpha,
                           shuffle_epoch,
                           random_state,
                           max_epochs,
                           centralize)
      else
         error("Invalid perceptron mode name: $(mode). \n Cadidates are: linear, kernel, voted or averaged")
      end
end

######################################################################################################
## Load and Store models (good for production)
function load(; filename::AbstractString = MODEL_FILENAME, modelname::AbstractString = MODEL_ID)
    local M
    jldopen(filename, "r") do file
        M = read(file, modelname)
    end
    M
end

function save(M::PerceptronModel; filename::AbstractString = MODEL_FILENAME, modelname::AbstractString = MODEL_ID)
    jldopen(filename, "w") do file
        write(file, modelname, M)
    end
end


function rfloor(val::Float64)::Integer
    rf_val = Integer(floor(val + val * 1e-10 + 1e-6))
    if (rf_val < val - 1 + 1e-6)
        rf_val += 1
    end
    return rf_val
end

function scale_vector_of_double(v::Vector{Float64})
    n = length(v)
    int_v = Vector{Integer}()
    max_val = maximum(v)
    scaling_factor = typemax(Cint) / (n + 1) / max_val
    for i in 1:n
        push!(int_v, rfloor(scaling_factor * v[i]))
    end
    return int_v
end

function minknap(p::Vector{Float64}, w::Vector{T}, capacity::T) where T <: Integer
    int_p = scale_vector_of_double(p)
    (obj, sol) = minknap(int_p, w, capacity)
    real_obj = sum(sol[i] * p[i] for i in 1:length(sol))
    return (real_obj, sol)
end

function minmcknap(p::Vector{Float64}, w::Vector{T}, capacity::T, items_per_class::Vector{T}) where T <: Integer
    int_p = scale_vector_of_double(p)
    (obj, sol) = minmcknap(int_p, w, capacity, items_per_class)
    real_obj = sum(sol[i] * p[i] for i in 1:length(sol))
    return (real_obj, sol)
end


struct EM{TGV, TF<:AbstractFloat} <: FirstOrderNonMetropolisSampler
    ∇V!::TGV
    β::TF
    Δt::TF
    σ::TF
end

"""
    EM(∇V!, β, γ, M, Δt)

Set up the EM integrator for overdamped Langevin.

### Fields

* ∇V!   - In place gradient of the potential
* β     - Inverse temperature
* Δt    - Time step
"""
function EM(∇V!::TGV, β::TF, Δt::TF) where{TGV, TF<:AbstractFloat}
    σ = sqrt(2 * Δt /β);
    return EM(∇V!, β, Δt, σ)
end

mutable struct EMState{Tx} <:FirstOrderNonMetropolisSamplerState
    x::Tx
    ∇V::Tx
end

function InitState!(x₀, sampler::EM)

    ∇V = similar(x₀);
    sampler.∇V!(∇V, x₀);
    return EMState(x₀, copy(∇V));
end

function InitState(x₀, sampler::EM)

    ∇V = similar(x₀);
    sampler.∇V!(∇V, x₀);
    return EMState(deepcopy(x₀), copy(∇V));
end

function UpdateState!(state::EMState, sampler::EM)

    @. state.x = state.x - sampler.Δt * state.∇V + sampler.σ * randn();
    sampler.∇V!(state.∇V, state.x);

    state
end


""" Categories of graphs and other categorical and algebraic aspects of graphs.
"""
module GraphCategories

using ..FinSets, ..CSets, ..Limits
using ...Graphs.BasicGraphs
import ...Graphs.GraphAlgorithms: connected_component_projection

function connected_component_projection(g::AbstractACSet)::FinFunction
  proj(coequalizer(FinFunction(src(g), nv(g)),
                   FinFunction(tgt(g), nv(g))))
end

end


function kernel_kmeans(K::Matrix, k::Int, ϵ::Float64 = 1e-5)
    n = size(K, 1)
    C = rand(1:k, n)
    C_next = copy(C)

    s_total = Inf
    s_total_next = 0

    while true
        for i in 1:n
            k_min = 1
            s_min = Inf

            for kk in 1:k
                s1 = K[i, i]

                N_kk = 0
                s2 = 0
                for l in 1:n
                    if C[l] == kk
                        N_kk += 1
                        s2 += K[i, l]
                    end
                end
                s2 = (-2/N_kk) * s2

                s3 = 0
                for l in 1:n, m in 1:n
                    if C[l] == kk && C[m] == kk
                        s3 += K[l, m]
                    end
                end
                s3 = (1/N_kk^2) * s3

                s = s1 + s2 + s3
                if s < s_min
                    s_min = s
                    k_min = kk
                end
            end

            C_next[i] = k_min
            s_total_next += s_min
        end

        println(s_total_next)

        if abs(s_total_next - s_total) < ϵ
            break
        end

        s_total, s_total_next = s_total_next, 0
        C, C_next = C_next, C
    end

    return [findin(C_next, i) for i in 1:k]
end

function max_k_cut{T}(D::Matrix{T}, k::Int)
    n = size(D, 1)
    Y = Semidefinite(n, n)

    constraints = Constraint[]

    for i in 1:n, j in 1:n
        if i == j
            push!(constraints, Y[i, j] == T(1))
        elseif k >= 3
            push!(constraints, Y[i, j] >= -inv(k - 1))
        end
    end

    problem = minimize(trace(D*Y), constraints)
    solve!(problem, MosekSolver())

    return Convex.evaluate(Y)
end

function factor_k_cut(Y::Matrix, k::Int)
    #Y = max_k_cut(D, k)
    U, S, V = svd(Y)
    S = diagm(S[1:k])
    W = U[:, 1:k] * sqrt(S)
end


using .RationalFunctionFields

logger = Logging.SimpleLogger(stdout, Logging.Debug)
Logging.global_logger(logger)

@testset "lol" begin
    FF = Nemo.QQ

    R, (x1, x2) = Nemo.PolynomialRing(FF, ["x1", "x2"])

    set = [
          ( x1^2 + x2^2 ) // R(1),
          ( x1 ) // R(1),
    ]

    FF = RationalFunctionField(set)

    println( RationalFunctionFields.contains(FF, x1 // R(1), p=1) )

    println( RationalFunctionFields.contains(FF, x2^2 // R(1), p=0.99) )

    println( RationalFunctionFields.groebner_ideal(set, check=true) )
end


#
using Test
using Arena
using LinearAlgebra

@testset "[TEST] basics" begin

  as = ArcPointsRangeSolve([0.0;1.0;0.0],[1.0/sqrt(2.0);1.0/sqrt(2.0);0.0],[1.0;0.0;0.0],1.0)
  findaxiscenter!(as)
  @show as.center, as.axis, as.angle
  @test norm(as.center-[0.0;0.0;0.0]) < 1e-4
  @test norm(as.axis-[0.0;0.0;-1.0]) < 1e-4
  @test -1e-4 < as.angle-pi/2 < 1e-4

  as = ArcPointsRangeSolve([2.0;0;0.0],[0.5;1.5;0.0],[-1.0;0;0.0],1.5)
  findaxiscenter!(as)
  @show as.center, as.axis, as.angle
  @test norm(as.center-[0.5;0.0;0.0]) < 1e-4
  @test norm(as.axis-[0.0;0.0;1.0]) < 1e-4
  @test -1e-4 < as.angle-pi < 1e-4

  as = ArcPointsRangeSolve([0;-1.0;0],[0;0.5;1.5],[0;2.0;0.0],1.5)
  findaxiscenter!(as)
  @show as.center, as.axis, as.angle
  @test norm(as.center-[0;0.5;0]) < 1e-4
  @test norm(as.axis-[-1.0;0.0;0.0]) < 1e-4
  @test -1e-4 < as.angle-pi < 1e-4

end


using Logging, Random
using JuMP, Gurobi
using DecisionProgramming

Random.seed!(13)

const N = 4
const c_k = rand(N)
const b = 0.03
fortification(k, a) = [c_k[k], 0][a]

@info("Creating the influence diagram.")
diagram = InfluenceDiagram()

add_node!(diagram, ChanceNode("L", [], ["high", "low"]))

for i in 1:N
    add_node!(diagram, ChanceNode("R$i", ["L"], ["high", "low"]))
    add_node!(diagram, DecisionNode("A$i", ["R$i"], ["yes", "no"]))
end

add_node!(diagram, ChanceNode("F", ["L", ["A$i" for i in 1:N]...], ["failure", "success"]))

add_node!(diagram, ValueNode("T", ["F", ["A$i" for i in 1:N]...]))

generate_arcs!(diagram)

X_L = [rand(), 0]
X_L[2] = 1.0 - X_L[1]
add_probabilities!(diagram, "L", X_L)

for i in 1:N
    x_R, y_R = rand(2)
    X_R = ProbabilityMatrix(diagram, "R$i")
    X_R["high", "high"] = max(x_R, 1-x_R)
    X_R["high", "low"] = 1 - max(x_R, 1-x_R)
    X_R["low", "low"] = max(y_R, 1-y_R)
    X_R["low", "high"] = 1-max(y_R, 1-y_R)
    add_probabilities!(diagram, "R$i", X_R)
end

X_F = ProbabilityMatrix(diagram, "F")
x_F, y_F = rand(2)
for s in paths([State(2) for i in 1:N])
    denominator = exp(b * sum(fortification(k, a) for (k, a) in enumerate(s)))
    s1 = [s...]
    X_F[1, s1..., 1] = max(x_F, 1-x_F) / denominator
    X_F[1, s..., 2] = 1.0 - X_F[1, s..., 1]
    X_F[2, s..., 1] = min(y_F, 1-y_F) / denominator
    X_F[2, s..., 2] = 1.0 - X_F[2, s..., 1]
end
add_probabilities!(diagram, "F", X_F)

Y_T = UtilityMatrix(diagram, "T")
for s in paths([State(2) for i in 1:N])
    cost = sum(-fortification(k, a) for (k, a) in enumerate(s))
    Y_T[1, s...] = 0 + cost
    Y_T[2, s...] = 100 + cost
end
add_utilities!(diagram, "T", Y_T)

generate_diagram!(diagram, positive_path_utility=true)

model = Model()
z = DecisionVariables(model, diagram)
x_s = PathCompatibilityVariables(model, diagram, z, probability_cut = false)
EV = expected_value(model, diagram, x_s)
@objective(model, Max, EV)

@info("Starting the optimization process.")
optimizer = optimizer_with_attributes(
    () -> Gurobi.Optimizer(Gurobi.Env()),
    "IntFeasTol"      => 1e-9,
)
set_optimizer(model, optimizer)
optimize!(model)

@info("Extracting results.")
Z = DecisionStrategy(z)
U_distribution = UtilityDistribution(diagram, Z)
S_probabilities = StateProbabilities(diagram, Z)

@info("Printing decision strategy:")
print_decision_strategy(diagram, Z, S_probabilities)

@info("State probabilities:")
print_state_probabilities(diagram, S_probabilities, ["L"])
print_state_probabilities(diagram, S_probabilities, [["R$i" for i in 1:N]...])
print_state_probabilities(diagram, S_probabilities, [["A$i" for i in 1:N]...])
print_state_probabilities(diagram, S_probabilities, ["F"])

@info("Printing utility distribution.")
print_utility_distribution(U_distribution)

@info("Printing statistics")
print_statistics(U_distribution)


module Day13

export day13

using CombinedParsers
import CombinedParsers.Regexp: word, whitespace_maybe

function parseinput(filename)
    input = Vector{String}()
    for line in eachline(filename)
        push!(input, line)
    end
    earliest = parse(Int, input[1])
    buses = join(Repeat(Either('x', Numeric(Int))), ',')
    (earliest, input[2] |> buses)
end

# Bus with timestamp t arrives at dt = l*t - earliest.
# If earliest = k*t + r, then dt = (k+1)*t - earliest
# = t - r = t - mod(earliest, t)
function day13(::Val{:a}, filename)
    (earliest, buses::Vector{Union{Char,Int}}) = parseinput(filename)
    buses = filter(buses) do x
        isa(x, Int)
    end
    later = map(buses) do t
        r = mod(earliest, t)
        r == 0 ? 0 : t - r
    end
    (dt,ind) = findmin(later)
    dt * buses[ind]
end

# The time t is such that t = -i+1 mod n_i, where
# n_i is the timestamp of the i-th bus in the list
# including the 'x'. We solve using the CRT.
function day13(::Val{:b}, filename)
    (earliest, buses::Vector{Union{Char,Int}}) = parseinput(filename)
    N = prod(filter(c -> isa(c, Int), buses))
    suma = 0
    for (i,m) in enumerate(buses)
        if isa(m, Int)
            n = div(N,m)
            suma = mod(suma + (n * (-i+1) * gcdx(n,m)[2]), N)
        end
    end
    suma
end

end


using Flora.RNN
using MLDatasets # FashionMNIST

# load full training set
train_x, train_y = FashionMNIST.traindata() # 60_000

picks = [
    [0, 3, 9], # 0 T-Shirt, 3 Dress, 9 Ankle boot
    [1, 2, 5],
]

m = chain(3)
for pick in picks
    X, Y = prepare(train_x, train_y, pick)
    p = train!(:fashion, m, pick, X, Y; n=3)
    show_accuracy(m, pick, X, Y)
    save(p)
end

#=
train! n=3
pick = [0, 3, 9]
mean(argmax(m(x)) .== argmax(y)) = 0.9375
pick = [1, 2, 5]
mean(argmax(m(x)) .== argmax(y)) = 0.9864444444444445
=#


println("Starting tests")

using Base.Test
using Evapotranspiration
using Dates

@test_approx_eq_eps convert(Radian, Degree(360)) 2π 0.0001
@test promote_type(Radian, Degree) == Radian

Evap = Evapotranspiration
latitude  = Degree(45.281258)
longitude = Degree(-75.575136)

debug=true
Tmax = Celcius(19.46)
Tmin = Celcius( 5.9 )
Tdew = Celcius( 6.25)
RHmean = RH(0.71)
latitude = Degree(45.1)

@test_approx_eq_eps convert(Kelvin, Tmax) 292.6 0.1

# Number of days through the year, J, days (1 = January 1)
J = Day(134)

# Lz longitude of the centre of the local time zone
Lz = Degree(75)

# Lm longitude of the measurement site [degrees west of Greenwich]
Lm = Degree(75.76498)

latent_heat_of_vaporaization = 2.54 #  MJ/kg

timestamp = 1368594000
d1 = unix2datetime(timestamp)
@test dayofyear(d1) == 135
d2 = Date(2013,2,27)
@test dayofyear(d2) == 58
d3 = Date(2012,12,31)
@test_approx_eq_eps dayofyear(d3) 366 1 # leap year

# Example 2:
@test_approx_eq_eps Evap.P(Meter(1800), Celcius(20)) 81.8 0.1
@test_approx_eq_eps Evap.γ(Meter(1800), Celcius(20)) 0.054 0.01
@test_approx_eq_eps Evap.γ(Meter(100)) 0.0642 0.001

@test_approx_eq_eps Evap.e°(Celcius(20)) 2.3383 0.0001
# Example 3:
@test_approx_eq_eps Evap.e°(Celcius(24.5)) 3.075 0.001
@test_approx_eq_eps Evap.e°(Celcius(15.0)) 1.705 0.001
@test_approx_eq_eps Evap.Es(Celcius(15), Celcius(24.5)) 2.39 0.01  # kPa
@test_approx_eq_eps Evap.Es(Celcius(17.5), Celcius(29)) 3.003 0.001  # kPa

## Saturation vapour pressure slope (Δ) in kPa/C:
@test_approx_eq_eps Evap.Δ(Celcius(28)) 0.2201 0.001 # kPa/C
@test_approx_eq_eps Evap.Δ(Celcius(26), Celcius(30)) 0.22008 0.00001 # kPa/C

## Actual vapour pressure (Ea) derived from dewpoint temperature
@test_approx_eq_eps Evap.Ea(Celcius(20)) 2.338 0.001 # kPa

# daily mean RH comes from forecast.io
# This is preferred over Ea(Tdew)
@test_approx_eq_eps Evap.Ea(Celcius(20), Celcius(20), RH(0.5)) 1.169 0.001 # kPa
@test_approx_eq_eps Evap.Ea(Celcius(18), Celcius(25), ave(RH(0.54),RH(0.82))) 1.7788 0.0001

# This is preferred over other methods
@test_approx_eq_eps Evap.Ea(Celcius(18), Celcius(25), RH(0.54), RH(0.82)) 1.7015 0.0001 # kPa

# Example 5
Tmin = Celcius(18); RHmax = RH(0.82)
Tmax = Celcius(25); RHmin = RH(0.54)
@test_approx_eq_eps Evap.e°(Tmin) 2.064 0.001
@test_approx_eq_eps Evap.e°(Tmax) 3.168 0.001
@test_approx_eq_eps Evap.Ea(Tmin, Tmax, RHmin, RHmax) 1.70 0.01
@test_approx_eq_eps Evap.Ea(Tmin, Tmax, ave(RHmin,RHmax)) 1.78 0.01
# Example 6
esa = Evap.Esa(Tmin, Tmax, RHmin, RHmax)
@test_approx_eq_eps esa 0.91 0.01

## Vapor pressure deficit, Es - Ea, kPa:
@test_approx_eq_eps Evap.Esa(Celcius(18), Celcius(25), Tdew=Celcius(17.8)) 0.5777 0.0001  # kPa
@test_approx_eq_eps Evap.Esa(Celcius(18), Celcius(25), RH(0.68)) 0.83708 0.00001  # kPa

Radian(2) == Radian(1)

# Example 8:
## Extraterrestrial radiation for daily periods, Ra, in MJ/(m^2*day)
sept3 = Date(2013,9,3)
latitude = Degree(-20) # degrees,
loc = Location(latitude, Lz, Lm)
ϕ = Evap.latitude(loc)
@test_approx_eq_eps ϕ -0.35 0.01
J = Day(dayofyear(sept3))
@test J == Day(246)
dᵣ = Evap.dᵣ(J)
@test_approx_eq_eps dᵣ 0.985 0.001
δ  = Evap.δ(J)
@test_approx_eq_eps δ  0.120 0.001
ωs = Evap.ωs(ϕ, δ)
@test_approx_eq_eps ωs 1.527 0.001
Ra = Evap.Ra(Day, J, loc) # MJ/(m^2*day)
@test_approx_eq_eps Ra 32.2 0.1 # MJ/(m^2*day)
Ra_mm_day = Evap.MJ2mm(Ra)
@test_approx_eq_eps Ra_mm_day 13.1 0.1 # mm/day

@test_approx_eq_eps Evap.ave(Hour, 0,1) 0.5 0.01
@test_approx_eq_eps Evap.ave(Hour, 24, 1) 24.5 0.01
@test_approx_eq_eps Evap.duration(Hour, 0,1) 1 0.01
@test_approx_eq_eps Evap.duration(Hour, 24, 1) 1 0.01
@test_approx_eq_eps Evap.duration(Hour, 24, 4) 4 0.01
@test_approx_eq_eps Evap.duration(Hour, 24, 0) 0 0.01
for hr in 0:6
  @test Evap.issunset(Hour, hr, J, loc) == true
end
for hr in 7:17
  @test Evap.issunset(Hour, hr, J, loc) == false
end
for hr in 18:24
  @test Evap.issunset(Hour, hr, J, loc) == true
end

# Example 9:
N = Evap.daylighthours(J, loc)
@test_approx_eq_eps N 11.7 0.1 # max daylight hours

t = 13.5
ωs = Evap.ωs(Hour, t, J, loc)
@test_approx_eq_eps ωs 0.385 0.001
# Extraterrestrial radiation for hourly periods, Ra, in mm/time_period)
hr1 = 13
hr2 = hr1+1
J = Day(134)  # dayofyear
ra_24hr = 0
step = 1
for hr1 in 0:step:24
  hr2 = (hr1+step)%24
  ra = Evap.MJ2mm(Evap.Ra(Hour, J, hr1, hr2, loc))
  ra_24hr += ra
end
@test_approx_eq_eps ra_24hr Evap.MJ2mm(Evap.Ra(Day, J, loc)) 0.2

# Example 10:
# The amount of radiation that penetrates the atmosphere, Rs, units same as Ra
latitude = Degree(-(22 + 54/60))
loc = Location(latitude, Lz, Lm)
J = Day(135)    # day of year
Ra = Evap.Ra(Day, J, loc)
@test_approx_eq_eps Ra 25.1 0.1
N = Evap.daylighthours(J, loc)
@test_approx_eq_eps N 10.9 0.1
# in May (31 days), 220 hours of sunshine were recorded
n = 220/31
@test_approx_eq_eps n 7.1 0.1
cloud_cover = Evap.cloudcover(n, J, loc)
@test_approx_eq_eps cloud_cover 0.348 0.001
z  = Meter(100)
as = 0.25
bs = 0.5
loc = Location(latitude, Lz, Lm, z, as, bs)
Rs = Evap.Rs(cloud_cover, J, Ra, loc)
@test_approx_eq_eps Rs 14.5 0.1
@test_approx_eq_eps Evap.MJ2mm(Rs) 5.9 0.1

Tmax = 25.1
Tmin = 19.1
Tdew = 18.1
ra = Evap.Ra(Day, J, loc)
cloud_cover = (1 - 7.1/10.9)
@test_approx_eq_eps Evap.MJ2mm(Evap.Rs(cloud_cover, J, ra, loc)) 5.90 0.01

# Example 11:
# Rio de Janeiro
latitude = Degree(-(22+54/60))
loc = Location(latitude, Lz, Lm, z, as, bs)
hrs_sunshine_per_month=220
n = hrs_sunshine_per_month/31
N = Evap.daylighthours(J, loc)
cloud_cover =  1 - n/N
@test_approx_eq_eps cloud_cover 0.349 0.001
Tmin = Celcius(19.1)
Tmax = Celcius(25.1)
Ea = kPa(2.1)  # kPa
Rso = Evap.Rso(Ra,as,bs)
@test_approx_eq_eps Rso 18.8 0.1
# Rs/Rso: Ratio of solar radiation that reaches the earth (compared to a clear sky day)
Rsso = Evap.Rsso(cloud_cover, J, Ra, loc)
@test_approx_eq_eps Rsso 0.77 0.01
## Net long wave radiation, Rnl, in mm/day
Rnl = Evap.Rnl(Rsso, Tmin, Tmax, Ea)
@test_approx_eq_eps Rnl 3.5 0.1

# Example 12:
## Net solar radiation, MJ/(m^2*day)
albedo = 0.23
loc = Location(latitude, Lz, Lm, z, as, bs, albedo)
Rns = Evap.Rns(Rs, loc)
@test_approx_eq_eps Rns 11.1 0.1
Rn = Evap.Rn(Rns, Rnl)
@test_approx_eq_eps Rn 7.6 0.1
@test_approx_eq_eps Evap.MJ2mm(Rn) 3.1 0.1
ea = kPa(2.1)
# Net radiation, Rn, in mm/day:
net_ra = Evap.net_radiation(Day, cloud_cover, J, Tmin, Tmax, RHmin, RHmax, loc)
@test_approx_eq_eps net_ra 7.2 0.1

hr1 = 12
hr2 = hr1+1
net_ra_hr = Evap.net_radiation(Hour, cloud_cover, J, Tmin, Tmax, RHmin, RHmax, hr1, hr2, loc)
@test_approx_eq_eps net_ra_hr -2.363 0.001

## For a day depth is between 0.1 and 0.2, 1 month is about 1, 4 months can be 2.0.
shf = Evap.soil_heat_flux(Day, 30, Celcius(14.1), Celcius(18.8))
@test_approx_eq_eps shf 0.08451 0.00001
@test_approx_eq_eps Evap.soil_heat_flux(Day, 1, Tmin, Tmax)  0.6048 0.0001
@test_approx_eq_eps Evap.soil_heat_flux(Day, 1, Tmax, Tmin) -0.6048 0.0001
# Example 13:
# Soil is warming
T1 = Celcius(14.1) # March
T2 = Celcius(16.1) # April
T3 = Celcius(18.8) # May
@test_approx_eq_eps Evap.soil_heat_flux(Month, 2, T1, T3) 0.33 0.01
@test_approx_eq_eps Evap.soil_heat_flux(Month, 1, T1, T2) 0.28 0.01

# Example 14:
## Windspeed height compensation (for weather stations)
# Find equiv windspeed at 2m
uz = 3.2  # wind speed at 10 m in m/s
z  = 10
u2 = Evap.u2(uz, z)
@test_approx_eq_eps u2 2.4 0.01

## Brussels, 6 July located at 50°48'N and at 100 m above sea level:
latitude = Degree(50 + 48/60)
z = Meter(100)
loc = Location(latitude, Lz, Lm, z, as, bs, albedo)
Tmin = Celcius(12.3)
Tmax = Celcius(21.5)
RHmin = RH(0.63)
RHmax = RH(0.84)
RHmean = ave(RHmin,RHmax)
P_kPa = kPa(100.1)
windspeed_10m_kmph = 10
cloud_cover = 0.43
J = Day(187)
eto_day = Evap.ETo(Day, 1, Tmin, Tmax, RHmin, RHmax, P_kPa, windspeed_10m_kmph, cloud_cover, J::Day, loc)
@test_approx_eq_eps eto_day 3.7 0.01  # mm/day

# Example 17:
# Given the monthly average climatic data of April of Bangkok (Thailand)
# located at 13°44'N and at an elevation of 2 m
latitude = Degree(13+44/60)
z  = Meter(2)  # elevation above sea level
# Lz longitude of the centre of the local time zone
Lz = Degree(100+29/60)
# Lm longitude of the measurement site [degrees west of Greenwich]
Lm = Lz
loc = Location(latitude, Lz, Lm, z, as, bs, albedo)
dt = Date(2015, 4, 15)
J = Day(dayofyear(dt))
Tmin = Celcius(25.6)
Tmax = Celcius(34.8)
u2 = 2
T2 = Celcius(30.2)  # mean monthly average temp, April
T1 = Celcius(29.2)  # mean monthly average temp, March
P_kPa = Evap.P(z, ave(T1,T2))
@test_approx_eq_eps P_kPa 101.3 0.1
n  = 8.5 # hrs/day of sunshine
cloud_cover = Evap.cloudcover(n, J, loc)
# Need to figure out what to do about RH values: TODO
eto_day = Evap.ETo(Day, 1, Tmin, Tmax, RHmin, RHmax, P_kPa, windspeed_10m_kmph, cloud_cover, J::Day, loc)

# Todo:

#function print_evaporation(forecast_data) {
#    vec = forecast_data
#    totalDewPoint = 0
#    totalRH = 0
#    numDewPoints = 0
#    for (i=0; i < vec.length; i++) {
#        v = vec[i]
#	v.date = Date(v.time*1000)
#	v.dom = v.date.getDate()
#	v.hr = v.date.getHours()
#        ave_temp_c = NaN
#        if ("temperature" in v) {
#            ave_temp_c = v.temperature
#        } else if (("temperatureMax" in v) && ("temperatureMin" in v)) {
#            ave_temp_c = v.temperatureMin + 0.75*(v.temperatureMax - v.temperatureMin)
#        }
#        dew_temp_c = v.dewPoint
#        solar_radiation = 6
#        sunshine = 1-v.cloudCover; # account for night having no sun.
#        wind_speed_kmph = v.windSpeed
#        E = evaporation_mm_per_day(wind_speed_kmph, ave_temp_c, dew_temp_c)
#        H = degree_days_mm_per_day(ave_temp_c, dew_temp_c, 6, sunshine)
#                U = potential_evaporation(ave_temp_c, dew_temp_c, wind_speed_kmph, solar_radiation, sunshine)
#        precipIntensity = NaN
#        if ("percipIntensityMax" in v) {
#            precipIntensity = v.percipIntensityMax
#        }
#        if ("precipIntensity" in v) {
#            precipIntensity = v.precipIntensity
#        }
#        percip_mm_day = v.precipIntensity; # * vec[i].precipProbability
#        if (v.dom == 18) {
#		console.log(v.date+': '+v.humidity+', '+v.dewPoint)
#		totalDewPoint += v.dewPoint
#		totalRH += v.humidity
#		numDewPoints += 1
#	}
#        if (typeof percip_mm_day == 'undefined') {
#            percip_mm_day = 0
#        }
#        AM = percip_mm_day - U
#        t = Date(1000*vec[i].time)
#        #console.log(vec[i].time+": "+t + ": E="+ E.toFixed(2) + ", H=" + H.toFixed(2)+", ETo="+U.toFixed(2)+", percip="+percip_mm_day.toFixed(2)+", AM="+AM.toFixed(2))
#
#	# New method:
#	Tmin = v.temperatureMin
#	Tmax = v.temperatureMax
#	Tdew = v.dewPoint
#	RHmean = v.humidity
#	P_kPa = v.pressure
#	# TODO: scale down wind because of shelter from trees, maybe 0.25
#	windspeed_10m_kmph = v.windSpeed
#	cloud_cover = v.cloudCover
#	dayofyear = dayofyear(v.time)
#        eto_day = ETo_day(Tmin, Tmax, RHmean, P_kPa, windspeed_10m_kmph, cloud_cover, latitude, dayofyear)
#	eto_diff = U - eto_day
#        #console.log("ETo day="+eto_day.toFixed(2)+" [mm/day]   diff="+eto_diff)
#    }
#    console.log("Total dew: "+ totalDewPoint+" RH: "+totalRH)
#    aveDewPoint = totalDewPoint/numDewPoints
#    aveRH = totalRH/numDewPoints
#    console.log("Ave: Dew Point: "+ aveDewPoint+", RH: "+ aveRH)
#}
#
#
#function print_daily_forecast(data) {
#  daily = data.daily.data
#  for (i=0; i<daily.length; i++) {
#      di = daily[i]
#      console.log(Date(1000*di.time) + ":")
#      console.log("  - sunrise: " + Date(1000*di.sunriseTime))
#      console.log("  - sunset:  " + Date(1000*di.sunsetTime))
#      console.log("  - TmaxTime: " + Date(1000*di.temperatureMaxTime))
#      console.log("  - TminTime:  " + Date(1000*di.temperatureMinTime))
#      console.log(di)
#  }
#}
#
#forecast.get(latitude, longitude, options, function (err, res, data) {
#  if (err) throw err
#  #console.log('res: ' + util.inspect(res))
#  #console.log('data: ' + util.inspect(data, true, 5))
#  #console.log('daily data: ' + util.inspect(data, true, 5))
#  #print_daily_forecast(data)
#  print_evaporation(data.daily.data)
#  print_evaporation(data.hourly.data)
#})
#
#

println("Done")


using Tmatrix
using Zygote

function objective_function(r_array_input, θ_array_input)
    # println(r_array)
    # println("I am <<objective_function>>, and the type of r_array is $(typeof(r_array))")
    # println("r_array = $r_array")

    # quadrupling_mesh
    r_θ_array = Tmatrix.quadruple_mesh_density(r_array_input, θ_array_input)
    r_array = r_θ_array[:,1]
    θ_array = r_θ_array[:,2]
    println(r_array)
    println(θ_array)
    ϕ_array = zeros(size(θ_array))

    T = Tmatrix.T_matrix_SeparateRealImag_arbitrary_mesh(
        n_max, wl_or_freq_input, input_unit, Eps_r_r_1, Eps_r_i_1, Mu_r_r_1, Mu_r_i_1, Eps_r_r_2, Eps_r_i_2, Mu_r_r_2, Mu_r_i_2,
        (collect(r_array)), θ_array, ϕ_array,
        rotationally_symmetric, symmetric_about_plane_perpendicular_z, BigFloat_precision
    )
    k1_complex = Tmatrix.get_WaveVector(
        wl_or_freq_input;
        input_unit=input_unit,
        Eps_r=Complex(Eps_r_r_1, Eps_r_i_1),
        Mu_r=Complex(Mu_r_r_1, Mu_r_i_1)
    )
    # return abs(T[1]) # if we need to minimize the first element of T-matrix

    # return sum(abs.(T)) # if we need to minimize the sum of all real and imag

    return Tmatrix.get_OrentationAv_scattering_CrossSection_from_Tmatrix(T, k1_complex) # if we need to minimize scattering cross section

    """
    return -1 * Tmatrix.get_OrentationAv_emissivity_from_Tmatrix(
        T,
        Complex(k1_r, k1_i),
        Tmatrix.calculate_surface_area_of_axisymmetric_particle(r_array, θ_array)
    ) # if we need to minimize emissivity
    """
end

function ∂objective_function(r_array, θ_array)
    # println("I am <<∂objective_function>>, and the type of r_array is $(typeof(r_array))")
    # println("r_array = $r_array")
    return Zygote.gradient(r_array -> objective_function(r_array, θ_array), (collect(r_array)))[1]
end

wl_or_freq_input = 1e-6; input_unit = "m"
n_max = 1
Eps_r_r_1 = 1.0; Eps_r_i_1 = 0.0
Eps_r_r_2 = 1.5; Eps_r_i_2 = 0.01
Mu_r_r_1 = 1.0; Mu_r_i_1 = 0.0
Mu_r_r_2 = 1.0; Mu_r_i_2 = 0.0

n_θ_points = 10
θ_array = collect(LinRange(1e-6, pi - 1e-6, n_θ_points))
r_array = 0.5 * wl_or_freq_input * ones(size(θ_array))
rotationally_symmetric = true
symmetric_about_plane_perpendicular_z = false
BigFloat_precision = nothing

learning_rate = 0.5e-18
global loss_here = -1e6
global ∂loss = 0
println("Starting optimization for T-matrix ...")
global n_iteration = 0
loss_array = []
loss_old = 9999999999999
while (n_iteration < 50) # (loss_here < 0)
    n_iteration += 1
    r_array = r_array .- learning_rate .* ∂loss

    loss_here = objective_function(r_array, θ_array)
    ∂loss = ∂objective_function(r_array, θ_array)
    append!(loss_array, loss_here)

    println()
    println("iteration #$n_iteration: loss_here = $loss_here, ∂loss = $∂loss")
    println("r_array = $r_array")

    xyz = vcat(Tmatrix.convert_coordinates_Sph2Cart.(r_array, θ_array, zeros(size(r_array)))...)
    p1 = plot(xyz_initial[:,1], xyz_initial[:,3], aspect_ratio=:equal, label="initial particle")
    p1 = plot!(xyz[:,1], xyz[:,3], aspect_ratio=:equal, label="particle after $n_iteration iterations", title="scattering cross section = $loss_here m")
    p2 = plot(1:length(loss_array), loss_array, xlabel="iteration #", ylabel="scattering cross section (m)")
    fig = plot(p1, p2, layout=(1, 2), size=(1200, 800))
    savefig(
        fig,
        "cache/iteration_particle_plots/maximizing_emissivity_07_12_2021__/particle_geom_iteration_$n_iteration.png"
    )
    # if (loss_here - loss_old) < 1e-6; break; end
    loss_old = loss_here
end


# This file is a part of BinaryCommitteeMachineFBP.jl. License is MIT: http://github.com/carlobaldassi/BinaryCommitteeMachineFBP.jl/LICENCE.md

module AtanhErf

export atanherf, batanherf

using StatsFuns
using SpecialFunctions
using JLD2
using Interpolations

const builddir = joinpath(dirname(@__FILE__), "..", "deps", "builds")
batanherf(x::Float64) = Float64(atanh(erf(big(x))))

let
    mm = 16.0
    st = 1e-4
    r = 1.0:st:mm
    rb = big.(first(r):step(r):last(r))

    interp_degree = Quadratic
    interp_boundary = Line
    interp_grid = OnGrid
    # interp_type = Interpolations.BSplineInterpolation{Float64,1,Vector{Float64},
    #                                                   BSpline{interp_degree{interp_boundary}},
    #                                                   OnGrid,0}

    interp_type = Interpolations.BSplineInterpolation{Float64,1,Array{Float64,1},
                                                      BSpline{interp_degree{interp_boundary{interp_grid}}},
                                                      Tuple{Base.Slice{UnitRange{Int64}}}}
    function getinp!()
        isdir(builddir) || mkdir(builddir)
        filename = joinpath(builddir, "atanherf_interp.max_$mm.step_$st.jld")
        if isfile(filename)
            @load filename inp
        else
            @info("Computing atanh(erf(x)) table, this may take a while...")
            inp = setprecision(BigFloat, 512) do
                interpolate!(Float64[atanh(erf(x)) for x in rb], BSpline(interp_degree(interp_boundary(interp_grid()))))
            end
            jldopen(filename, "w") do f
                write(f, "inp", inp)
            end
        end
        # @show interp_type
        # @show typeof(inp)
        # x = 0.0
        # @show inp[(x - first(r)) / step(r) + 1]::Float64
        return inp::interp_type
    end

    inp = getinp!()

    global atanherf_interp
    atanherf_interp(x::Float64) = inp((x - first(r)) / step(r) + 1)::Float64
end

function atanherf_largex(x::Float64)
    x² = x^2
    t = 1/x²

    return sign(x) * (2log(abs(x)) + log4π + 2x² +
                      t * @evalpoly(t,
                                    1,
                                    -1.25,
                                    3.0833333333333335,
                                    -11.03125,
                                    51.0125,
                                    -287.5260416666667,
                                    1906.689732142857,
                                    -14527.3759765625,
                                    125008.12543402778,
                                    -1.1990066259765625e6)
                     ) / 4
end

function atanherf(x::Float64)
    ax = abs(x)
    ax ≤ 2 && return atanh(erf(x))
    ax ≤ 15 && return sign(x) * atanherf_interp(ax)
    return atanherf_largex(x)
end

end


# https://adventofcode.com/2021/day/15
using AdventOfCode
using Graphs
using SimpleWeightedGraphs

input = split.(readlines("2021/data/day_15.txt"),"") |> Base.Fix1(reduce,hcat) .|> parse
function makegraph(mat)
    g=SimpleWeightedGraph(Graphs.SimpleGraphs.grid(size(mat)), 1)
    for edge in edges(g)
        w=mat[src(edge)]+mat[dst(edge)]
        add_edge!(g,src(edge),dst(edge),w)
    end
    g
end

function part_1(input)
    g=makegraph(input)
    ds=dijkstra_shortest_paths(g,1)
    (ds.dists[end]-input[begin]+input[end])÷2
end

function part_2(input)
    fullmap=(repeat(input,5,5).+
        repeat((0:4).+(0:4)',inner=size(input)).-1) .%9 .+1
    g=makegraph(fullmap)
    ds=dijkstra_shortest_paths(g,1)
    (ds.dists[end]-input[begin]+input[end])÷2
end


mutable struct NonNegGraphReg <: AbstractGraphReg
  L::AbstractMatrix{Float64}
  scale::Float64
  idxgraph::IndexGraph
end

#Retrieve the matrix component of the regularizer for use in initialization
matrix(g::NonNegGraphReg) = g.L

#
function NonNegGraphReg(IG::IndexGraph, scale::Float64=1.)
  L = laplacian_matrix(IG.graph)
  NonNegGraphReg(L, scale, IG)
end

function prox(g::NonNegGraphReg, Y::AbstractMatrix{Float64}, α::Number)
  #Y*(2α*g.scale*g.QL + eye(g.QL))⁻¹
  #g.QL is guaranteed to be sparse and symmetric positive definite
  #Factorize (2α*g.scale*g.QL + I)
  L = Symmetric((2α*g.scale)*g.L)
  max(A_ldiv_Bt(cholfact(L, shift=1.), Y)', 0)
end

function prox!(g::NonNegGraphReg, Y::AbstractMatrix{Float64}, α::Number)
  #Y*(2α*g.scale*g.QL + eye(g.QL))⁻¹
  #g.QL is guaranteed to be sparse and symmetric positive definite
  #Factorize (2α*g.scale*g.QL + I)
  L = Symmetric((2α*g.scale)*g.L)
  #invQLpI = cholfact(QL, shift=1.) \ eye(QL)
  #Y*invQLpI
  transpose!(Y, A_ldiv_Bt(cholfact(L, shift=1.), Y))
  Ymax = maximum(Y)
  clamp!(Y, 0, Ymax)
end

function evaluate(g::NonNegGraphReg, Y::AbstractMatrix{Float64})
  for yi in Y
    if yi < 0
      return Inf
    end
  end
  g.scale*sum((Y'*Y) .* g.L)
end

function embed(g::NonNegGraphReg, yidxs::Array)
  NonNegGraphReg(embed_graph(g.idxgraph, yidxs), g.scale)
end


# the grid data structure contains 37 members
# (6) Surface structures
# sgeo, and the 5 ids
# (17) Volume structures
# vgeo, and the 16 ids
# (2) Volume to Surface index
# vmap⁻, vmap⁺
# (1) Topology
# topology
# (3) Operators
# grid.D (differentiation matrix), grid.Imat (integration matrix?), grid.ω (quadrature weights)
# information (1)
# activedofs (active degrees of freedom)
# Domain Boundary Information (1)
# elemtobndy (element to boundary)
# MPI stuff (6)
# interiorelems, exteriorelems, nabrtovmaprecv, nabrtovmapsend, vmaprecv, vmapsendv

# sgeo, the first index is for the ids
# the second index are the gauss-lobatto entries
# the third index is the face
# the last index is the element
# the ids are: (1, n1id), (2 n2id), (3 n3id) (4, sMid), (5, vMIid)
# grid.sgeo[1,:,:,:] are normals in the n1 direction
# norm(grid.vgeo[:, grid.MIid, :][grid.vmap⁻] - grid.sgeo[5,:,:,:]) is zero
# x[grid.vmap⁻[10]] is connected to x[grid.vmap⁺[10]]

# vgeo, first index is guass-lobatto points
# second index is ids
# third index is elements
# the are are 16 ids
# 1-9 are metric terms
# 10 is the mass matrix
# 11 is the inverse mass matrix
# 12 is MHid (horizontal mass matrix?)
# 13-15 are x1 x2 x3
# 16 is the vertical volume jacobian
# grid.vgeo[:, grid.ξ1x1id, :]


qp_solver = optimizer_with_attributes(
    Ipopt.Optimizer,
    "print_level" => 0,
    "warm_start_init_point" => "yes",
)
optimizer_solver = optimizer_with_attributes(
    SQP.Optimizer,
    "external_optimizer" => qp_solver,
    "algorithm" => "SQP-TR",
    "OutputFlag" => 0,
)

model = Model(optimizer_solver)

@variable(model, X);
@variable(model, Y);
@objective(model, Min, X^2 + X);
@NLconstraint(model, X^2 - X == 2);
@NLconstraint(model, X * Y == 1);
@NLconstraint(model, X * Y >= 0);
@constraint(model, X >= -2);

JuMP.optimize!(model);

xsol = JuMP.value.(X)
ysol = JuMP.value.(Y)
status = termination_status(model)


# Add support for analytical derivatives of min and max using Heaviside function (taken from SymPy)
function differentiate(::SymbolParameter{:min}, args, wrt)
   k = 1
   derivative = "("*string(differentiate(args[k],wrt))*")"
   derivative *= "*heaviside(min("*replace(replace(string(args[[1:(k-1), (k+1):end]]), r"\{|\}", ""), ":","")*") - ($(args[k]))) + "
   for k in 2:length(args)
       derivative *= string(differentiate(args[k],wrt))
       derivative *= "*heaviside(min("*replace(replace(string(args[[1:(k-1), (k+1):end]]), r"\{|\}", ""), ":","")*") - ($(args[k]))) + "
   end
   parse(derivative[1:(end - 2)])
end

function differentiate(::SymbolParameter{:max}, args, wrt)
   k = 1
   derivative = "("*string(differentiate(args[k],wrt))*")"
   derivative *= "*heaviside(-max("*replace(replace(string(args[[1:(k-1), (k+1):end]]), r"\{|\}", ""), ":","")*") + ($(args[k]))) + "
   for k in 2:length(args)
       derivative *= string(differentiate(args[k],wrt))
       derivative *= "*heaviside(-max("*replace(replace(string(args[[1:(k-1), (k+1):end]]), r"\{|\}", ""), ":","")*") + ($(args[k]))) + "
   end
   parse(derivative[1:(end - 2)])
end

function differentiate(::SymbolParameter{:heaviside}, args, wrt)
   derivative = "("*string(differentiate(args[1],wrt))*")"
   derivative *= "*dirac("*replace(replace(string(args), r"\{|\}", ""), ":","")*")"
   parse(derivative)
end

differentiate(::SymbolParameter{:dirac}, args, wrt) = 0


# Toy problem to test the solver

using luteos

P = P3() # Polynomial order of solution

mesh   = Mesh2D( "square", P, N = 30, M = 30)
master = Master2D( P )

# source function
function funcS( p::Array{Float64} )
  return fill(1.0, size(p,1), 1)
end

function funcB( p::Array{Float64} )
  return fill(0.0, size(p,1), 1)
end

prob = CDR( "Example 3 - Poission", 1.0, [0.0,0.0], funcS, [1,1,1,1], true, [funcB, funcB, funcB, funcB] )

@time (uhath, uh, qh, uhathTri ) = hdgSolve( master, mesh, prob )

# write solution
writeTecplot( "blaCD.dat", prob, mesh, uh, qh )


using Distributions
using Parameters
using LightGraphs
using TransformVariables
using LogDensityProblems
using DynamicHMC
using ForwardDiff
using BenchmarkTools
using Plots

include("model.jl")
include("generate.jl")

S = 10

nicheweb = niche_network(S, 0.1)

heatmap(nicheweb.fw, legend = false)

m = model(nicheweb.fw)

heatmap(m.foodweb, legend = false)

θ = (n = nicheweb.n[m.reorder],
     r = nicheweb.r[m.reorder[(m.basal + 1):end]],
     c = nicheweb.c[m.reorder[(m.basal + 1):end]])

m(θ)

toθ = as((n = as(Array, as𝕀, m.S),
          r = as(Array, as𝕀, m.nonbasal),
          c = as(Array, as𝕀, m.nonbasal)))

lp_t = TransformedLogDensity(toθ, m)
lp∇ = ForwardDiffLogDensity(lp_t, chunk = ForwardDiff.Chunk{24}())

inits = inverse(toθ, θ)
logdensity(LogDensityProblems.ValueGradient, lp∇, inits)

chain, smplr = NUTS_init_tune_mcmc(lp∇, 500, max_depth = 10)

NUTS_statistics(chain)

posterior = transform.(Ref(lp∇.transformation), get_position.(chain))

n = mean(first, posterior)

foo = zip(posterior...) |> collect

scatter(θ.n, n)


@testset "Charlwood Problems" begin
    (x, ) = @variables x

    #= ::Package:: =#

    #= ::Title:: =#
    #=Kevin*Charlwood - Integration*on*Computer*Algebra*Systems*(2008)=#

    #= ::Subsection::Closed:: =#
    #=Problem #1=#

    @test_int [asin(x)*log(x), x, 8, -2*sqrt(1 - x^2) + atanh(sqrt(1 - x^2)) - x*asin(x)*(1 - log(x)) + sqrt(1 - x^2)*log(x), -2*sqrt(1 - x^2) - x*asin(x) + atanh(sqrt(1 - x^2)) + sqrt(1 - x^2)*log(x) + x*asin(x)*log(x)]

    #= ::Subsection::Closed:: =#
    #=Problem #2=#

    @test_int [x*asin(x)/sqrt(1 - x^2), x, 2, x - sqrt(1 - x^2)*asin(x)]

    #= ::Subsection::Closed:: =#
    #=Problem #3=#

    @test_int [asin(sqrt(x + 1) - sqrt(x)), x, -3, ((sqrt(x) + 3*sqrt(1 + x))*sqrt(-x + sqrt(x)*sqrt(1 + x)))/(4*sqrt(2)) - (3/8 + x)*asin(sqrt(x) - sqrt(1 + x))]

    #= ::Subsection::Closed:: =#
    #=Problem #4=#

    @test_int [log(1 + x*sqrt(1 + x^2)), x, -32, -2*x + sqrt(2*(1 + sqrt(5)))*atan(sqrt(-2 + sqrt(5))*(x + sqrt(1 + x^2))) - sqrt(2*(-1 + sqrt(5)))*atanh(sqrt(2 + sqrt(5))*(x + sqrt(1 + x^2))) + x*log(1 + x*sqrt(1 + x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #5=#

    @test_int [cos(x)^2/sqrt(cos(x)^4 + cos(x)^2 + 1), x, -5, x/3 + (1/3)*atan((cos(x)*(1 + cos(x)^2)*sin(x))/(1 + cos(x)^2*sqrt(1 + cos(x)^2 + cos(x)^4)))]

    #= ::Subsection::Closed:: =#
    #=Problem #6=#

    @test_int [tan(x)*sqrt(1 + tan(x)^4), x, 7, (-(1/2))*asinh(tan(x)^2) - atanh((1 - tan(x)^2)/(sqrt(2)*sqrt(1 + tan(x)^4)))/sqrt(2) + (1/2)*sqrt(1 + tan(x)^4)]

    #= ::Subsection::Closed:: =#
    #=Problem #7=#

    @test_int [tan(x)/sqrt(1 + sec(x)^3), x, 4, (-(2/3))*atanh(sqrt(1 + sec(x)^3))]

    #= ::Subsection::Closed:: =#
    #=Problem #8=#

    @test_int [sqrt(tan(x)^2 + 2*tan(x) + 2), x, 9, asinh(1 + tan(x)) - sqrt((1/2)*(1 + sqrt(5)))*atan((2*sqrt(5) - (5 + sqrt(5))*tan(x))/(sqrt(10*(1 + sqrt(5)))*sqrt(2 + 2*tan(x) + tan(x)^2))) - sqrt((1/2)*(-1 + sqrt(5)))*atanh((2*sqrt(5) + (5 - sqrt(5))*tan(x))/(sqrt(10*(-1 + sqrt(5)))*sqrt(2 + 2*tan(x) + tan(x)^2)))]

    #= ::Subsection::Closed:: =#
    #=Problem #9=#

    @test_int [sin(x)*atan(sqrt(sec(x) - 1)), x, 7, (1/2)*atan(sqrt(-1 + sec(x))) - atan(sqrt(-1 + sec(x)))*cos(x) + (1/2)*cos(x)*sqrt(-1 + sec(x))]

    #= ::Subsection::Closed:: =#
    #=Problem #10=#

    @test_int [x^3*ℯ^asin(x)/sqrt(1 - x^2), x, 5, (1/10)*ℯ^asin(x)*(3*x + x^3 - 3*sqrt(1 - x^2) - 3*x^2*sqrt(1 - x^2)), (3/10)*ℯ^asin(x)*x + (1/10)*ℯ^asin(x)*x^3 - (3/10)*ℯ^asin(x)*sqrt(1 - x^2) - (3/10)*ℯ^asin(x)*x^2*sqrt(1 - x^2)]

    #= ::Subsection::Closed:: =#
    #=Problem #11=#

    @test_int [(x*log(1 + x^2)*log(x + sqrt(1 + x^2)))/sqrt(1 + x^2), x, 7, 4*x - 2*atan(x) - x*log(1 + x^2) - 2*sqrt(1 + x^2)*log(x + sqrt(1 + x^2)) + sqrt(1 + x^2)*log(1 + x^2)*log(x + sqrt(1 + x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #12=#

    @test_int [atan(x + sqrt(1 - x^2)), x, -40, -(asin(x)/2) + (1/4)*sqrt(3)*atan((-1 + sqrt(3)*x)/sqrt(1 - x^2)) + (1/4)*sqrt(3)*atan((1 + sqrt(3)*x)/sqrt(1 - x^2)) - (1/4)*sqrt(3)*atan((-1 + 2*x^2)/sqrt(3)) + x*atan(x + sqrt(1 - x^2)) - (1/4)*atanh(x*sqrt(1 - x^2)) - (1/8)*log(1 - x^2 + x^4)]

    #= ::Subsection::Closed:: =#
    #=Problem #13=#

    @test_int [x*atan(x + sqrt(1 - x^2))/sqrt(1 - x^2), x, -32, -(asin(x)/2) + (1/4)*sqrt(3)*atan((-1 + sqrt(3)*x)/sqrt(1 - x^2)) + (1/4)*sqrt(3)*atan((1 + sqrt(3)*x)/sqrt(1 - x^2)) - (1/4)*sqrt(3)*atan((-1 + 2*x^2)/sqrt(3)) - sqrt(1 - x^2)*atan(x + sqrt(1 - x^2)) + (1/4)*atanh(x*sqrt(1 - x^2)) + (1/8)*log(1 - x^2 + x^4)]

    #= ::Subsection::Closed:: =#
    #=Problem #14=#

    @test_int [asin(x)/(1 + sqrt(1 - x^2)), x, 9, -((x*asin(x))/(1 + sqrt(1 - x^2))) + asin(x)^2/2 - log(1 + sqrt(1 - x^2)), -(asin(x)/x) + (sqrt(1 - x^2)*asin(x))/x + asin(x)^2/2 - atanh(sqrt(1 - x^2)) - log(x)]

    #= ::Subsection::Closed:: =#
    #=Problem #15=#

    @test_int [log(x + sqrt(1 + x^2))/(1 - x^2)^(3/2), x, 3, (-(1/2))*asin(x^2) + (x*log(x + sqrt(1 + x^2)))/sqrt(1 - x^2)]

    #= ::Subsection::Closed:: =#
    #=Problem #16=#

    @test_int [asin(x)/(1 + x^2)^(3/2), x, 3, (x*asin(x))/sqrt(1 + x^2) - asin(x^2)/2]

    #= ::Subsection::Closed:: =#
    #=Problem #17=#

    @test_int [log(x + sqrt(x^2 - 1))/(1 + x^2)^(3/2), x, 3, (-(1/2))*acosh(x^2) + (x*log(x + sqrt(-1 + x^2)))/sqrt(1 + x^2)]

    #= ::Subsection::Closed:: =#
    #=Problem #18=#

    @test_int [log(x)/(x^2*sqrt(x^2 - 1)), x, 4, sqrt(-1 + x^2)/x - atanh(x/sqrt(-1 + x^2)) + (sqrt(-1 + x^2)*log(x))/x]

    #= ::Subsection::Closed:: =#
    #=Problem #19=#

    @test_int [sqrt(1 + x^3)/x, x, 4, (2*sqrt(1 + x^3))/3 - (2/3)*atanh(sqrt(1 + x^3))]

    #= ::Subsection::Closed:: =#
    #=Problem #20=#

    @test_int [x*log(x + sqrt(x^2 - 1))/sqrt(x^2 - 1), x, 2, -x + sqrt(-1 + x^2)*log(x + sqrt(-1 + x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #21=#

    @test_int [x^3*(asin(x)/sqrt(1 - x^4)), x, 5, (1/4)*x*sqrt(1 + x^2) - (1/2)*sqrt(1 - x^4)*asin(x) + asinh(x)/4]

    #= ::Subsection::Closed:: =#
    #=Problem #22=#

    @test_int [x^3*(asec(x)/sqrt(x^4 - 1)), x, 7, -(sqrt(-1 + x^4)/(2*sqrt(1 - 1/x^2)*x)) + (1/2)*sqrt(-1 + x^4)*asec(x) + (1/2)*atanh((sqrt(1 - 1/x^2)*x)/sqrt(-1 + x^4)), -(sqrt(-1 + x^4)/(2*sqrt(1 - 1/x^2)*x)) + (1/2)*sqrt(-1 + x^4)*asec(x) + (sqrt(1 - x^2)*atan(sqrt(-1 + x^4)/sqrt(1 - x^2)))/(2*sqrt(1 - 1/x^2)*x)]

    #= ::Subsection::Closed:: =#
    #=Problem #23=#

    @test_int [x*atan(x)*log(x + sqrt(1 + x^2))/sqrt(1 + x^2), x, 4, (-x)*atan(x) + (1/2)*log(1 + x^2) + sqrt(1 + x^2)*atan(x)*log(x + sqrt(1 + x^2)) - (1/2)*log(x + sqrt(1 + x^2))^2]

    #= ::Subsection::Closed:: =#
    #=Problem #24=#

    @test_int [x*log(1 + sqrt(1 - x^2))/sqrt(1 - x^2), x, 5, sqrt(1 - x^2) - log(1 + sqrt(1 - x^2)) - sqrt(1 - x^2)*log(1 + sqrt(1 - x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #25=#

    @test_int [x*log(x + sqrt(1 + x^2))/sqrt(1 + x^2), x, 2, -x + sqrt(1 + x^2)*log(x + sqrt(1 + x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #26=#

    @test_int [x*log(x + sqrt(1 - x^2))/sqrt(1 - x^2), x, 18, sqrt(1 - x^2) + atanh(sqrt(2)*x)/sqrt(2) - atanh(sqrt(2)*sqrt(1 - x^2))/sqrt(2) - sqrt(1 - x^2)*log(x + sqrt(1 - x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #27=#

    @test_int [log(x)/(x^2*sqrt(1 - x^2)), x, 3, -(sqrt(1 - x^2)/x) - asin(x) - (sqrt(1 - x^2)*log(x))/x]

    #= ::Subsection::Closed:: =#
    #=Problem #28=#

    @test_int [x*atan(x)/sqrt(1 + x^2), x, 2, -asinh(x) + sqrt(1 + x^2)*atan(x)]

    #= ::Subsection::Closed:: =#
    #=Problem #29=#

    @test_int [atan(x)/(x^2*sqrt(1 - x^2)), x, 7, -((sqrt(1 - x^2)*atan(x))/x) - atanh(sqrt(1 - x^2)) + sqrt(2)*atanh(sqrt(1 - x^2)/sqrt(2))]

    #= ::Subsection::Closed:: =#
    #=Problem #30=#

    @test_int [x*atan(x)/sqrt(1 - x^2), x, 5, -asin(x) - sqrt(1 - x^2)*atan(x) + sqrt(2)*atan((sqrt(2)*x)/sqrt(1 - x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #31=#

    @test_int [atan(x)/(x^2*sqrt(1 + x^2)), x, 4, -((sqrt(1 + x^2)*atan(x))/x) - atanh(sqrt(1 + x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #32=#

    @test_int [asin(x)/(x^2*sqrt(1 - x^2)), x, 2, -((sqrt(1 - x^2)*asin(x))/x) + log(x)]

    #= ::Subsection::Closed:: =#
    #=Problem #33=#

    @test_int [x*log(x)/sqrt(x^2 - 1), x, 5, -sqrt(-1 + x^2) + atan(sqrt(-1 + x^2)) + sqrt(-1 + x^2)*log(x)]

    #= ::Subsection::Closed:: =#
    #=Problem #34=#

    @test_int [log(x)/(x^2*sqrt(1 + x^2)), x, 3, -(sqrt(1 + x^2)/x) + asinh(x) - (sqrt(1 + x^2)*log(x))/x]

    #= ::Subsection::Closed:: =#
    #=Problem #35=#

    @test_int [x*asec(x)/sqrt(x^2 - 1), x, 2, sqrt(-1 + x^2)*asec(x) - (x*log(x))/sqrt(x^2)]

    #= ::Subsection::Closed:: =#
    #=Problem #36=#

    @test_int [x*log(x)/sqrt(1 + x^2), x, 5, -sqrt(1 + x^2) + atanh(sqrt(1 + x^2)) + sqrt(1 + x^2)*log(x)]

    #= ::Subsection::Closed:: =#
    #=Problem #37=#

    @test_int [sin(x)/(1 + sin(x)^2), x, 2, -(atanh(cos(x)/sqrt(2))/sqrt(2))]

    #= ::Subsection::Closed:: =#
    #=Problem #38=#

    @test_int [(1 + x^2)/((1 - x^2)*sqrt(1 + x^4)), x, 2, (1/sqrt(2))*atanh(sqrt(2)*(x/sqrt(1 + x^4)))]

    #= ::Subsection::Closed:: =#
    #=Problem #39=#

    @test_int [(1 - x^2)/((1 + x^2)*sqrt(1 + x^4)), x, 2, atan((sqrt(2)*x)/sqrt(1 + x^4))/sqrt(2)]

    #= ::Subsection::Closed:: =#
    #=Problem #40=#

    @test_int [log(sin(x))/(1 + sin(x)), x, 4, -x - atanh(cos(x)) - (cos(x)*log(sin(x)))/(1 + sin(x))]

    #= ::Subsection::Closed:: =#
    #=Problem #41=#

    @test_int [log(sin(x))*sqrt(1 + sin(x)), x, 6, (4*cos(x))/sqrt(1 + sin(x)) - (2*cos(x)*log(sin(x)))/sqrt(1 + sin(x)) - 4*atanh(cos(x)/sqrt(1 + sin(x)))]

    #= ::Subsection::Closed:: =#
    #=Problem #42=#

    @test_int [sec(x)/sqrt(sec(x)^4 - 1), x, -5, -(atanh((cos(x)*cot(x)*sqrt(sec(x)^4 - 1))/sqrt(2))/sqrt(2))]

    #= ::Subsection::Closed:: =#
    #=Problem #43=#

    @test_int [tan(x)/sqrt(1 + tan(x)^4), x, 4, -(atanh((1 - tan(x)^2)/(sqrt(2)*sqrt(1 + tan(x)^4)))/(2*sqrt(2)))]

    #= ::Subsection::Closed:: =#
    #=Problem #44=#

    @test_int [sin(x)/sqrt(1 - sin(x)^6), x, 4, atanh((sqrt(3)*cos(x)*(1 + sin(x)^2))/(2*sqrt(1 - sin(x)^6)))/(2*sqrt(3)), atanh((cos(x)*(6 - 3*cos(x)^2))/(2*sqrt(3)*sqrt(3*cos(x)^2 - 3*cos(x)^4 + cos(x)^6)))/(2*sqrt(3))]

    #= ::Subsection::Closed:: =#
    #=Problem #45=#

    @test_int [sqrt(sqrt(sec(x) + 1) - sqrt(sec(x) - 1)), x, -1, sqrt(2)*(sqrt(-1 + sqrt(2))*atan((sqrt(-2 + 2*sqrt(2))*(-sqrt(2) - sqrt(-1 + sec(x)) + sqrt(1 + sec(x))))/(2*sqrt(-sqrt(-1 + sec(x)) + sqrt(1 + sec(x))))) - sqrt(1 + sqrt(2))*atan((sqrt(2 + 2*sqrt(2))*(-sqrt(2) - sqrt(-1 + sec(x)) + sqrt(1 + sec(x))))/(2*sqrt(-sqrt(-1 + sec(x)) + sqrt(1 + sec(x))))) - sqrt(1 + sqrt(2))*atanh((sqrt(-2 + 2*sqrt(2))*sqrt(-sqrt(-1 + sec(x)) + sqrt(1 + sec(x))))/(sqrt(2) - sqrt(-1 + sec(x)) + sqrt(1 + sec(x)))) + sqrt(-1 + sqrt(2))*atanh((sqrt(2 + 2*sqrt(2))*sqrt(-sqrt(-1 + sec(x)) + sqrt(1 + sec(x))))/(sqrt(2) - sqrt(-1 + sec(x)) + sqrt(1 + sec(x)))))*cot(x)*sqrt(-1 + sec(x))*sqrt(1 + sec(x))]

    #= ::Subsection::Closed:: =#
    #=Problem #46=#

    @test_int [x*log(x^2 + 1)*atan(x)^2, x, 13, 3*x*atan(x) - (3*atan(x)^2)/2 - (1/2)*x^2*atan(x)^2 - (3/2)*log(1 + x^2) - x*atan(x)*log(1 + x^2) + (1/2)*(1 + x^2)*atan(x)^2*log(1 + x^2) + (1/4)*log(1 + x^2)^2]

    #= ::Subsection::Closed:: =#
    #=Problem #47=#

    @test_int [atan(x*sqrt(1 + x^2)), x, 12, x*atan(x*sqrt(1 + x^2)) + (1/2)*atan(sqrt(3) - 2*sqrt(1 + x^2)) - (1/2)*atan(sqrt(3) + 2*sqrt(1 + x^2)) - (1/4)*sqrt(3)*log(2 + x^2 - sqrt(3)*sqrt(1 + x^2)) + (1/4)*sqrt(3)*log(2 + x^2 + sqrt(3)*sqrt(1 + x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #48=#

    @test_int [atan(sqrt(x + 1) - sqrt(x)), x, 6, sqrt(x)/2 + (1 + x)*atan(sqrt(1 + x) - sqrt(x)), sqrt(x)/2 + (pi*x)/4 - atan(sqrt(x))/2 - (1/2)*x*atan(sqrt(x))]

    #= ::Subsection::Closed:: =#
    #=Problem #49=#

    @test_int [asin(x/sqrt(1 - x^2)), x, 4, x*asin(x/sqrt(1 - x^2)) + atan(sqrt(1 - 2*x^2))]

    #= ::Subsection::Closed:: =#
    #=Problem #50=#

    @test_int [atan(x*sqrt(1 - x^2)), x, 6, x*atan(x*sqrt(1 - x^2)) - sqrt((1/2)*(1 + sqrt(5)))*atan(sqrt((1/2)*(1 + sqrt(5)))*sqrt(1 - x^2)) + sqrt((1/2)*(-1 + sqrt(5)))*atanh(sqrt((1/2)*(-1 + sqrt(5)))*sqrt(1 - x^2)), (-sqrt(2/(-1 + sqrt(5))))*atan(sqrt(2/(-1 + sqrt(5)))*sqrt(1 - x^2)) + x*atan(x*sqrt(1 - x^2)) + sqrt(2/(1 + sqrt(5)))*atanh(sqrt(2/(1 + sqrt(5)))*sqrt(1 - x^2))]
    end

test/0 Independent test suites/Charlwood Problems.jl


# Randomized matrix initialization

function randinit(m::Int,
                  n::Int,
                  type::String="Gaussian")
    if type == "Gaussian"
        return randn(Float64, (m,n))
    elseif type == "Uniform"
        return rand(Float64, (m, n))
    elseif type == "Rademacher"
        return -1
    end
end