# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/PDEAssembler.jl/blob/master/LICENSE

# Poisson's problem as example

struct Poisson <: FieldProblem end

FEMBase.get_unknown_field_name(::Problem{Poisson}) = "u"

function FEMBase.assemble_elements!(problem::Problem{Poisson},
                                    assembly::Assembly,
                                    elements::Vector{Element{E}},
                                    time::Float64) where E

    bi = BasisInfo(E)
    ndofs = length(bi)
    Ke = zeros(ndofs, ndofs)
    fe = zeros(ndofs)

    for element in elements
        fill!(Ke, 0.0)
        fill!(fe, 0.0)
        for ip in get_integration_points(element)
            J, detJ, N, dN = element_info!(bi, element, ip, time)
            k = 1.0
            if haskey(element, "coefficient")
                k = element("coefficient", ip, time)
            end
            Ke += ip.weight * k*dN'*dN * detJ
            if haskey(element, "source")
                f = element("source", ip, time)
                fe += ip.weight * N'*f * detJ
            end
        end
        gdofs = get_gdofs(problem, element)
        add!(assembly.K, gdofs, gdofs, Ke)
        add!(assembly.f, gdofs, fe)
    end

    return nothing

end

function FEMBase.assemble_elements!(problem::Problem{Poisson},
                                    assembly::Assembly,
                                    elements::Vector{Element{E}},
                                    time::Float64) where E<:Union{Seg2,Seg3}

    bi = BasisInfo(E)
    ndofs = length(bi)
    Ce = zeros(ndofs, ndofs)
    fe = zeros(ndofs)
    ge = zeros(ndofs)

    for element in elements
        fill!(Ce, 0.0)
        fill!(fe, 0.0)
        fill!(ge, 0.0)
        for ip in get_integration_points(element)
            J, detJ, N, dN = element_info!(bi, element, ip, time)
            if haskey(element, "flux")
                f = element("flux", ip, time)
                fe += ip.weight * N'*f * detJ
            end
            if haskey(element, "fixed u")
                f = element("fixed u", ip, time)
                Ce += ip.weight * N'*N * detJ
                ge += ip.weight * N'*f * detJ
            end
        end
        gdofs = get_gdofs(problem, element)
        add!(assembly.C1, gdofs, gdofs, Ce')
        add!(assembly.C2, gdofs, gdofs, Ce)
        add!(assembly.f, gdofs, fe)
        add!(assembly.g, gdofs, ge)
    end

    return nothing

end

"""
    get_unit_square(nel_x=10, nel_y=10; lx=1.0, ly=1.0)

Return elements and boundary elements for a standard test problem, unit square.
"""
function get_unit_square(nel_x=20, nel_y=20; lx=1.0, ly=1.0)

    nnodes_x = nel_x+1
    nnodes_y = nel_y+1
    nnode = nnodes_x*nnodes_y
    nodemap = reshape(1:nnode, nnodes_x, nnodes_y)
    nodes_1 = vec(nodemap[1:nnodes_x-1, 1:nnodes_y-1])
    nodes_2 = vec(nodemap[2:nnodes_x, 1:nnodes_y-1])
    nodes_3 = vec(nodemap[2:nnodes_x, 2:nnodes_y])
    nodes_4 = vec(nodemap[1:nnodes_x-1, 2:nnodes_y])

    X = Dict{Int64, Vector{Float64}}()

    # Create nodes
    nid = 1
    for y in linspace(0, ly, nnodes_y)
        for x in linspace(0, lx, nnodes_x)
            X[nid] = [x, y]
            nid += 1
        end
    end

    # Create elements for volume
    field_elements = []
    for c in zip(nodes_1, nodes_2, nodes_3, nodes_4)
        push!(field_elements, Element(Quad4, collect(c)))
    end

    # add boundary elements to the left side of domain
    boundary_elements = []
    nids = 1:nel_x+1:(nel_x+1)*(nel_y+1) # LEFT and RIGHT
    for c in zip(nids[1:end-1], nids[2:end])
        connectivity = collect(c)
        push!(boundary_elements, Element(Seg2, connectivity))
        push!(boundary_elements, Element(Seg2, connectivity+nel_x))
    end
    nids = 1:nel_x+1 # BOTTOM and TOP
    for c in zip(nids[1:end-1], nids[2:end])
        connectivity = collect(c)
        push!(boundary_elements, Element(Seg2, connectivity))
        push!(boundary_elements, Element(Seg2, connectivity+(nel_x+1)*nel_y))
    end

    update!(field_elements, "geometry", X)
    update!(boundary_elements, "geometry", X)

    return field_elements, boundary_elements
end


#
# Linear Algebra of order 2 Tensors
#
# Even though DenseTensor{_,2} is strided
# and passable to BLAS/LAPACK, it cannot
# be made <: StridedArray
export eigs,
       truncerror,
       entropy

function Base.:*(T1::Tensor{ElT1,2,StoreT1,IndsT1},
                 T2::Tensor{ElT2,2,StoreT2,IndsT2}) where
                                       {ElT1,StoreT1<:Dense,IndsT1,
                                        ElT2,StoreT2<:Dense,IndsT2}
  RM = matrix(T1)*matrix(T2)
  indsR = IndsT1(ind(T1,1),ind(T2,2))
  return Tensor(Dense(vec(RM)),indsR)
end

function LinearAlgebra.exp(T::DenseTensor{ElT,2}) where {ElT}
  expTM = exp(matrix(T))
  return Tensor(Dense(vec(expTM)),inds(T))
end

function expHermitian(T::DenseTensor{ElT,2}) where {ElT}
  # exp(::Hermitian/Symmetric) returns Hermitian/Symmetric,
  # so extract the parent matrix
  expTM = parent(exp(Hermitian(matrix(T))))
  return Tensor(Dense(vec(expTM)),inds(T))
end

"""
  Spectrum
contains the (truncated) density matrix eigenvalue spectrum which is computed during a
decomposition done by `svd` or `eigenHermitian`. In addition stores the truncation error.
"""
struct Spectrum
  eigs::Vector{Float64}
  truncerr::Float64
end

eigs(s::Spectrum) = s.eigs
truncerror(s::Spectrum) = s.truncerr

function entropy(s::Spectrum)
  S = 0.0
  for p in eigs(s)
    p > 1e-13 && (S -= p*log(p))
  end
  return S
end

#svd of an order-2 tensor
function LinearAlgebra.svd(T::DenseTensor{ElT,2,IndsT};
                           kwargs...) where {ElT,IndsT}
  maxdim::Int = get(kwargs,:maxdim,minimum(dims(T)))
  mindim::Int = get(kwargs,:mindim,1)
  cutoff::Float64 = get(kwargs,:cutoff,0.0)
  absoluteCutoff::Bool = get(kwargs,:absoluteCutoff,false)
  doRelCutoff::Bool = get(kwargs,:doRelCutoff,true)
  fastSVD::Bool = get(kwargs,:fastSVD,false)

  if fastSVD
    MU,MS,MV = svd(matrix(T))
  else
    MU,MS,MV = svd_recursive(matrix(T))
  end
  conj!(MV)

  P = MS.^2
  truncerr,_ = truncate!(P;mindim=mindim,
                         maxdim=maxdim,
                         cutoff=cutoff,
                         absoluteCutoff=absoluteCutoff,
                         doRelCutoff=doRelCutoff)
  spec = Spectrum(P,truncerr)
  dS = length(P)
  if dS < length(MS)
    MU = MU[:,1:dS]
    resize!(MS,dS)
    MV = MV[:,1:dS]
  end

  # Make the new indices to go onto U and V
  u = eltype(IndsT)(dS)
  v = eltype(IndsT)(dS)
  Uinds = IndsT((ind(T,1),u))
  Sinds = IndsT((u,v))
  Vinds = IndsT((ind(T,2),v))
  U = Tensor(Dense(vec(MU)),Uinds)
  S = Tensor(Diag(MS),Sinds)
  V = Tensor(Dense(vec(MV)),Vinds)
  return U,S,V,spec
end

function eigenHermitian(T::DenseTensor{ElT,2,IndsT};
                        kwargs...) where {ElT,IndsT}
  ispossemidef::Bool = get(kwargs,:ispossemidef,false)
  maxdim::Int = get(kwargs,:maxdim,minimum(dims(T)))
  mindim::Int = get(kwargs,:mindim,1)
  cutoff::Float64 = get(kwargs,:cutoff,0.0)
  absoluteCutoff::Bool = get(kwargs,:absoluteCutoff,false)
  doRelCutoff::Bool = get(kwargs,:doRelCutoff,true)

  DM,UM = eigen(Hermitian(matrix(T)))

  # Sort by largest to smallest eigenvalues
  p = sortperm(DM; rev = true)
  DM = DM[p]
  UM = UM[:,p]

  if ispossemidef
    truncerr,_ = truncate!(DM;maxdim=maxdim,
                           cutoff=cutoff,
                           absoluteCutoff=absoluteCutoff,
                           doRelCutoff=doRelCutoff)
    dD = length(DM)
    if dD < size(UM,2)
      UM = UM[:,1:dD]
    end
  else
    dD = length(DM)
    truncerr = 0.0
  end
  spec = Spectrum(DM,truncerr)

  # Make the new indices to go onto U and V
  u = eltype(IndsT)(dD)
  v = eltype(IndsT)(dD)
  Uinds = IndsT((ind(T,1),u))
  Dinds = IndsT((u,v))
  U = Tensor(Dense(vec(UM)),Uinds)
  D = Tensor(Diag(DM),Dinds)
  return U,D,spec
end

function LinearAlgebra.qr(T::DenseTensor{ElT,2,IndsT}) where {ElT,
                                                              IndsT}
  # TODO: just call qr on T directly (make sure
  # that is fast)
  QM,RM = qr(matrix(T))
  # Make the new indices to go onto Q and R
  q,r = inds(T)
  q = dim(q) < dim(r) ? sim(q) : sim(r)
  Qinds = IndsT((ind(T,1),q))
  Rinds = IndsT((q,ind(T,2)))
  Q = Tensor(Dense(vec(Matrix(QM))),Qinds)
  R = Tensor(Dense(vec(RM)),Rinds)
  return Q,R
end

function polar(T::DenseTensor{ElT,2,IndsT}) where {ElT,IndsT}
  QM,RM = polar(matrix(T))
  dim = size(QM,2)
  # Make the new indices to go onto Q and R
  q = eltype(IndsT)(dim)
  # TODO: use push/pushfirst instead of a constructor
  # call here
  Qinds = IndsT((ind(T,1),q))
  Rinds = IndsT((q,ind(T,2)))
  Q = Tensor(Dense(vec(QM)),Qinds)
  R = Tensor(Dense(vec(RM)),Rinds)
  return Q,R
end


function compute_update0!(H2, dHdtau, H, min_dxy2, _dx, _dy, dx, dy)
  @inbounds @fastmath for iy = 1:size(dHdtau, 2)
    for ix = 1:size(dHdtau, 1)
      dHdtau[ix, iy] =
        (
          -((
            -((H[(ix+1)+1, iy+1] - H[ix+1, iy+1])) * _dx -
            -((H[ix+1, iy+1] - H[ix, iy+1])) * _dx
          )) * _dx -
          (
            -((H[ix+1, (iy+1)+1] - H[ix+1, iy+1])) * _dy -
            -((H[ix+1, iy+1] - H[ix+1, iy])) * _dy
          ) * _dy
        ) + 0.6 * dHdtau[ix, iy]
      H2[ix+1, iy+1] = H[ix+1, iy+1] + min_dxy2 / 4.1 * dHdtau[ix, iy]  # sets the BC as H[1]=H[end]=0
    end
  end
  return
end
function compute_update1!(H2, dHdtau, H, min_dxy2, _dx, _dy, dx, dy)
  @turbo for iy = 1:size(dHdtau, 2)
    for ix = 1:size(dHdtau, 1)
      dHdtau[ix, iy] =
        (
          -((
            -((H[(ix+1)+1, iy+1] - H[ix+1, iy+1])) * _dx -
            -((H[ix+1, iy+1] - H[ix, iy+1])) * _dx
          )) * _dx -
          (
            -((H[ix+1, (iy+1)+1] - H[ix+1, iy+1])) * _dy -
            -((H[ix+1, iy+1] - H[ix+1, iy])) * _dy
          ) * _dy
        ) + 0.6 * dHdtau[ix, iy]
      H2[ix+1, iy+1] = H[ix+1, iy+1] + min_dxy2 / 4.1 * dHdtau[ix, iy]  # sets the BC as H[1]=H[end]=0
    end
  end
  return
end

@testset "conv" begin
  lx, ly = 10.0, 10.0   # domain size
  nx, ny = 12, 12     # number of grid points
  nt = 1e3          # max number of iterations
  dx, dy = lx / nx, ly / ny
  xc, yc = LinRange(dx / 2, lx - dx / 2, nx), LinRange(dy / 2, ly - dy / 2, ny)
  dHdtau = zeros(nx - 2, ny - 2)
  dHdtau2 = zeros(nx - 2, ny - 2)
  H = exp.(.-(xc .- lx / 2) .^ 2 .- (yc .- ly / 2)' .^ 2)
  H1 = copy(H)
  H2 = copy(H)
  min_dxy2 = min(dx, dy)^2
  _dx, _dy = 1.0 / dx, 1.0 / dy
  compute_update0!(H1, dHdtau, H, min_dxy2, _dx, _dy, dx, dy)
  compute_update1!(H2, dHdtau2, H, min_dxy2, _dx, _dy, dx, dy)
  @test H1 ≈ H2
  @test dHdtau ≈ dHdtau2
end


"""
A multilayer perceptron.
"""
module MultilayerPerceptron

include("activation.jl")
include("loss.jl")
include("layer.jl")
include("util.jl")

export MLP, fit!, predict

struct MLP
    layers::Vector{Layer}
end

function MLP(layers::Vector{Tuple{Int, Int}})
    layers = [Layer(sz) for sz in layers]
    MLP(layers)
end

function fit!(
    mlp::MLP,
    train_x::AbstractMatrix{Float64}, train_y::AbstractMatrix,
    ;
    loss_fn::Function = mse,
    learning_rate::Float64 = 0.001,
    batch_size::Int = 1000,
    n_epochs::Int = 10,
    reporting_interval = 1000,
)
    (_, n) = size(train_x)
    batch_iter = Iterators.partition(1:n, batch_size)
    n_batches = 0
    ewma_loss = nothing  # Exponentially weighted moving average
    α = min(8 / reporting_interval, 1.0)  # EWMA α
    for epoch in 1:n_epochs
        for cols in batch_iter
            batch_x = @view train_x[:, cols]
            batch_y = @view train_y[:, cols]
            for l in mlp.layers
                batch_x = forward!(l, batch_x)
            end
            batch_ŷ = batch_x

            (loss, ∂e_over_∂y) = loss_fn(batch_ŷ, batch_y)
            ewma_loss = isnothing(ewma_loss) ? loss : loss * α + ewma_loss * (1.0 - α)
            if (n_batches += 1) % reporting_interval == 0
                println("#$n_batches: $ewma_loss")
            end

            for l in Iterators.reverse(mlp.layers)
                ∂e_over_∂y = backward!(l, ∂e_over_∂y, learning_rate)
            end
        end
    end
end

function predict(mlp::MLP, test_x::AbstractMatrix{Float64})::Matrix{Float64}
    x = test_x
    for l in mlp.layers
        x = forward!(l, x; training=false)
    end
    x
end

end


using TransformVariables, LogDensityProblems, DynamicHMC, MCMCDiagnostics, Parameters,
    Distributions, Statistics, StatsFuns, ForwardDiff

struct NUTS_result{T}
    chain::Vector{NUTS_Transition{Vector{Float64},Float64}}
    transformation
    samples::Vector{T}
    tuning
end

Base.show(io::IO, n::NUTS_result) = begin
    println(io, "NUTS_result with samples:")
    println(IOContext(io, :limit => true, :compact => true), n.samples)
end

export nuts

function nuts(m :: Model; kwargs...)
    ℓ = makeLogdensity(m)

    result = NUTS_result{}
    t = xform(m; data...)
    P = TransformedLogDensity(t, ℓ)
    ∇P = ADgradient(:ForwardDiff, P)
    chain, tuning = NUTS_init_tune_mcmc(∇P, 1000);
    samples = TransformVariables.transform.(Ref(parent(∇P).transformation), get_position.(chain));
    NUTS_result(chain, t, samples, tuning)
end

function nuts(m :: Model, data :: NamedTuple; kwargs...)
    f1 = makeLogdensity(m)
    ℓ(pars) = f1(merge(data,pars))

    result = NUTS_result{}
    t = xform(m; data...)
    P = TransformedLogDensity(t, ℓ)
    ∇P = ADgradient(:ForwardDiff, P)
    chain, tuning = NUTS_init_tune_mcmc(∇P, 1000);
    samples = TransformVariables.transform.(Ref(parent(∇P).transformation), get_position.(chain));
    NUTS_result(chain, t, samples, tuning)
end


"""
    NoncentralChisq(ν, λ)

The *noncentral chi-squared distribution* with `ν` degrees of freedom and noncentrality parameter `λ` has the probability density function

```math
f(x; \\nu, \\lambda) = \\frac{1}{2} e^{-(x + \\lambda)/2} \\left( \\frac{x}{\\lambda} \\right)^{\\nu/4-1/2} I_{\\nu/2-1}(\\sqrt{\\lambda x}), \\quad x > 0
```

It is the distribution of the sum of squares of `ν` independent [`Normal`](@ref) variates with individual means ``\\mu_i`` and

```math
\\lambda = \\sum_{i=1}^\\nu \\mu_i^2
```

```julia
NoncentralChisq(ν, λ)     # Noncentral chi-squared distribution with ν degrees of freedom and noncentrality parameter λ

params(d)    # Get the parameters, i.e. (ν, λ)
```

External links

* [Noncentral chi-squared distribution on Wikipedia](https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution)
"""
struct NoncentralChisq{T<:Real} <: ContinuousUnivariateDistribution
    ν::T
    λ::T
    NoncentralChisq{T}(ν::T, λ::T) where {T <: Real} = new{T}(ν, λ)
end

function NoncentralChisq(ν::T, λ::T) where {T <: Real}
    @check_args(NoncentralChisq, ν > zero(ν) && λ >= zero(λ))
    return NoncentralChisq{T}(ν, λ)
end

NoncentralChisq(ν::T, λ::T, ::NoArgCheck) where {T<:Real} = NoncentralChisq{T}(ν, λ)
NoncentralChisq(ν::Real, λ::Real) = NoncentralChisq(promote(ν, λ)...)
NoncentralChisq(ν::Integer, λ::Integer) = NoncentralChisq(float(ν), float(λ))

@distr_support NoncentralChisq 0.0 Inf

#### Conversions

function convert(::Type{NoncentralChisq{T}}, ν::S, λ::S) where {T <: Real, S <: Real}
    NoncentralChisq(T(ν), T(λ))
end
function convert(::Type{NoncentralChisq{T}}, d::NoncentralChisq{S}) where {T <: Real, S <: Real}
    NoncentralChisq(T(d.ν), T(d.λ), NoArgCheck())
end

### Parameters

params(d::NoncentralChisq) = (d.ν, d.λ)
@inline partype(d::NoncentralChisq{T}) where {T<:Real} = T

### Statistics

mean(d::NoncentralChisq) = d.ν + d.λ
var(d::NoncentralChisq) = 2(d.ν + 2d.λ)
skewness(d::NoncentralChisq) = 2sqrt2*(d.ν + 3d.λ)/sqrt(d.ν + 2d.λ)^3
kurtosis(d::NoncentralChisq) = 12(d.ν + 4d.λ)/(d.ν + 2d.λ)^2

function mgf(d::NoncentralChisq, t::Real)
    exp(d.λ * t/(1 - 2t))*(1 - 2t)^(-d.ν/2)
end

function cf(d::NoncentralChisq, t::Real)
    cis(d.λ * t/(1 - 2im*t))*(1 - 2im*t)^(-d.ν/2)
end

### Evaluation & Sampling

@_delegate_statsfuns NoncentralChisq nchisq ν λ

# TODO: remove RFunctions dependency
@rand_rdist(NoncentralChisq)
rand(d::NoncentralChisq) = StatsFuns.RFunctions.nchisqrand(d.ν, d.λ)


function powerlaw(x, k, G)

    # set negatives to zero -
    x = max.(0.0, x)

    # get system dimension -
    ℛ = length(k)
    ℳ = length(x)

    # initialize the rate array -
    rate_array = Array{Float64,1}(undef,ℛ)

    for i ∈ 1:ℛ

        # tmp -
        tmp_value = 1.0
        for j ∈ 1:ℳ
            tmp_value = tmp_value*(x[j])^(G[j,i])
        end

        # store the rate value -
        rate_array[i] = k[i]*tmp_value
    end

    # return -
    return rate_array
end


module Binless
include("../src/binless.jl")
using Distributions

function entropy()
    srand(1234)
    x = randn(1000)
    E = entropy(x)
    Base.Test.@test_approx_eq E 2.0082391501948837
    println("Test passed. E = $E")
end

function entropy2()
    srand(1234)
    MR = MultivariateNormal(zeros(5),eye(5))
    X = rand(MR,1000)
    E = entropy(X)
    Base.Test.@test_approx_eq E 10.211657210460308
    println("Test passed. E = $E")
end

end #Module


""" Useful tools for analysing and comparing between multiple PEtriNet models
"""

module ModelComparison

using AlgebraicPetri
using Catlab
using Catlab.CategoricalAlgebra
import Catlab.CategoricalAlgebra: Subobject

export petri_homomorphisms, compare, PetriSubobject

""" petri_homomorphisms
Calculates all homomorphisms between two PetriNet models and returns an array
of ACSetTransformations that describe these homomorphisms.

NOTE:
This function does restrict to homomorphisms which preserve the transition
signatures (number of input/output wires).
"""
function petri_homomorphisms(p1::AbstractPetriNet, p2::AbstractPetriNet; kw...)
  results = ACSetTransformation[]
  sigsTS = Set{Vector{Int64}}()
  homs = homomorphisms(PetriNet(p1), PetriNet(p2); kw...)
  for transform in homs
    if all([length(inputs(p1, t)) == length(inputs(p2, transform.components[:T](t))) &&
            length(outputs(p1, t)) == length(outputs(p2, transform.components[:T](t)))
            for t in 1:nt(p1)]) &&
        vcat(transform.components[:T].func, transform.components[:S].func) ∉ sigsTS

      push!(results, ACSetTransformation(deepcopy(transform.components), p1, p2))
      push!(sigsTS, vcat(transform.components[:T].func,
                         transform.components[:S].func))
    end
  end
  results
end

""" compare
Calculates all homomorphisms from a single Petri net (`apex`) to other Petri
nets. This is returned as a Multispan with `apex` as the apex and the
homomorphisms as the legs.
"""
function compare(apex::T, pn::T...; monic=[:S, :T, :I, :O], kw...) where {T <: AbstractPetriNet}
  legs = map(enumerate(pn)) do (i, p)
    hp = petri_homomorphisms(apex, p; monic=monic, kw...)
    isempty(hp) && error("No homomorphism from apex to leg $i which is monic in $monic")
    hp
  end
  Multispan(apex, vcat(legs...))
end

end


# Fit a Brownian motion with drift to and scale to
# Georgia election data:
# fraction of total count of votes vs difference in votes
using DelimitedFiles
datafile = download("https://alex.github.io/nyt-2020-election-scraper/battleground-state-changes.csv")
datatable, header = readdlm(datafile, ',', header=true)

ii = datatable[:,1] .== "Pennsylvania (EV: 20)"

ss = 2(datatable[ii,3] .== "Trump") .- 1
c = reverse(datatable[ii,5] + datatable[ii,6])
x = reverse(datatable[ii,7].*ss)

N = c[end]
# N = C + J
R = datatable[ii, 8][1]
T = (N - R)/N # remaining percentage to count
X = x[end] #vote difference

t = c ./ (R + N)

#=
(1, "state")
(2, "timestamp")
(3, "leading_candidate_name")
(4, "trailing_candidate_name")
(5, "leading_candidate_votes")
(6, "trailing_candidate_votes")
(7, "vote_differential")
(8, "votes_remaining")
(9, "new_votes")
(10, "new_votes_relevant")
(11, "new_votes_formatted")
(12, "leading_candidate_partition")
(13, "trailing_candidate_partition")
(14, "precincts_reporting")
(15, "precincts_total")
(16, "hurdle")
(17, "hurdle_change")
(18, "hurdle_mov_avg")
(19, "counties_partition")
(20, "total_votes_count")

data = [0.9222672139181572 103896.10389610389
    0.9267802009311443 87662.33766233765
    0.9277217593727027 85714.28571428571
    0.9285683655966674 83116.88311688312
    0.9299797843665768 79220.77922077922
    0.9310169076206811 78571.42857142857
    0.9329974271012007 77922.07792207792
    0.9332804459691252 77922.07792207792
    0.9364788042146532 68181.81818181818
    0.9368567753001715 68831.16883116882
    0.9372335211957853 68181.81818181818
    0.9426035285469248 60389.61038961039
    0.9435438617985787 57142.85714285713
    0.9438268806665033 57142.85714285713
    0.9471195785346729 47402.597402597385
    0.9484409458466062 48051.94805194806
    0.9526721391815731 33116.88311688311
    0.9532369517275178 31818.18181818181
    0.9578473413379074 18831.16883116882]
    t = data[:, 1]
    x = data[:, 2]

=#

using Mitosis
using Random, Test, LinearAlgebra, Statistics
using Distributions

function gibbs(t, x, iterations)
    # prior μ
    mw, Λw = zeros(1), Matrix(1e-12I(1))
    # prior σ2
    α0, β0 = 0.01, 0.01 # Inverse Gamma

    n = length(t)
    μ = -1e5
    σ = 1e5
    out = [(μ, σ)]
    for iter in 1:iterations
        # conjugate μ
        p0 = Gaussian{(:F,:Γ)}(mw, Λw)
        p = p0
        for i in n-1:-1:1
            Δ = t[i+1] - t[i]
            r = Mitosis.Regression([μ], (ConstantMap(Δ),), identity)
            k = kernel(Gaussian; μ=r, Σ=ConstantMap(σ^2*Δ))
            con = Mitosis.conjugate(k, x[i])
            _, pi = backward(BF(), con, [x[i+1]])
            _, p = fuse(p, pi)
        end

        # conjugate σ2
        #α = α0
        #β = β0
        α = 2n
        β = var(Binomial(N, (1-μ/N)/2))*α
        μ = rand(convert(Gaussian{(:μ,:Σ)}, p))[]
        for i in n-1:-1:1
            Δ = t[i+1] - t[i]
            r = Mitosis.Regression([μ], (ConstantMap(Δ),), identity)
            s = Mitosis.Regression([σ^2], (ConstantMap(Δ),), zero)
            k = kernel(Gaussian; μ=r, Σ=s)
            α = α + 1/2
            β = β + (x[i+1] - mean(k(x[i])))^2/(2Δ)
        end
        #σ = sqrt(rand(InverseGamma(α, β)))
        σ = std(Binomial(N, (1-μ/N)/2))
        push!(out, (μ, σ))
    end
    out
end

chain = gibbs(t, x, 1000)
μs = first.(chain)
σs = last.(chain)

println("μ")
describe(μs)
println("σ")
describe(σs)

using Makie
# Chain mixing

pl1 = hbox(lines(getindex.(chain, 1)),
   lines(getindex.(chain, 2)))

s = range(T, 1.00-eps(), length=100)
μu = quantile(μs, 0.80)
μl = quantile(μs, 0.20)
μ = median(μs)
σ = median(σs)

σu = quantile(σs, 0.8)
m = X .+ (s .- T)*μ
u = X .+ (s .- T)*μu .+ 2σu*sqrt.(s .- T)
l = X .+ (s .- T)*μl .- 2σu*sqrt.(s .- T)
Scene(resolution = (600, 600), textsize=20)
scatter!([T], [X])
#lines!(s, u)
lines!(s, m)
#lines!(s, l)
pl2 = band!(s, l, u)

v = [rand(kernel(Gaussian; μ=x->x+rand(μs)*(1-T), Σ=_->rand(σs)^2*(1-T))(X))
   for k in 1:1000]
mean(v .> 0)
mean(u[end] .> v .> l[end])
#scatter!(one.(v)+0.00001*randn(length(v)), v, markersize=0.1)
pl2

function bm(x, s, μ, σ)
    y = float(x)
    ys = [y]
    for i in 1:length(s)-1
        Δ = s[i+1] - s[i]
        r = Mitosis.Regression([μ], (ConstantMap(Δ),), identity)
        k = kernel(Gaussian; μ=r, Σ=ConstantMap(σ^2*Δ))
        y = rand(k(y))
        push!(ys, y)
    end
    ys
end

for i in 1:100
    lines!(s, bm(X, s, rand(μs), rand(σs)))
end
pl2

#
σ = std(Binomial(N, (1-μ/N)/2))


# Preamble ########################################################################################
using Pkg
Pkg.activate(Base.current_project())

cd("..") # Go to the parent directory
###################################################################################################
# Packages needed #################################################################################
using PolyGibbs
using DelimitedFiles
using ImageCore
using ImageIO
using ImageMagick
using FileIO
using PyPlot
###################################################################################################
# Angle of rotation ###############################################################################

const θ = π/4

###################################################################################################
# Retrieve of data from image #####################################################################

@info "Retrieving the image and extracting the two-dimensional field"
input = Float64.(channelview(Gray.(load("data/origin/symm/symm50.png"))))

###################################################################################################
# Identification of the grayscale field dimensions ################################################

j = (size(input)[1] - 1)/2;

N = Integer(2*j + 1);

@info "The size of the field is $N"

###################################################################################################
# Rotation of the image ###########################################################################

@info "Performing rotation at θ = $θ"

output = Emodes(input, θ)

###################################################################################################
# Saving results ##################################################################################

@info "Writting the results"
writedlm("data/target/symm/symm50_rpi4.dat", real(output))

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

begin
	fig = figure()
	ax = gca()
	ax.imshow(real(output), cmap = "gray")
	tight_layout()
	savefig("data/target/symm/symm50_rpi4.png", dpi = 300)
end


#=
  Demos for standard glm algos
=#

using glmSolverjl
using Random: seed!

seed!(0);
gammaX, gammaY = simulateData(Float64, GammaDistribution(), LogLink(), 30, 10_000);
gammaBlockX = matrixToBlock(gammaX, 10);
gammaBlockY = matrixToBlock(gammaY, 10);

binomialX, binomialY = simulateData(Float64, BinomialDistribution(), LogLink(), 30, 10_000);
binomialBlockX = matrixToBlock(binomialX, 10);
binomialBlockY = matrixToBlock(binomialY, 10);

poissonX, poissonY = simulateData(Float64, BinomialDistribution(), LogLink(), 30, 10_000);
poissonBlockX = matrixToBlock(poissonX, 10);
poissonBlockY = matrixToBlock(poissonY, 10);

# Fit the models
gamma_distrib_log_link = glm(RegularData(), gammaX, gammaY, GammaDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse());
gamma_distrib_inverse_link = glm(RegularData(), gammaX, gammaY, GammaDistribution(), InverseLink(), GESVSolver(), inverse = GETRIInverse());
gamma_distrib_identity_link = glm(RegularData(), gammaX, gammaY, GammaDistribution(), IdentityLink(), GESVSolver(),  inverse = GETRIInverse());

gamma_distrib_power_model_1 = glm(RegularData(), gammaX, gammaY, GammaDistribution(), PowerLink(0.0), GESVSolver(), inverse = GETRIInverse());
gamma_distrib_power_model_2 = glm(RegularData(), gammaX, gammaY, GammaDistribution(), PowerLink(1/3), GESVSolver(), inverse = GETRIInverse());
gamma_distrib_negative_binomial_link_1 = glm(RegularData(), gammaX, gammaY, GammaDistribution(), NegativeBinomialLink(1.0), GESVSolver(), inverse = GETRIInverse());
gamma_distrib_negative_binomial_link_2 = glm(RegularData(), gammaX, gammaY, GammaDistribution(), NegativeBinomialLink(2.0), GESVSolver(), inverse = GETRIInverse());

#= Binomial Distribution With Logit Link =#
binomial_logit_link = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse());
#= Binomial Distribution With Probit Link =#
binomial_probit_link = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), ProbitLink(), GESVSolver(), inverse = GETRIInverse());
binomial_cauchit_link = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), CauchitLink(), GESVSolver(), inverse = GETRIInverse());
binomial_oddspower_link_1 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(0.0), GESVSolver(), inverse = GETRIInverse());

binomial_distrib_odds_power_link_1 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(0.0), GESVSolver(), inverse = GETRIInverse());
# binomial_distrib_odds_power_link_2 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(2.0), GESVSolver(), inverse = GETRIInverse())

# binomial_oddspower_link_2 = glm(RegularData(), binomialX, binomialY, BinomialDistribution, OddsPowerLink{1})

# bernoulli_logcomplementary = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogComplimentLink(), GESVSolver(), inverse = GETRIInverse())
bernoulli_loglog = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogLogLink(), GESVSolver(), inverse = GETRIInverse());
# bernoulli_complementaryloglog = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), ComplementaryLogLogLink(), GESVSolver(), inverse = GETRIInverse())

# LogLink With Gaussian Distribution
log_link_gaussian_distrib = glm(RegularData(), gammaX, gammaY, GaussianDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())

# LogLink With Gamma Distribution
log_link_gamma_distribution = glm(RegularData(), gammaX, gammaY, GammaDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
log_link_inversegaussian_distribution = glm(RegularData(), gammaX, gammaY, InverseGaussianDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())

log_link_poisson_distribution = glm(RegularData(), poissonX, poissonY, PoissonDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
logit_link_bernoulli_distrib = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())

log_link_negative_bernoulli_distrib = glm(RegularData(), gammaX, gammaY, NegativeBernoulliDistribution(0.5), LogLink(), GESVSolver(), inverse = GETRIInverse())
cauchit_link_binomial_distribution = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), CauchitLink(), GESVSolver(), inverse = GETRIInverse())

#=======================================================================================#
igaussianLogModel = glm(RegularData(), gammaX, gammaY, InverseGaussianDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
igaussianInverseModel = glm(RegularData(), gammaX, gammaY, InverseGaussianDistribution(), InverseLink(), GESVSolver(), inverse = GETRIInverse())

bernoulliLogit = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())
bernoulliProbit = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), ProbitLink(), GESVSolver(), inverse = GETRIInverse())

bernoulliOddsPower0 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(0.0), GESVSolver(), inverse = GETRIInverse())

# bernoulliLogComplementary = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogComplimentLink(), GESVSolver(), inverse = GETRIInverse())
bernoulliLogLog = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogLogLink(), GESVSolver(), inverse = GETRIInverse())

#
# bernoulliComplementaryLogLog = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), ComplementaryLogLogLink(), GESVSolver(), inverse = GETRIInverse())

# Count Data
poissonLog = glm(RegularData(), poissonX, poissonY, PoissonDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
# Here the parameter is the inverse of what it is in R
# negativeBernoulliLog = glm(RegularData(), poissonX, poissonY, NegativeBernoulliDistribution(1/2), LogLink(), GESVSolver(), inverse = GETRIInverse())

# Education for full binomial data
binomialLogitModel = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())

# Testing the Cauchit Link function
binomialCauchit = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), CauchitLink(), GESVSolver(), inverse = GETRIInverse())

#=
TODO:

1. Do test/examples for offset and weights
=#

# Block Demos
block_gamma_distrib_log_link = glm(Block1D(), gammaBlockX, gammaBlockY, GammaDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
block_gamma_distrib_inverse_link = glm(Block1D(), gammaBlockX, gammaBlockY, GammaDistribution(), InverseLink(), GESVSolver(), inverse = GETRIInverse())
block_gamma_distrib_identity_link = glm(Block1D(), gammaBlockX, gammaBlockY, GammaDistribution(), IdentityLink(), GESVSolver(), inverse = GETRIInverse())
block_gamma_distrib_power_model_1 = glm(Block1D(), gammaBlockX, gammaBlockY, GammaDistribution(), PowerLink(0.0), GESVSolver(), inverse = GETRIInverse())
block_gamma_distrib_negative_binomial_link_2 = glm(Block1D(), gammaBlockX, gammaBlockY, GammaDistribution(), NegativeBinomialLink(2.0), GESVSolver(), inverse = GETRIInverse())
block_log_link_gaussian_distrib = glm(Block1D(), gammaBlockX, gammaBlockY, GaussianDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
block_igaussianLogModel = glm(Block1D(), gammaBlockX, gammaBlockY, InverseGaussianDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
block_igaussianInverseModel = glm(Block1D(), gammaBlockX, gammaBlockY, InverseGaussianDistribution(), InverseLink(), GESVSolver(), inverse = GETRIInverse())
# block_binomial_oddspower_link_1 = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), OddsPowerLink(2.0), GESVSolver(), inverse = GETRIInverse())
# block_binomial_distrib_odds_power_link_2 = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), OddsPowerLink(2.0), GESVSolver(), inverse = GETRIInverse())
block_cauchit_link_binomial_distribution = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), CauchitLink(), GESVSolver(), inverse = GETRIInverse())
block_poissonLog = glm(Block1D(), poissonBlockX, poissonBlockY, PoissonDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())

block_logit_link_bernoulli_distrib = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())
block_binomial_distrib_odds_power_link_1 = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), OddsPowerLink(0.0), GESVSolver(), inverse = GETRIInverse())
block_bernoulliLogit = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())
block_bernoulliOddsPower0 = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), OddsPowerLink(0.0), GESVSolver(), inverse = GETRIInverse())

block_gamma_distrib_log_link.coefficients |> println
block_gamma_distrib_inverse_link.coefficients |> println
block_gamma_distrib_identity_link.coefficients |> println
block_gamma_distrib_power_model_1.coefficients |> println
block_gamma_distrib_negative_binomial_link_2.coefficients |> println
block_log_link_gaussian_distrib.coefficients |> println
block_igaussianLogModel.coefficients |> println
block_igaussianInverseModel.coefficients |> println
block_cauchit_link_binomial_distribution.coefficients |> println
block_poissonLog.coefficients |> println

block_logit_link_bernoulli_distrib.coefficients |> println
block_binomial_distrib_odds_power_link_1.coefficients |> println
block_bernoulliLogit.coefficients |> println
block_bernoulliOddsPower0.coefficients |> println

gamma_distrib_log_link = glm(RegularData(), gammaX, gammaY, GammaDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
gamma_distrib_inverse_link = glm(RegularData(), gammaX, gammaY, GammaDistribution(), InverseLink(), GESVSolver(), inverse = GETRIInverse())
gamma_distrib_identity_link = glm(RegularData(), gammaX, gammaY, GammaDistribution(), IdentityLink(), GESVSolver(), inverse = GETRIInverse())
gamma_distrib_power_model_1 = glm(RegularData(), gammaX, gammaY, GammaDistribution(), PowerLink(0.0), GESVSolver(), inverse = GETRIInverse())
gamma_distrib_negative_binomial_link_2 = glm(RegularData(), gammaX, gammaY, GammaDistribution(), NegativeBinomialLink(2.0), GESVSolver(), inverse = GETRIInverse())
log_link_gaussian_distrib = glm(RegularData(), gammaX, gammaY, GaussianDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
igaussianLogModel = glm(RegularData(), gammaX, gammaY, InverseGaussianDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
igaussianInverseModel = glm(RegularData(), gammaX, gammaY, InverseGaussianDistribution(), InverseLink(), GESVSolver(), inverse = GETRIInverse())
# binomial_oddspower_link_1 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(2.0), GESVSolver(), inverse = GETRIInverse())
# binomial_distrib_odds_power_link_2 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(2.0), GESVSolver(), inverse = GETRIInverse())
cauchit_link_binomial_distribution = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), CauchitLink(), GESVSolver(), inverse = GETRIInverse())
poissonLog = glm(RegularData(), poissonX, poissonY, PoissonDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())

logit_link_bernoulli_distrib = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())
binomial_distrib_odds_power_link_1 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(0.0), GESVSolver(), inverse = GETRIInverse())
bernoulliLogit = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())
bernoulliOddsPower0 = glm(RegularData(), binomialX, binomialY, BinomialDistribution(), OddsPowerLink(0.0), GESVSolver(), inverse = GETRIInverse())

gamma_distrib_log_link.coefficients |> println
gamma_distrib_inverse_link.coefficients |> println
gamma_distrib_identity_link.coefficients |> println
gamma_distrib_power_model_1.coefficients |> println
gamma_distrib_negative_binomial_link_2.coefficients |> println
log_link_gaussian_distrib.coefficients |> println
igaussianLogModel.coefficients |> println
igaussianInverseModel.coefficients |> println
binomial_oddspower_link_1.coefficients |> println
cauchit_link_binomial_distribution.coefficients |> println
poissonLog.coefficients |> println

logit_link_bernoulli_distrib.coefficients |> println
binomial_distrib_odds_power_link_1.coefficients |> println
bernoulliLogit.coefficients |> println
bernoulliOddsPower0.coefficients |> println

# Parallel Block Algorithm
block_gamma_distrib_log_link = glm(Block1D(), gammaBlockX, gammaBlockY, GammaDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())
block_parallel_gamma_distrib_log_link = glm(Block1DParallel(), gammaBlockX, gammaBlockY, GammaDistribution(), LogLink(), GESVSolver(), inverse = GETRIInverse())

# block_binomial_oddspower_link_1 = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), OddsPowerLink(2.0), GESVSolver(), inverse = GETRIInverse())
# block_parallel_binomial_oddspower_link_1 = glm(Block1DParallel(), binomialBlockX, binomialBlockY, BinomialDistribution(), OddsPowerLink(2.0), GESVSolver(), inverse = GETRIInverse())

block_logit_link_bernoulli_distrib = glm(Block1D(), binomialBlockX, binomialBlockY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())
block_parallel_logit_link_bernoulli_distrib = glm(Block1DParallel(), binomialBlockX, binomialBlockY, BinomialDistribution(), LogitLink(), GESVSolver(), inverse = GETRIInverse())


######################################################################
# includes stuff I don't want to need distributions for...
######################################################################
export traj2mat

function my_pdf(x::NTuple{2,Float64},m::Vector{Float64},S::Matrix{Float64})
	dx = x[1] - m[1]
	dy = x[2] - m[2]

	Si = inv(S)
	e_stuff = dx*Si[1,1]*dx + dx*Si[1,2]*dy + dy*Si[2,1]*dx + dy*Si[2,2]*dy
	return det(sqrtm(Si)) * exp(-0.5 * e_stuff) / (2.0 * pi)
end

function my_pdf(x::VF, m::VF, S::MF)
	k = length(x)
	xm = x - m
	return exp(-0.5 * dot(xm, inv(S)*xm)) / sqrt((2pi)^k * det(S))
end

# In order to be faster, precompute inverse and denominator.
# That way, I don't have to recompute them for every cell in domain.
function fast_pdf(x::VF, m::VF, inv_S::MF, den::Float64)
	xm = x - m
	return exp(-0.5 * dot(xm, inv_S*xm)) / den
end

function centroid(d::Matrix{Float64}, L::Float64)
	x_val = 0.0; y_val = 0.0
	n = size(d,1)
	cell_size = L / n
	d_sum = 0.0
	for x = 1:n
		for y = 1:n
			x_val += (x-.5) * d[x,y]
			y_val += (y-.5) * d[x,y]
			d_sum += d[x,y]
		end
	end
	return x_val*cell_size/d_sum, y_val*cell_size/d_sum
end

function covariance(d::Matrix{Float64}, L::Float64)
	mu_x, mu_y = centroid(d, L)
	c_xx = c_xy = c_yy = 0.0
	n = size(d,1)
	cell_size = L / n
	d_sum = 0.0
	for xi = 1:n
		for yi = 1:n
			x = (xi-0.5)*cell_size
			y = (yi-0.5)*cell_size

			c_xx += d[xi,yi] * x * x
			c_yy += d[xi,yi] * y * y
			c_xy += d[xi,yi] * (x - mu_x) * (y - mu_y)
			d_sum += d[xi,yi]
		end
	end
	c_xx = c_xx/d_sum - (mu_x * mu_x)
	c_yy = c_yy/d_sum - (mu_y * mu_y)
	c_xy = c_xy/d_sum

	# add 1e-3 to ensure we are positive definite
	return [c_xx+1e-3 c_xy; c_xy c_yy+1e-3]
end

# unscaled
# g_f1 = grad_1 f
# g_f2 = grad_2 f
# u1, u2 are components of direction
# basically,
# [g_f1, g_f2]' * [u1, u2]'
function directional_derivative(g_f1::VVF, g_f2::VVF, u1::VVF, u2::VVF)
	N = length(g_f2)
	dd = 0.0
	for i = 1:N
		dd += dot(g_f1[i], u1[i]) + dot(g_f2[i], u2[i])
	end
	dd += dot(g_f1[N+1], u1[n+1])
	return dd
end
function directional_derivative(g_f1::Matrix{Float64}, g_f2::Matrix{Float64}, u1::VVF, u2::VVF)
	#N = length(g_f1)
	N = size(g_f2, 2)
	#n = size(u1[1])
	#m = size(u2[1])
	#println("N = ", N)
	dd = 0.0
	for i = 1:N
		# old way
		#dd += g_f1[1,i]*u1[i][1] + g_f1[2,i]*u1[i][2]
		#dd += g_f2[1,i]*u2[i][1] + g_f2[2,i]*u2[i][2]

		dd += dot(g_f1[:,i], u1[i]) + dot(g_f2[:,i], u2[i])
	end
	dd += dot(g_f1[:,N+1], u1[N+1])
	return dd
end
# TODO: turn this one into somepin correct for matrix{Float64} version
function directional_derivative(g_f1::Matrix{Float64}, g_f2::Matrix{Float64}, u1::Matrix{Float64}, u2::Matrix{Float64})
	#N = length(g_f1)
	N = size(g_f1, 2)
	dd = 0.0
	for i = 1:N
		#dd += dot(g_f1[i], u1[i]) + dot(g_f2[i], u2[i])
		dd += g_f1[1,i]*u1[1,i] + g_f1[2,i]*u1[2,i]
		dd += g_f2[1,i]*u2[1,i] + g_f2[2,i]*u2[2,i]
	end
	return dd
end
# like above, but scales the direction first
function scaled_dd(g_f1::VVF, g_f2::VVF, u1::VVF, u2::VVF)
	dd = directional_derivative(g_f1, g_f2, u1, u2)
	norm_factor = sqrt( dot(u1,u1) + dot(u2,u2) )
	return dd / norm_factor
end

function scaled_dd(g_f1::Matrix{Float64}, g_f2::Matrix{Float64}, u1::VVF, u2::VVF)
	dd = directional_derivative(g_f1, g_f2, u1, u2)
	norm_factor = sqrt( dot(u1,u1) + dot(u2,u2) )
	return dd / norm_factor
end

function scaled_dd(g_f1::Matrix{Float64}, g_f2::Matrix{Float64}, u1::Matrix{Float64}, u2::Matrix{Float64})
	dd = directional_derivative(g_f1, g_f2, u1, u2)
	norm_factor = sqrt( sum(u1.*u1) + sum(u2.*u2) )
	return dd / norm_factor
end

# TODO: why exactly do I do this?
# "normalizes" a matrix so (bins * bins) / sum(mat) = 1.0
function normalize!(mat::Matrix{Float64}, dA::Float64)
	num_x, num_y = size(mat)
	#c = (bins * bins) / sum(mat)
	c = 1.0 / (sum(mat) * dA)	# LD 3/02/2017
	for xi = 1:num_x
		for yi = 1:num_y
			mat[xi,yi] *= c
		end
	end
end
function normalize!(mat::Array{Float64,3}, dV::Float64)
	num_x, num_y, num_z = size(mat)
	#c = (bins * bins * bins) / sum(mat)
	c = 1.0 / (sum(mat) * dV)		# LD change 3/02/2017
	for xi = 1:num_x
		for yi = 1:num_y
			for zi = 1:num_z
				mat[xi,yi,zi] *= c
			end
		end
	end
end
export normalize!

"""
`mat2traj(mat::Matrix{Float64})`

Converts a matrix to a vector of Float64 vectors (VVF's).
Assumes the matrix is `N x n`, where `n` is the dimensionality of the state space and `N` is the number of points in the trajectory.
"""
function mat2traj(mat::Matrix{Float64})
	N,n = size(mat)
	traj = Array(Vector{Float64}, N)
	for i = 1:N
		traj[i] = vec(mat[i,:])
	end
	return traj
end

"""
`traj2mat(traj::VVF)`

Converts vector of vector of floats into `N x n` matrix, where `N` is the number of points in trajectory and `n` is the dimensionality of the state.
"""
function traj2mat(traj::VVF)
	N = length(traj)
	n = length(traj[1])
	mat = zeros(N, n)
	for i = 1:N
		mat[i,:] = traj[i]
	end
	return mat
end

export vvvf2vvf
function vvvf2vvf(xd::VVVF, ud::VVVF)
	N = length(ud[1])
	xdcat = hcat(xd...)
	udcat = hcat(ud...)
	x = VVF(N+1)
	u = VVF(N)
	for n = 1:N
		x[n] = vcat(xdcat[n,:]...)
		u[n] = vcat(udcat[n,:]...)
	end
	x[N+1] = vcat(xdcat[N+1,:]...)
	return x,u
end


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    Line(a, b)

A line passing through points `a` and `b`.

See also [`Segment`](@ref).
"""
struct Line{Dim,T} <: Primitive{Dim,T}
  a::Point{Dim,T}
  b::Point{Dim,T}
end

Line(a::Tuple, b::Tuple) = Line(Point(a), Point(b))

paramdim(::Type{<:Line}) = 1

(l::Line)(t) = l.a + t * (l.b - l.a)


"""
    Aquifer

Abstract type representing an aquifer, either confined or unconfined.

The vertically averaged governing equation of an unconfined, inhomogeneous and
isotropic aquifer in one dimension can be written as:

S * ∂ϕ / ∂t = ∂ / ∂x * (kH * ∂ϕ / ∂x) + Q

with:
* S: storativity (or storage coefficient)
* ϕ: hydraulic head (groundwater level)
* t: time
* k: conductivity
* H: H the (saturated) aquifer height: groundwater level - aquifer bottom
  elevation
* η: elevation of aquifer bottom
* Q: fluxes from boundary conditions (e.g. recharge or abstraction)

The simplest finite difference formulation is forward in time, central in space,
and can be written as:

Sᵢ * (ϕᵢᵗ⁺¹ - ϕᵢᵗ) / Δt = -Cᵢ₋₁ * (ϕᵢ₋₁ - ϕᵢ) -Cᵢ * (ϕᵢ₊₁ - ϕᵢ) + Qᵢ

with:
* ᵢ as cell index
* ᵗ as time index
* Δt as step size
* Cᵢ₋₁ as the intercell conductance between cell i-1 and i
* Cᵢ as the intercell conductance between cell i and i+1

Conductance is defined as:

C = kH * w / l

with:
* w the width of the cell to cell connection
* l the length of the cell to cell connection

k and H may both vary in space; intercell conductance is therefore an average
using the properties of two cells. See the documentation below.

There is only one unknown, ϕᵢᵗ⁺¹. Reshuffling terms:

ϕᵢᵗ⁺¹ = ϕᵢᵗ + (Cᵢ₋₁ * (ϕᵢ - ϕᵢ₋₁) + Cᵢ * (ϕᵢ₊₁ - ϕᵢ) + Qᵢ) * Δt / Sᵢ

This can be generalized to two dimensions, for both regular and irregular cell
connectivity.

See this paper for more details:
     Chu, W. S., & Willis, R. (1984). An explicit finite difference model for
     unconfined aquifers. Groundwater, 22(6), 728-734.

Boundary conditions can be classified into three categories:
* specified head (Dirichlet)
* specified flux (Neumann)
* head-dependent flux (Robin)

Neumann and Robin conditions are implemented by adding to or subtracting from a
net (lumped) cell flux. Dirichlet conditions are special cased, since they
cannot (easily) be implemented via the flux, but the head is set directly
instead.
"""
abstract type Aquifer end

abstract type AquiferBoundaryCondition end

"""
    ConfinedAquifer{T} <: Aquifer

Confined aquifers are overlain by a poorly permeable confining layer (e.g.
clay). No air can get in to fill the pore space so that the aquifer always
remains fully saturated. For a confined aquifer, water will always flow along
the complete height over the aquifer and transmissivity kH (m d⁻¹) is a
constant.

Specific storage is the amount of water an aquifer releases per unit change in
hydraulic head, per unit volume of aquifer, as the aquifer and the groundwater
itself is compressed. Its value is much smaller than specific yield, between
1E-5 (stiff) and 0.01 (weak).

NOTA BENE: **specific** storage is per m of aquifer (conf. specific weight).
**Storativity** or (**storage coefficient**) is for the entire aquifer (conf.
transmissivity).
"""
struct ConfinedAquifer{T} <: Aquifer
    head::Vector{T}  # hydraulic head [m]
    k::Vector{T}  # horizontal conductivity [m m⁻¹]
    top::Vector{T} # top of groundwater layer [m]
    bottom::Vector{T} # bottom of groundwater layer
    area::Vector{T} # area of cell
    specific_storage::Vector{T} # [m m⁻¹ m⁻¹]
    storativity::Vector{T} # [m m⁻¹]
    conductance::Vector{T} # Confined aquifer conductance is constant
end

"""
    UnconfinedAquifer{T} <: Aquifer

The upper boundary of an unconfined aquifer is the water table (the phreatic
surface).

Specific yield (or drainable porosity) represents the volumetric fraction the
aquifer will yield when all water drains and the pore volume is filled by air
instead. Specific yield will vary roughly between 0.05 (clay) and 0.45 (peat)
(Johnson, 1967).
"""
struct UnconfinedAquifer{T} <: Aquifer
    head::Vector{T}  # hydraulic head [m]
    k::Vector{T}  # horizontal conductivity [m d⁻¹]
    top::Vector{T} # top of groundwater layer [m]
    bottom::Vector{T} # bottom of groundwater layer
    area::Vector{T}
    specific_yield::Vector{T} # [m m⁻¹]
    conductance::Vector{T} #
    # Unconfined aquifer conductance is computed with degree of saturation
end

storativity(A::UnconfinedAquifer) = A.specific_yield
storativity(A::ConfinedAquifer) = A.storativity

"""
    harmonicmean_conductance(k1, k2, H1, H2, l1, l2, width)

The harmonic mean is the exact interblock transmissivity for steady-state
one-dimensional flow with no recharge if the transmissivity is assumed to be
spatially uniform over each finite difference block, changing abruptly at the
block interface.

Refer to:

    Goode, D. J., & Appel, C. A. (1992). Finite-Difference Interblock Transmissivity
    for Unconﬁned Aquifers and for Aquifers having Smoothly Varying Transmissivity.
    Water-resources investigations report, 92, 4124.
"""
function harmonicmean_conductance(k1, k2, H1, H2, l1, l2, width)
    kH1 = k1 * H1
    kH2 = k2 * H2
    if (kH1 * kH2) > 0.0
        return width * kH1 * kH2 / (kH1 * l2 + kH2 * l1)
    else
        return 0.0
    end
end

function saturated_thickness(aquifer::UnconfinedAquifer, index::Int)
    min(aquifer.top[index], aquifer.head[index]) - aquifer.bottom[index]
end

function saturated_thickness(aquifer::ConfinedAquifer, index::Int)
    aquifer.top[index] - aquifer.bottom[index]
end

"""
    horizontal_conductance(i, j, nzi, aquifer, C)

Compute fully saturated horizontal conductance for a single connection between two cells
(indexed with `i` and `j`). Geometry characteristics are taken from the
connectivity struct `C`, using the non-zero index (nzi) of its CSC data
structure.
"""
function horizontal_conductance(
    i::Int,
    j::Int,
    nzi::Int,
    aquifer::A,
    connectivity::Connectivity,
) where A <: Aquifer
    k1 = aquifer.k[i]
    k2 = aquifer.k[j]
    H1 = aquifer.top[i] - aquifer.bottom[i]
    H2 = aquifer.top[j] - aquifer.bottom[j]
    length1 = connectivity.length1[nzi]
    length2 = connectivity.length2[nzi]
    width = connectivity.width[nzi]
    return harmonicmean_conductance(k1, k2, H1, H2, length1, length2, width)
end

"""
    initialize_conductance!(aquifer::A, connectivity::Connectivity) where A <: Aquifer

Conductance for a confined aquifer is constant, and only has to be set once.
For an unconfined aquifer, conductance is computed per timestep by multiplying by
degree of saturation [0.0 - 1.0].
"""
function initialize_conductance!(aquifer::A, connectivity::Connectivity) where A <: Aquifer
    for i in 1:connectivity.ncell
        # Loop over connections for cell j
        for nzi in connections(connectivity, i)
            j = connectivity.rowval[nzi]
            aquifer.conductance[nzi] = horizontal_conductance(i, j, nzi, aquifer, connectivity)
        end
    end
end

function conductance(aquifer::ConfinedAquifer, i, j, nzi)
    return aquifer.conductance[nzi]
end

"""
    conductance(aquifer::UnconfinedAquifer, connectivity::Connectivity)

This computes the conductance for an unconfined aquifer using the "upstream
saturated fraction" as the MODFLOW documentation calls it. In this approach, the
saturated thickness of a cell-to-cell is approximated using the cell with the
highest head. This results in a consistent overestimation of the saturated
thickness, but it avoids complexities related with cell drying and rewetting,
such as having to define a "wetting threshold" or a "wetting factor".

See the documentation for MODFLOW-NWT or MODFLOW6 for more background:
Niswonger, R.G., Panday, Sorab, and Ibaraki, Motomu, 2011, MODFLOW-NWT, A Newton
formulation for MODFLOW-2005: U.S. Geological Survey Techniques and Methods
6-A37, 44 p.

Langevin, C.D., Hughes, J.D., Banta, E.R., Niswonger, R.G., Panday, Sorab, and
Provost, A.M., 2017, Documentation for the MODFLOW 6 Groundwater Flow Model:
U.S. Geological Survey Techniques and Methods, book 6, chap. A55, 197 p.,
https://doi.org/10.3133/tm6A55.

For background on drying and rewetting, see:
McDonald, M.G., Harbaugh, A.W., Orr, B.R., and Ackerman, D.J., 1991, A method of
converting no-flow cells to variable-head cells for the U.S. Geological Survey
modular finite-difference groundwater flow model: U.S. Geological Survey
Open-File Report 91-536, 99 p
"""
function conductance(aquifer::UnconfinedAquifer, i, j, nzi)
    ϕᵢ = aquifer.head[i]
    ϕⱼ = aquifer.head[j]
    if ϕᵢ >= ϕⱼ
        saturation = saturated_thickness(aquifer, i) / (aquifer.top[i] - aquifer.bottom[i])
    else
        saturation = saturated_thickness(aquifer, j) / (aquifer.top[j] - aquifer.bottom[j])
    end
    return saturation * aquifer.conductance[nzi]
end

function flux!(Q, aquifer, connectivity)
    for i in 1:connectivity.ncell
        # Loop over connections for cell j
        for nzi in connections(connectivity, i)
            # connection from i -> j
            j = connectivity.rowval[nzi]
            Δϕ = aquifer.head[i] - aquifer.head[j]
            cond = conductance(aquifer, i, j, nzi)
            Q[i] -= cond * Δϕ
        end
    end
    return Q
end

struct ConstantHead{T}
    head::Vector{T}
    index::Vector{Int}
end

"""
    stable_timestep(aquifer)

Compute a stable timestep size given the forward-in-time, central in space scheme.
The following criterion can be found in Chu & Willis (1984)

Δt * k * H / (Δx * Δy * S) <= 1/4
"""
function stable_timestep(aquifer)
    Δtₘᵢₙ = Inf
    for i in eachindex(aquifer.head)
        Δt = aquifer.area[i] * storativity(aquifer)[i] / (aquifer.k[i] * saturated_thickness(aquifer, i))
        Δtₘᵢₙ = Δt < Δtₘᵢₙ ? Δt : Δtₘᵢₙ
    end
    return 0.25 * Δtₘᵢₙ
end

minimum_head(aquifer::ConfinedAquifer) = aquifer.head
minimum_head(aquifer::UnconfinedAquifer) = max.(aquifer.head, aquifer.bottom)

function update(gwf, Q, Δt)
    Q .= 0.0  # TODO: Probably remove this when linking with other components
    flux!(Q, gwf.aquifer, gwf.connectivity)
    for boundary in gwf.boundaries
        flux!(Q, boundary, gwf.aquifer)
    end
    gwf.aquifer.head .+= (Q ./ gwf.aquifer.area .* Δt ./ storativity(gwf.aquifer))
    # Set constant head (dirichlet) boundaries
    gwf.aquifer.head[gwf.constanthead.index] .= gwf.constanthead.head
    # Make sure no heads ends up below an unconfined aquifer bottom
    gwf.aquifer.head .= minimum_head(gwf.aquifer)
    return gwf
end

Base.@kwdef struct GroundwaterFlow{A, B}
    aquifer::A
    connectivity::Connectivity
    constanthead::ConstantHead
    boundaries::Vector{B}
    function GroundwaterFlow(
            aquifer::A,
            connectivity,
            constanthead,
            boundaries::Vector{B}) where {A <: Aquifer, B <: AquiferBoundaryCondition}
        initialize_conductance!(aquifer, connectivity)
        new{A, B}(aquifer, connectivity, constanthead, boundaries)
    end
end


"""
        fs_expansion(x, u, v, L, T, n = i -> 2^-(1 + i/2))
Global recursion: find the value of the process on the dyadic points given by the first L Levels,
`x` is a vector containing coordinates `u`,`v` initial and final point
`T` length of the basis
`n` is the normalization function (in our case known)
"""
function fs_expansion(x::Vector{Float64}, u::Float64, v::Float64, L::Int64, T::Float64, n = i -> 2^-(1 + i/2))
        y = fill(NaN, 2<<L + 1)
        y[1] = u
        y[end] = v
        k = 1
        c = sqrt(T)
        for i in 0:L
                for j in 0:(1<<i - 1)
                        l = 1<<(L-i) + 2<<(L-i)*(j) + 1 #new index
                        y[l] = x[k]*n(i)*c + (y[l + 1<<(L-i)] + y[l - 1<<(L-i)])/2
                        k += 1
                end
        end
        y
end

"""
        fs_expansion(ϕ::Fs, ξ, u, v, L::Int64, T::Float64, n = i -> 2^-(1 + i/2))
Local recursion, like Global but on the support of the Faber Schauder function `ϕ` up to level `L`
This function evaluates only the necessary functions for the evaluation of the points inside the support
of the function ϕ.
`x` is a vector containing coordinates `u`,`v` initial and final point
`T` length of the basis
`n` is the normalization function (in our case known)

"""
function fs_expansion(ϕ::Fs, ξ::Vector{Float64}, u::Float64, v::Float64, L::Int64, T::Float64, n = i -> 2^-(1 + i/2))
        y = fill(NaN, 2<<(L) + 1)   #number of points in which we require the evaluation of the function
        y[end] = v
        y[1] = u
        c = sqrt(T)
        for k in ϕ.nhb
                i,j = Faber(k)
                l = 2<<(L-i-1) + 2<<(L-i)*(j) + 1 #new index
                y[l] = ξ[k]*n(i)*c + (y[l + 2<<(L-i-1)]  + y[l - 2<<(L-i-1)])/2
                k += 1
        end
        y[(2<<(L-ϕ.i)*ϕ.j + 1):(2<<(L-ϕ.i)*(ϕ.j + 1) + 1)]
end

"""
        interpolate(x:: array, y::array, x0)

simple interpolation function given `x`, `y`: 2-element array, `x0` floating
"""
function interpolate(x::Vector{Float64}, y::Vector{Float64}, x0::Float64)
        if x0 == x[1]
                return y[1]
        elseif x0 == x[2]
                return y[2]
        else
                return ( y[2] - (y[2] - y[1])/(x[2] - x[1])*(x[2] - x0))
        end
end

"""
        fs_expansion(S::System, t::Float64, u, v, n = i -> 2^-(1 + i/2))

Find value of the process (Piecewise linear) for any t ∈ [0, T]
does not evaluate the  whole path, but just the points needed for t
"""
function fs_expansion(S::System, t::Float64, u::Float64, v::Float64, n = i -> 2^-(1 + i/2))
        dt = 0:S.T/(2<<S.L):S.T
        k = (searchsortedfirst(dt, t) - 1)
        j0 =  Int(ceil(k/2))-1
        n0 = Faber(S.L, j0)
        if k % 2 != 0
                return interpolate([dt[k], dt[k + 1]], fs_expansion(S.ϕ[n0], S.ξ, u, v, S.L, S.T)[1:2], t)
        else
                return interpolate([dt[k], dt[k + 1]], fs_expansion(S.ϕ[n0], S.ξ, u, v,  S.L, S.T)[2:3], t)
        end
end


using .MiniQhull
import .MiniQhull: delaunay
export delaunay

"""
    delaunay(X::LazySet)

Compute the Delaunay triangulation of the given convex set.

### Input

- `X` -- set

### Output

A union of polytopes in vertex representation.

### Notes

This function requires that you have properly installed
the Julia package [MiniQhull.jl](https://github.com/gridap/MiniQhull.jl), including
the library [Qhull](http://www.qhull.org/).

The method works in arbitrary dimension and the requirement is that the list of
vertices of `X` can be obtained.
"""
function delaunay(X::LazySet)
    n = dim(X)
    v = vertices_list(X)
    m = length(v)
    coordinates = vcat(v...)
    connectivity_matrix = delaunay(n, m, coordinates)
    nelements = size(connectivity_matrix, 2)
    elements = [VPolytope(v[connectivity_matrix[:, i]]) for i in 1:nelements]
    return UnionSetArray(elements)
end


# Utility operators for computing prox iteratively, e.g. using CG

import Base: *, size, eltype
import LinearAlgebra: mul!

abstract type LinOp end

infer_shape_of_y(Op, ::AbstractVector) = (size(Op, 1), )
infer_shape_of_y(Op, x::AbstractMatrix) = (size(Op, 1), size(x, 2))

function (*)(Op::LinOp, x)
    y = zeros(promote_type(eltype(Op), eltype(x)), infer_shape_of_y(Op, x))
    mul!(y, Op, x)
end

size(Op::LinOp, i::Integer) = i <= 2 ? size(Op)[i] : 1

# AAc (Gram matrix)

mutable struct AAc{M, T} <: LinOp
    A::M
    buf::Maybe{AbstractArray{T}}
    function AAc{M, T}(A::M) where {M, T}
        new(A, nothing)
    end
end

AAc(A::M) where {M} = AAc{M, eltype(A)}(A)

function mul!(y, Op::AAc, x)
    if Op.buf === nothing
        Op.buf = adjoint(Op.A) * x
    else
        mul!(Op.buf, adjoint(Op.A), x)
    end
    mul!(y, Op.A, Op.buf)
end

mul!(y, Op::Adjoint{AAc}, x) = mul!(y, adjoint(Op), x)

size(Op::AAc) = size(Op.A, 1), size(Op.A, 1)
eltype(Op::AAc) = eltype(Op.A)

# AcA (Covariance matrix)

mutable struct AcA{M, T} <: LinOp
    A::M
    buf::Maybe{AbstractArray{T}}
    function AcA{M, T}(A::M) where {M, T}
        new(A, nothing)
    end
end

AcA(A::M) where {M} = AcA{M, eltype(A)}(A)

function mul!(y, Op::AcA, x)
    if Op.buf === nothing
        Op.buf = Op.A * x
    else
        mul!(Op.buf, Op.A, x)
    end
    mul!(y, adjoint(Op.A), Op.buf)
end

mul!(y, Op::Adjoint{AcA}, x) = mul!(y, adjoint(Op), x)

size(Op::AcA) = size(Op.A, 2), size(Op.A, 2)
eltype(Op::AcA) = eltype(Op.A)

# Shifted symmetric linear operator

struct ScaleShift{M, T} <: LinOp
    alpha::T
    A::M
    rho::T
    function ScaleShift{M, T}(alpha::T, A::M, rho::T) where {M, T}
        if eltype(A) != T
            error("type of alpha, rho ($T) is different from that of A ($(eltype(A)))")
        end
        new(alpha, A, rho)
    end
end

ScaleShift(alpha::T, A::M, rho::T) where {M, T} = ScaleShift{M, T}(alpha, A, rho)

function mul!(y, Op::ScaleShift, x)
    mul!(y, Op.A, x)
    y .*= Op.alpha
    y .+= Op.rho .* x
end

function mul!(y, Op::Adjoint{ScaleShift}, x)
    mul!(y, adjoint(Op.A), x)
    y .*= Op.alpha
    y .+= Op.rho .* x
end

size(Op::ScaleShift) = size(Op.A, 2), size(Op.A, 2)
eltype(Op::ScaleShift) = eltype(Op.A)


@testset "AbsAlgAss" begin

  @testset "Decomposition" begin
    Fp = GF(3)
    G = small_group(8, 4)
    FpG = group_algebra(Fp, G)

    dec = decompose(FpG)
    @test length(dec) == 5
    dim1 = 0
    dim4 = 0
    for (A, toFpG) in dec
      if dim(A) == 1
        dim1 += 1
      elseif dim(A) == 4
        dim4 += 1
      end
    end
    @test dim1 == 4
    @test dim4 == 1

    # Check whether the maps "work"
    A, toFpG = dec[1]
    e = toFpG(one(A))
    @test e^2 == e
    ee = toFpG\one(FpG)
    @test ee^2 == ee

    # And now the same for AlgAss
    FpG = AlgAss(FpG)[1]
    dec = decompose(FpG)
    @test length(dec) == 5
    dim1 = 0
    dim4 = 0
    for (A, toFpG) in dec
      if dim(A) == 1
        dim1 += 1
      elseif dim(A) == 4
        dim4 += 1
      end
    end
    @test dim1 == 4
    @test dim4 == 1

    # Check whether the maps "work"
    A, toFpG = dec[1]
    e = toFpG(one(A))
    @test e^2 == e
    ee = toFpG\one(FpG)
    @test ee^2 == ee

    # And now for AlgMat
    A = matrix_algebra(Fp, 2)

    dec = decompose(A)
    @test length(dec) == 1

    B, BtoA = dec[1]
    @test dim(B) == 4

    e = BtoA(one(B))
    @test e^2 == e
    ee = BtoA\one(A)
    @test ee^2 == ee

    A = matrix_algebra(Fp, [ matrix(Fp, [ 1 1; 0 1 ]) ]) # not semisimple!
    @test_throws AssertionError decompose(A)

    Qx, x = FlintQQ["x"]
    A = AlgAss((x^2 + 1)*(x^2 + 3))
    dec = Hecke.as_number_fields(A)

    @test length(dec) == 2
    @test degree(dec[1][1]) == 2
    @test degree(dec[2][1]) == 2

    K, AtoK = dec[1]
    e = AtoK(one(A))
    @test e^2 == e
    ee = AtoK\one(K)
    @test ee^2 == ee
  end

  @testset "Generators" begin
    Qx, x = FlintQQ["x"]
    A = AlgAss((x^2 + 1)*(x^2 + 3))
    g, full_basis, v = gens(A, Val{true})

    @test length(full_basis) == dim(A)

    M = zero_matrix(FlintQQ, dim(A), dim(A))
    for i = 1:dim(A)
      Hecke.elem_to_mat_row!(M, i, full_basis[i])
    end
    @test rank(M) == dim(A)

    for i = 1:dim(A)
      b = full_basis[i]
      a = one(A)
      for (j, k) in v[i]
        a *= g[j]^k
      end
      @test b == a
    end
  end

end


function rosenbrock2d(x)
  return (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
end

function rosenbrock(x)
  return( sum( 100*( x[2:end] - x[1:end-1].^2 ).^2 + ( x[1:end-1] - 1 ).^2 ) )
end

facts("bboptimize") do
  context("example 1 from README") do
    best, fitness = bboptimize(rosenbrock2d; search_range = (-5.0, 5.0), dimensions = 2, parameters = {:ShowTrace => false})
    @fact fitness < 0.001 => true
  end

  context("example 2 from README") do
    best, fitness = bboptimize(rosenbrock2d; search_range = [(-5.0, 5.0), (-2.0, 2.0)], parameters = {:ShowTrace => false})
    @fact fitness < 0.001 => true
  end

  context("example 3 from README") do
    best, fitness = bboptimize(rosenbrock2d; search_range = (-5.0, 5.0), dimensions = 2, method = :de_rand_1_bin, parameters = {:ShowTrace => false})
    @fact fitness < 0.001 => true
  end

  context("example 4 from README") do
    best, fitness = bboptimize(rosenbrock2d; search_range = (-5.0, 5.0), dimensions = 2,
      method = :random_search, max_time = 10.0, parameters = {:ShowTrace => false})
    @fact fitness < 0.2 => true
  end

  context("example 5 from README") do
    BlackBoxOptim.compare_optimizers(rosenbrock; search_range = (-5.0, 5.0), dimensions = 30, max_time = 5.0, parameters = {:ShowTrace => false})
  end

  context("run one longer example in case there is problem with the reporting in long runs") do
    best, fitness = bboptimize(rosenbrock2d; search_range = (-5.0, 5.0), dimensions = 2,
      method = :de_rand_1_bin, parameters = {:ShowTrace => true, :MaxSteps => 25001})
    @fact fitness < 0.001 => true
  end
end


using InvertibleNetworks, NNlib, LinearAlgebra, Test

# Data
nx = 28
ny = 28
n_in = 8
batchsize = 1
n_hidden = 8
k = 3   # kernel size
s = 1   # stride
p = 1   # padding

pars = (nx, ny, n_in, batchsize, k, s,p)
###################################################################################################
# Test layer invertibility

function test_inv(nx, ny, n_in, batchsize, k, s, p, n_hidden, action)
    print("\nInvertibility test hyperbolic layer with action=$(action)\n")
    # Data
    X0 = randn(Float32, nx, ny, n_in, batchsize)
    X1 = randn(Float32, nx, ny, n_in, batchsize)

    # Layer
    H1 = HyperbolicLayer(nx, ny, n_in, batchsize, k, s, p; action=action, α=.2f0, hidden_factor=n_hidden)

    Y0, Y1 = H1.forward(X0, X1)
    X0_, X1_ = H1.inverse(Y0, Y1)

    # Test invertibility
    @test isapprox(norm(X0 - X0_)/norm(X0_), 0f0; atol=1f-6)
    @test isapprox(norm(X1 - X1_)/norm(X1_), 0f0; atol=1f-6)

end

# Loss
function loss(H, X_prev_in, X_curr_in, Y_curr, Y_new)
    X_curr, X_new = H.forward(X_prev_in, X_curr_in)
    f = .5f0*norm(X_new - Y_new)^2 + .5*norm(X_curr - Y_curr)^2
    ΔX_curr = X_curr - Y_curr
    ΔX_new = X_new - Y_new
    ΔX_prev_, ΔX_curr_, X_prev_, X_curr_ = H.backward(ΔX_curr, ΔX_new, X_curr, X_new)

    # Check inverse is correct
    @test isapprox(norm(X_prev_ - X_prev_in)/norm(X_prev_in), 0f0; atol=1f-6)
    @test isapprox(norm(X_curr_ - X_curr_in)/norm(X_curr_in), 0f0; atol=1f-6)

    return f, ΔX_prev_, ΔX_curr_, H.W.grad, H.b.grad
end

function test_grad_X(nx, ny, n_in, batchsize, k, s, p, n_hidden, action)
    H1 = HyperbolicLayer(nx, ny, n_in, batchsize, k, s, p; action=action, α=.2f0, hidden_factor=n_hidden)
    # Initial guess and residual
    X00 = randn(Float32, nx, ny, n_in, batchsize)
    X01 = randn(Float32, nx, ny, n_in, batchsize)
    dX0 = randn(Float32, nx, ny, n_in, batchsize)
    dX1 = randn(Float32, nx, ny, n_in, batchsize)

    # Observed data
    Y_curr, Y_new = H1.forward(X00, X01)

    # Gradient test for X
    maxiter = 10
    print("\nGradient test hyperbolic layer  input with action=$(action)\n")
    f0, ΔX_prev, ΔX_curr = loss(H1, X00, X01, Y_curr, Y_new)[1:3]
    h = .1f0
    err1 = zeros(Float32, maxiter)
    err2 = zeros(Float32, maxiter)
    for j=1:maxiter
        f = loss(H1, X00 + h*dX0, X01 + h*dX1, Y_curr, Y_new)[1]
        err1[j] = abs(f - f0)
        err2[j] = abs(f - f0 - h*dot(dX0, ΔX_prev) - h*dot(dX1, ΔX_curr))
        print(err1[j], "; ", err2[j], "\n")
        h = h/2f0
    end

    @test isapprox(err1[end] / (err1[1]/2^(maxiter-1)), 1f0; atol=1f1)
    @test isapprox(err2[end] / (err2[1]/4^(maxiter-1)), 1f0; atol=1f1)
end

function test_grad_par(nx, ny, n_in, batchsize, k, s, p, n_hidden, action)
    # Gradient test for W and b
    H0 = HyperbolicLayer(nx, ny, n_in, batchsize, k, s, p; action="down", α=.2f0, hidden_factor=8)
    Hini = deepcopy(H0)

    dW = randn(Float32, size(H0.W.data))
    db = randn(Float32, size(H0.b.data))
    maxiter = 10

    # Data
    X0 = randn(Float32, nx, ny, n_in, batchsize)
    X1 = randn(Float32, nx, ny, n_in, batchsize)

    # Observed data
    Y_curr, Y_new = H0.forward(X0, X1)

    print("\nGradient test hyperbolic layer weights with action=$(action)\n")
    f0, ΔX_prev, ΔX_curr, ΔW, Δb = loss(H0, X0, X1, Y_curr, Y_new)

    h = .1f0
    err3 = zeros(Float32, maxiter)
    err4 = zeros(Float32, maxiter)

    for j=1:maxiter
        H0.W.data = Hini.W.data + h*dW
        H0.b.data = Hini.b.data + h*db
        f = loss(H0, X0, X1, Y_curr, Y_new)[1]
        err3[j] = abs(f - f0)
        err4[j] = abs(f - f0 - h*dot(dW, ΔW) - h*dot(db, Δb))
        print(err3[j], "; ", err4[j], "\n")
        h = h/2f0
    end
    @test isapprox(err3[end] / (err3[1]/2^(maxiter-1)), 1f0; atol=1f1)
    @test isapprox(err4[end] / (err4[1]/4^(maxiter-1)), 1f0; atol=1f1)

end

for action in ["up", "down", "same"]
    test_inv(nx, ny, n_in, batchsize, k, s, p, n_hidden, action)
    test_grad_X(nx, ny, n_in, batchsize, k, s, p, n_hidden, action)
    test_grad_par(nx, ny, n_in, batchsize, k, s, p, n_hidden, action)
end


# Π(θ) = γ[1] * quality + γ[2] log(y - p)
# C(i;θ) = (β[1] - β[2] * η ) i + β[3] i^2 + β[4] ownQ 1(i>0& ownQ > 0 )

struct Parameter
    γ::Array{Real,1}
    β::Array{Real,1}
    Parameter() = new([1],[0.2, 0.1, 0.2, 0.0])
    Parameter(γ,β) = new(γ,β)
end
#
struct State
    ownQ::Real
    otherQ::Array{Real,1}
    η::Real  #The private shock
    State() = new(sqrt(3) * randn() + 0.5,[],sqrt(2)*randn());
    State(ownQ::Real,otherQ::RealVector,η::Real) = new(ownQ,otherQ,η)
end
#
function profit(s::State,invest::Real,p::Parameter)
    profit = s.ownQ * p.γ[1]
    cost = (invest * (p.β[1] - p.β[2] * s.η) +
    p.β[3] * invest^2 + p.β[4]*(invest>0) * (s.ownQ > 0) * invest * s.ownQ)
    return profit - cost
end
#
mutable struct ProfitFn
    γ::RealVector
    β::RealVector
    p::Parameter
    ProfitFn(p) = new(p.γ, p.β,p) #constructor of a profit function
    (self::ProfitFn)() = 0
    (self::ProfitFn)(k) = println(k)
    (self::ProfitFn)(s,i) = profit(s,i,self.p) #callable struct
    # function profit(s,i) = profit(s,i,p)
end


module Competition

using RecordedArrays
using Random
using Test
using EvolutionaryModelingTools
using EvolutionaryModelingTools: sample

# parameters
clock = ContinuousClock(100.0)
r = 0.5   # growth rate
K = 100   # carrying capacity
μ = 0.01  # mutation rate

# state
v = recorded(DynamicEntry, clock, [100])
m = recorded(StaticEntry, clock, ones(1, 1))

# simulate with this package
@reaction growth begin
    r * (1 - μ) * v
    v[ind] += 1
end

@reaction mutation begin
    @. r * μ * v
    begin
        push!(v, 1)
        n = length(v)
        resize!(m, (n, n))
        # rand competition matrix
        m[:, n] = rand(rng, n)
        m[n, 1:n-1] = rand(rng, 1, n - 1)
    end
end

@reaction competition begin
    begin
        c = r / K # baseline competition coefficient
        @. c * v * m * v'
    end
    begin
        i = ind[1]
        v[i] -= 1
        if v[i] == 0 # check extinction
            deleteat!(v, i)
            resize!(m, (not(i), not(i)))
        end
    end
end

ps = (; r, K, μ, v, m)
rs = (growth, mutation, competition)

Random.seed!(1)
ps_updated = gillespie(clock, ps, rs)[1]

# simulate manually
Random.seed!(1)
init!(clock)

for _ in clock
    growth_rate = r * (1 - μ) * v
    mutation_rate = r * μ * v
    c = r / K # baseline competition coefficient
    competition_rate = @. c * v * m * v'
    rates_sum = (sum(growth_rate), sum(mutation_rate), sum(competition_rate))
    rates_sum_acc = accumulate(+, rates_sum)
    rates_sum_sum = rates_sum_acc[end]
    τ = -log(rand()) / rates_sum_sum
    increase!(clock, τ)
    rn = rand() * rates_sum_sum
    ind = searchsortedfirst(collect(rates_sum_acc), rn)
    if ind == 1
        i = sample(growth_rate, rn)
        v[i] += 1
    elseif ind == 2
        i = sample(mutation_rate, rn - rates_sum_acc[1])
        push!(v, 1)
        n = length(v)
        resize!(m, (n, n))
        # rand competition matrix
        m[:, n] = rand(n)
        m[n, 1:n-1] = rand(1, n - 1)
    elseif ind == 3
        i = sample(competition_rate, rn - rates_sum_acc[2])[1]
        v[i] -= 1
        if v[i] == 0 # check extinction
            deleteat!(v, i)
            resize!(m, (not(i), not(i)))
        end
    end
    length(v) == 0 && break
end

@test ps_updated.v == v
@test ps_updated.m == m

@test all(map(==, getentries(ps_updated.v), getentries(v)))
@test all(map(==, values(getentries(ps_updated.m)), values(getentries(m))))

end


 _ALGO_HELP_MSG_ = """algorithm (ISRES | PRAXIS | SBPLX | SLSQP | LBFGS | AUGLAG | Newton | Anneal)

  Separate algorithms by commas to run them in succession; e.g. LBGFS,Newton

  Algorithms:
  ISRES   -   Improved stochastic ranking evolution strategy;
              nonlinear rained global optimization;
              note: requires arbitrary bounds on unknowns
  PRAXIS  -   PRincipal AXIS; gradient-free local optimization
  SBPLX   -   Subplex; variant of Nelder-Mead that is more efficient and robust;
              gradient-free local optimization
  SLSQP   -   Sequential quadratic programming for nonlineraly rained,
              gradient-based optimizationa
  LBFGS   -   low-storage BFGS; gradient-based unrained optimization
  AUGLAG  -   Augmented Lagrangian; rained optimization with LBFGS
  Newton  -   My implementation of the Newton-Raphson method
  Anneal  -   My implementation of simulated annealing

  Special syntax: try algorithm1,algorithm2 then backup_algorithms
  This first tries an algorithm that may be less robust but faster and
  if it fails then an algorithm that is more robust and slower.

  Note: in general, I've found SBPLX to be incredibly robust (for this problem).
  If you have no clue at an initial guess, my advice is to start with some
  simulated anneeling, hit with the SBPLX, and then finish it off with a little
  of Newton's method. I've tested this with intentionally pathological initial
  guesses and often converge to the solution anyway :)
""";


# Unit tests for functions in utility.jl

M_c = [complex(1,0)       complex(0,-1)
       complex(-0.5,0.5)  complex(0,0)]

M_r = [1.0   0.0   0.0  -1.0
       0.0   1.0   1.0  0.0
      -0.5  0.5    0.0  0.0
      -0.5  -0.5   0.0  0.0]

# Variable bounds to test auxiliary_variable_bounds function
l = [-2, -1, 0, 2]
u = [-1,  2, 3, 2]

@testset "Tests: complex to real matrix function" begin
    test_M_r = QCO.complex_to_real_gate(M_c)
    @test test_M_r == M_r
end

@testset "Tests: real to complex matrix function" begin
    test_M_c = QCO.real_to_complex_gate(M_r)
    @test test_M_c == M_c
end

@testset "Tests: auxiliary variable product bounds" begin
    m = Model()
    @variable(m, l[i] <= x[i=1:length(l)] <= u[i])
    # x1*x2
    @test QCO.auxiliary_variable_bounds([x[1], x[2]]) == (-4, 2)
    # x1*x3
    @test QCO.auxiliary_variable_bounds([x[1], x[3]]) == (-6, 0)
    # x1*x4
    @test QCO.auxiliary_variable_bounds([x[1], x[4]]) == (-4, -2)
    # x4*x4
    @test QCO.auxiliary_variable_bounds([x[4], x[4]]) == (4, 4)
    # x1*x2*x3
    @test QCO.auxiliary_variable_bounds([x[1], x[2], x[3]]) == (-12, 6)
    @test QCO.auxiliary_variable_bounds([x[3], x[1], x[2]]) == (-12, 6)
    @test QCO.auxiliary_variable_bounds([x[2], x[1], x[3]]) == (-12, 6)
    # x1*x2*x3*x4
    @test QCO.auxiliary_variable_bounds([x[1], x[2], x[3], x[4]]) == (-24, 12)
    @test QCO.auxiliary_variable_bounds([x[4], x[1], x[3], x[2]]) == (-24, 12)
    @test QCO.auxiliary_variable_bounds([x[4], x[3], x[2], x[1]]) == (-24, 12)
end

@testset "Tests: unique_matrices and unique_idx" begin

    D = zeros(3,3,4)
    D[:,:,1] = Matrix(LA.I,3,3)
    D[:,:,2] = rand(3,3)
    D[:,:,3] = Matrix(LA.I,3,3)
    D[:,:,4] = rand(3,3)

    D[1,1,3] = 1 + 2*tol_0
    D[3,3,3] = 1 - tol_0
    D[1,2,3] = 0 - tol_0
    D[1,3,3] = 0 + tol_0

    D[isapprox.(D, 0, atol=tol_0)] .= 0

    D_unique, D_unique_idx = QCO.unique_matrices(D)

    @test D_unique_idx == [1,2,4]
    @test D_unique[:,:,1] == D[:,:,1]
    @test D_unique[:,:,2] == D[:,:,2]
    @test D_unique[:,:,3] == D[:,:,4]

end

@testset "Tests: commuting matrices" begin
    params = Dict{String, Any}(
       "num_qubits" => 2,
       "maximum_depth" => 5,

       "elementary_gates" => ["H_1", "H_2", "CNot_1_2", "Identity"],
       "target_gate" => QCO.CNotRevGate()
       )

    data = QCO.get_data(params)
    C2,C2_Identity = QCO.get_commutative_gate_pairs(data["gates_dict"])
    @test length(C2) == 4
    @test length(C2_Identity) == 0
    @test isapprox(data["gates_real"][:,:,C2[1][1]] * data["gates_real"][:,:,C2[1][2]], data["gates_real"][:,:,C2[1][2]] * data["gates_real"][:,:,C2[1][1]], atol=tol_0)
end

@testset "Tests: kron_single_qubit_gate" begin
    I1 = QCO.kron_single_qubit_gate(4, QCO.IGate(1), "q1")
    I2 = QCO.kron_single_qubit_gate(4, QCO.IGate(1), "q2")
    I3 = QCO.kron_single_qubit_gate(4, QCO.IGate(1), "q3")
    I4 = QCO.kron_single_qubit_gate(4, QCO.IGate(1), "q4")
    @test I1 == I2 == I3 == I4
end

@testset "Tests: get_redundant_gate_product_pairs" begin

    params = Dict{String, Any}(
        "num_qubits" => 3,
        "maximum_depth" => 15,
        "elementary_gates" => ["T_1", "T_2", "T_3", "H_3", "CNot_1_2", "CNot_1_3", "CNot_2_3", "Tdagger_2", "Tdagger_3", "Identity"],
        "target_gate" => QCO.ToffoliGate()
        )
    data = QCO.get_data(params)
    redundant_pairs = QCO.get_redundant_gate_product_pairs(data["gates_dict"])
    @test length(redundant_pairs) == 0

end

@testset "Tests: get_idempotent_gates" begin

    params = Dict{String, Any}(
        "num_qubits" => 2,
        "maximum_depth" => 14,
        "elementary_gates" => ["Y_1", "Y_2", "Z_1", "Z_2", "T_2", "Tdagger_1", "Sdagger_1", "SX_1", "SXdagger_2", "CNot_2_1", "CNot_1_2", "Identity"],
        "target_gate" => -QCO.HCoinGate())

    data = QCO.get_data(params)
    idempotent_gates = QCO.get_idempotent_gates(data["gates_dict"])
    @test idempotent_gates[1] == 6
    @test idempotent_gates[2] == 7
    @test "Tdagger_1" in data["gates_dict"]["$(idempotent_gates[1])"]["type"]
    @test "Sdagger_1" in data["gates_dict"]["$(idempotent_gates[2])"]["type"]
    @test "Z_1" in data["gates_dict"]["3"]["type"]

    M1 = data["gates_dict"]["$(idempotent_gates[1])"]["matrix"]
    M2 = data["gates_dict"]["$(idempotent_gates[2])"]["matrix"]

    @test isapprox(M1^2, data["gates_dict"]["7"]["matrix"], atol = tol_0)
    @test isapprox(M2^2, data["gates_dict"]["3"]["matrix"], atol = tol_0)
end

@testset "Tests: _parse_gate_string" begin
    v1 = QCO._parse_gate_string("RX_1", qubits=true)
    @test v1[1] == 1

    v2 = QCO._parse_gate_string("CU3_51", qubits=true)
    @test v2[1] == 51

    v3 = QCO._parse_gate_string("CRZ_9_1", qubits=true)
    @test length(v3) == 2
    @test v3[1] == 9
    @test v3[2] == 1
end

@testset "Tests: is_gate_real" begin
    M_test = [0 1E-7im -1E-7im; 1E-10im 0 0; -1E-8im -1E-10im 0]
    @test QCO.is_gate_real(M_test)

    M_test = [0 1E-7im -1E-7im; 1E-10im 0 0; -1E-8im -1E-5im 0]
    @test !QCO.is_gate_real(M_test)
end

@testset "Tests: _get_constraint_slope_intercept" begin
    v1 = (0.0, 0.0)
    v2 = (1.0, 1.0)
    m,c = QCO._get_constraint_slope_intercept(v1, v2)
    @test m == 1
    @test c == 0

    m,c = QCO._get_constraint_slope_intercept(v2, v1)
    @test m == 1
    @test c == 0

    v2 = (0, 0.5)
    m,c = QCO._get_constraint_slope_intercept(v1, v2)
    @test !isfinite(m)
    @test !isfinite(c)

    v1 = (1.0, 2.0)
    v2 = (-3.0, 4.0)
    m,c = QCO._get_constraint_slope_intercept(v1, v2)
    @test m == -0.5
    @test c == 2.5

    QCO._get_constraint_slope_intercept(v1, v1)
end


@testset "6.3 Hyperbolic tangent" begin
    include("6.3.1 (c+d x)^m (a+b tanh)^n.jl")
    include("6.3.2 Hyperbolic tangent functions.jl")
    include("6.3.7 (d hyper)^m (a+b (c tanh)^n)^p.jl")
end

test/6 Hyperbolic functions/6.3 Hyperbolic tangent/.jl


using HOODESolver
using LinearAlgebra
using Plots
using Plots.PlotMeasures
function getmindif(tab::Vector{Vector{BigFloat}})
    nmmin = Inf
    ret = (0, 0)
    for i=1:size(tab,1)
        for j=(i+1):size(tab,1)
            nm = norm(tab[i]-tab[j], Inf)
            if nm < nmmin
                nmmin = nm
                ret = i, j
            end
        end
    end
    return ret
end
function fctmain(n_tau, prec)
    setprecision(prec)
    u0=BigFloat.([90, -44, 83, 13]//100)
    t_max = big"1.0"
    epsilon=big"0.0017"
    println("epsilon=$epsilon")
    nbmaxtest=12
    ordmax=17
    debord=3
    pasord=2
    y = ones(Float64, nbmaxtest, div(ordmax-debord,pasord)+1 )
    res_gen = Array{ Array{BigFloat,1}, 2}(undef, nbmaxtest, div(ordmax-debord,pasord)+1 )
    x=zeros(Float64,nbmaxtest)
    A = [0 0 1 0; 0 0 0 0;-1 0 0 0; 0 0 0 0]
    prob = HOODEProblem(henon_heiles, u0, (big"0.0",t_max), missing, A, epsilon)
    tabsol = Array{Array{BigFloat,1},1}(undef,0)
    println("ordmax=$ordmax")
    indref = 1
    ind=1
    for order=debord:pasord:ordmax
        println("eps=$epsilon order=$order")
        nb = 100
        indc = 1
        labels=Array{String,2}(undef, 1, div(order-debord, pasord)+1)
        resnorm=0
        resnormprec=1
        sol =undef
        println("preparation ordre $order + 2")
        par_u0 = missing
        borindc = (order < ordmax) ? nbmaxtest : nbmaxtest+1
        while indc <= borindc
            if indc > nbmaxtest
                nb = 51*2^nbmaxtest
            end
            res = solve(prob, nb_tau=n_tau, order=order, nb_t=nb,par_u0=par_u0, dense=false)
            par_u0=res.par_u0
            sol = res[end]
            push!(tabsol, sol)
            if indc <= nbmaxtest
                res_gen[indc,ind] = sol
                x[indc] = 1.0/nb
            end
            (a, b) = getmindif(tabsol)
            if a != 0
                indref = a
                solref = tabsol[b]
                for i=1:ind
                    borne = (i <ind) ? size(res_gen,1) : min(indc, size(res_gen,1))
                    for j = 1:borne
                        nm = norm(res_gen[j,i] - solref, Inf)
                        if nm == 0
                            nm = norm(res_gen[j,i] - tabsol[a], Inf)
                        end
                        y[j,i] = (nm < 1) ? nm : NaN
                   end
                end
            end
            println("result=$y")
            println("log2(y)=$(log2.(y))")
            nb *= 2
            indc += 1
        end
        for i=debord:pasord:order
            labels[1,(i-debord)÷pasord+1] = " order = $i "
        end
        p=Plots.plot(
                        x,
                        view(y,:,1:ind),
                        xlabel="Δt",
                        xaxis=:log,
                        ylabel="error",
                        yaxis=:log,
                        legend=:bottomright,
                        label=labels,
                        marker=2
                    )
        pp=Plots.plot(
                        x,
                        view(y,:,1:ind),
                        xlabel="Δt",
                        xaxis=:log,
                        ylabel="error",
                        yaxis=:log,
                        legend=:bottomright,
                        label=labels,
                        marker=2,
                        bottom_margin=30px,
                        left_margin=60px
            )
        prec_v = precision(BigFloat)
        eps_v = convert(Float32,epsilon)
        Plots.savefig(p,"out/res4p_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_henon_heiles.pdf")
        Plots.savefig(p,"out/res4p_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_henon_heiles.png")
        Plots.savefig(pp,"out/res4pp_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_henon_heiles.pdf")
        Plots.savefig(pp,"out/res4pp_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_henon_heiles.png")
        ind+= 1
    end
end

# testODESolver()

# for i=3:9
#     fctMain(2^i)
# end
fctmain(32,512)


# code for FastTransforms.jl until that package is compatible with Polynomials v2.0.0

## Use FastTransform for conversion, as  possible
## FastTransforms.kind2string.(0:15)
# Legendre<--Chebyshev
function _convert(::Type{Q}, p::P) where {
    T <: AbstractFloat,
    Q <: Union{Legendre, OrthonormalLegendre},
    P <: Union{Chebyshev{T}, OrthonormalChebyshev{T}}}
    ps = coeffs(p)
    Q(cheb2leg(ps, normcheb=isorthonormal(P), normleg=isorthonormal(Q)))
end

# Chebyshev<--Legendre
function _convert(::Type{Q}, p::P) where {
    T <: AbstractFloat,
    Q <: Union{Chebyshev, OrthonormalChebyshev},
    P <: Union{Legendre{T}, OrthonormalLegendre{T}}
}
    ps =  coeffs(p)
    Q(leg2cheb(ps, normleg=isorthonormal(P), normcheb=isorthonormal(Q)) )
end

# ultraspherical<--ultraspherical
function _convert(::Type{Q}, p::P) where {
    β, α,  T <: AbstractFloat,
    Q <: Union{Gegenbauer{β}, OrthonormalGegenbauer{β}},
    P <: Union{Gegenbauer{α, T}, OrthonormalGegenbauer{α, T}}
}

    ps =  coeffs(p)
    Q( ultra2ultra(ps, α, β, norm1=isorthonormal(P), norm2=isorthonormal(Q)) )

end

# Jacobi<--Jacobi
function _convert(::Type{Q}, p::P) where {
    γ, δ, α, β,  T <: AbstractFloat,
    Q <: Union{Jacobi{γ, δ}, OrthonormalJacobi{γ, δ}},
    P <: Union{Jacobi{α, β, T}, OrthonormalJacobi{α, β, T}}
}

    ps =  coeffs(p)
    Q( jac2jac(ps, α, β, γ, δ,  norm1=isorthonormal(P), norm2=isorthonormal(Q)) )

end

# Laguerre<--Laguerre
function _convert(::Type{Q}, p::P) where {
    α, β,  T <: AbstractFloat,
    Q <: Union{Laguerre{β}, OrthonormalLaguerre{β}},
    P <: Union{Laguerre{α, T}, OrthonormalLaguerre{α, T}}
}

    ps =  coeffs(p)
    Q( lag2lag(ps, α, β,  norm1=isorthonormal(P), norm2=isorthonormal(Q)) )

end

# Jacobi<--ultraspherical
function _convert(::Type{Q}, p::P) where {
    γ, δ, α,  T <: AbstractFloat,
    Q <: Union{Jacobi{γ, δ}, OrthonormalJacobi{γ, δ}},
    P <: Union{Gegenbauer{α, T}, OrthonormalGegenbauer{α, T}}
}

    ps =  coeffs(p)
    Q( ultra2jac(ps, α, γ, δ,  normultra=isorthonormal(P), normjac=isorthonormal(Q)) )

end

# ultraspherical<--Jacobi
function _convert(::Type{Q}, p::P) where {
    α, γ, δ,  T <: AbstractFloat,
    Q <: Union{Gegenbauer{α, T}, OrthonormalGegenbauer{α, T}},
    P <: Union{Jacobi{γ, δ}, OrthonormalJacobi{γ, δ}}
}

    ps =  coeffs(p)
    Q( jac2ultra(ps,γ, δ, α, normjac=isorthonormal(P), normultra=isorthonormal(Q)) )

end

# Jacobi<--Chebyshev
function _convert(::Type{Q}, p::P) where {
    γ, δ,  T <: AbstractFloat,
    Q <: Union{Jacobi{γ, δ}, OrthonormalJacobi{γ, δ}},
    P <: Union{Chebyshev{T}, OrthonormalChebyshev{T}}
}

    ps =  coeffs(p)
    Q( cheb2jac(ps,γ, δ, normcheb=isorthonormal(P), normjac=isorthonormal(Q)) )

end

# Chebyshev<--Jacobi
function _convert(::Type{Q}, p::P) where {
    γ, δ,  T <: AbstractFloat,
    Q <: Union{Chebyshev, OrthonormalChebyshev},
    P <: Union{Jacobi{γ, δ,T}, OrthonormalJacobi{γ, δ,T}}
}

    ps =  coeffs(p)
    Q( jac2cheb(ps,γ, δ, normjac = isorthonormal(P), normcheb=isorthonormal(Q)) )

end

# ultraspherical<--Chebyshev
function _convert(::Type{Q}, p::P) where {
    α,  T <: AbstractFloat,
    Q <: Union{Gegenbauer{α}, OrthonormalGegenbauer{α}},
    P <: Union{Chebyshev{T}, OrthonormalChebyshev{T}}
}

    ps =  coeffs(p)
    Q( cheb2ultra(ps,α, normcheb=isorthonormal(P), normultra=isorthonormal(Q)) )

end

# Chebyshev<--ultraspherical
function _convert(::Type{Q}, p::P) where {
    α,  T <: AbstractFloat,
    Q <: Union{Chebyshev, OrthonormalChebyshev},
    P <: Union{Gegenbauer{α,T}, OrthonormalGegenbauer{α,T}}
}
    ps =  coeffs(p)
    Q( ultra2cheb(ps,α, normultra=isorthonormal(P), normcheb=isorthonormal(Q)) )

end


using TensorOperations

function make4(a1,a2,a3,a4,a12,a34,a1234)
   t1 = HTensorNode(a1)
   t2 = HTensorNode(a2)
   t3 = HTensorNode(a3)
   t4 = HTensorNode(a4)
   t12 = HTensorNode(a12, [t1,t2])
   t34 = HTensorNode(a34, [t3,t4])
   t1234 = HTensorNode(a1234, [t12,t34])
   return t1234
end

# B[i,j,k] = A[a,b,k] U[i,a] V[j,b]
function AxUV(A, U, V)
   T = eltype(A)
   na, nb, nk = size(A)
   ni = size(U,1)
   nj = size(V,1)
   @assert size(U,2) == na
   @assert size(V,2) == nb
   tmp = Array{T}(ni, nb)
   B = zeros(T, ni, nj, nk)
   for k=1:nk
      fill!(tmp, zero(T))
      for b=1:nb, a=1:na, i=1:ni
         tmp[i,b] += A[a,b,k] * U[i,a]
      end
      for b=1:nb, j=1:nj, i=1:ni
         B[i,j,k] += tmp[i,b] * V[j,b]
      end
   end
   return B
end

function cost(u1,u2,u3,u4,u12,u34,u1234,target)
   a12 = AxUV(u12, u1, u2)
   a34 = AxUV(u34, u3, u4)
   n1, n2, n12 = size(a12)
   n3, n4, n34 = size(a34)
   a1234 = AxUV( u1234, reshape(a12, n1*n2, n12), reshape(a34, n3*n4, n34))
   guess = reshape(a1234, n1, n2, n3, n4, 1)
   return sum(abs2, guess-target)
end


# Copyright 2014 Mitchell Kember. Subject to the MIT License.
# Google Code Jam Qualification Round 2014
# Problem C: Minesweeper Master

# Computes a minesweeper grid with, r rows, c columns, and m mines that can be
# won win a single click on the cell labelled 'c'.
function oneclickgrid(r, c, m)
	if m == 0
		grid = fill('.', r, c)
		grid[1,1] = 'c'
		return grid
	end
	if r <= 2 && c <= 2
		return nothing
	end
	ns = r*c - m
	grid = fill('*', r, c)
	grid[1,1] = '.'
	if solve!(grid, ns)
		grid[1,1] = 'c'
		return grid
	end
	if r != 1 && c != 1
		fill!(grid, '*')
		grid[2,2] = '.'
		if solve!(grid, ns)
			grid[2,2] = 'c'
			return grid
		end
	end
	if r >= 3 && c >= 3
		fill!(grid, '*')
		grid[3,3] = '.'
		if solve!(grid, ns)
			grid[3,3] = 'c'
			return grid
		end
	end
	nothing
end

# Attempts to solve the grid so that there are ns safe cells which will all be
# revealed with a one click on the 'c' cell. Returns true if sucessful.
function solve!(g, ns)
	sz = size(g)
	nsc = 1
	while nsc < ns
		ind = (0,0)
		smallest = 9
		for i = 1:sz[1], j = 1:sz[2]
			if g[i,j] == '.'
				n = nmines(g, i, j)
				if nsc + n == ns
					demine_neighbours!(g, i, j)
					return true
				end
				if n > 0 && n < smallest
					ind = (i,j)
					smallest = n
				end
			end
		end
		if smallest == 9
			return false
		end
		demine_neighbours!(g, ind[1], ind[2])
		nsc += smallest
	end
	false
end

# Returns the number of mines neibouring the cell at (i,j).
function nmines(g, i, j)
	sz = size(g)
	f(x,y) = (x >= 1 && y >= 1 && x <= sz[1] && y <= sz[2] && g[x,y] == '*')?1:0
	+(f(i-1,j-1),f(i-1,j),f(i-1,j+1),f(i,j-1),f(i,j+1),f(i+1,j-1),f(i+1,j),f(i+1,j+1))
end

# Removes mines from cells neighbouring (i,j).
function demine_neighbours!(g, i, j)
	sz = size(g)
	f(x,y) = (x >= 1 && y >= 1 && x <= sz[1] && y <= sz[2])?g[x,y]='.':nothing
	f(i-1,j-1);f(i-1,j);f(i-1,j+1);f(i,j-1);f(i,j+1);f(i+1,j-1);f(i+1,j);f(i+1,j+1)
end

# Converts a grid, or nothing, to a string.
function message(grid)
	if grid === nothing
		"Impossible"
	else
		join(map(i -> join(grid[i,:]), 1:size(grid, 1)), '\n')
	end
end

function main()
	n = int(readline())
	for i = 1:n
		r, c, m = int(split(readline()))
		grid = oneclickgrid(r, c, m)
		println("Case #$i:")
		println(message(grid))
	end
end

main()


using Hecke

boundexp = ARGS[1]
startfield = ARGS[2]
number = ARGS[3]

sprint_formatted(fmt, args...) = @eval @sprintf($fmt, $(args...))

if length(ARGS) == 4
  basename = ARGS[4]
else
  basename = ""
end

boundexp = parse(Int, boundexp)
startfield = parse(Int, startfield)
number = parse(Int, number)

@assert iseven(boundexp)

boundquad = div(boundexp, 2)

lall = Hecke.quadratic_extensions(10^boundquad)

l = Hecke.quadratic_extensions(10^boundquad, u = startfield:(startfield + number - 1))

width = length(string(length(lall)))

for (i, K) in enumerate(l)
  z = Hecke.D5_extensions(fmpz(10)^boundexp, [K])
  if length(z) != 0
    fname = basename * sprint_formatted("%0$(width)d", startfield + i - 1)
    @show fname
    file = open(fname, "a")
    write(file, "# $(K.pol)\n")
    for (p, D) in z
      write(file, "($p, $D)\n")
    end
    close(file)
  end
end


# DESCRIPTION
# Runge-Kutta 4 integration scheme.
#
# INPUT
# - fdyn: name of system dynamics function
# - finp: name of control input function
# - t: time
# - dt: time step
# - x_curr: current state vector
# - u0: initial control vector
#
# OUTPUT
# - x_new: new state vector after integration
#
# ------------------------------------------------------------------------------

function rk4(fdyn,finp,t,dt,x_curr,u0)
    #
    u=finp(t)+u0
    xd, y = fdyn(x_curr,u)
    k1=dt*xd
    #
    u=finp(t+0.5*dt)+u0
    xd, y = fdyn(x_curr+0.5*k1,u)
    k2=dt*xd
    #
    xd, y = fdyn(x_curr+0.5*k2,u)
    k3=dt*xd
    #
    u=finp(t+dt)+u0
    xd, y = fdyn(x_curr+k3,u)
    k4=dt*xd
    # weighted average
    x_new=x_curr+k1/6+k2/3+k3/3+k4/6
    #
    return x_new
end


#=
# 2D DG poisson
=#

### If the Finch package has already been added, use this line #########
using Finch # Note: to add the package, first do: ]add "https://github.com/paralab/Finch.git"

### If not, use these four lines (working from the examples directory) ###
# if !@isdefined(Finch)
#     include("../Finch.jl");
#     using .Finch
# end
##########################################################################

init_finch("dgpoisson3d");

@useLog("dgpoisson3dlog")

@domain(3)
@solver(DG)
@functionSpace(LEGENDRE, 1)

n = 12;
@mesh(HEXMESH, n)

@variable(u)
@testSymbol(v)

@boundary(u, 1, DIRICHLET, 0)

@coefficient(f, "-3*pi*pi*sin(pi*x)*sin(pi*y)*sin(pi*z)")
@coefficient(beta, 100000*n)
@weakForm(u, "-dot(grad(u), grad(v)) - f*v + surface(dot(ave(grad(u)), jump(v)) + dot(ave(grad(v)), jump(u)) - beta*dot(jump(u), jump(v)))")

solve(u);

# # solution is stored in the variable's "values"
# using Plots
# pyplot();
# display(plot(Finch.grid_data.allnodes[1,:], Finch.grid_data.allnodes[2,:], u.values[:], st=:surface))

# exact solution is sin(pi*x)*sin(pi*y)*sin(pi*z)
# check error
maxerr = 0;
exact(x,y,z) = sin(pi*x)*sin(pi*y)*sin(pi*z);

for i=1:size(Finch.grid_data.allnodes,2)
    x = Finch.grid_data.allnodes[1,i];
    y = Finch.grid_data.allnodes[2,i];
    z = Finch.grid_data.allnodes[3,i];
    err = abs(u.values[i] - exact(x,y,z));
    global maxerr;
    maxerr = max(err,maxerr);
end
println("max error = "*string(maxerr));

@finalize()


using Test
using QuantumOpticsBase
using SparseArrays, LinearAlgebra

@testset "metrics" begin

b1 = SpinBasis(1//2)
b2 = FockBasis(6)

psi1 = spinup(b1) ⊗ coherentstate(b2, 0.1)
psi2 = spindown(b1) ⊗ fockstate(b2, 2)

rho = tensor(psi1, dagger(psi1))
sigma = tensor(psi2, dagger(psi2))

# tracenorm
@test tracenorm(0*rho) ≈ 0.
@test tracenorm_h(0*rho) ≈ 0.
@test tracenorm_nh(0*rho) ≈ 0.

@test tracenorm(rho) ≈ 1.
@test tracenorm_h(rho) ≈ 1.
@test tracenorm_nh(rho) ≈ 1.

@test_throws ArgumentError tracenorm(sparse(rho))
@test_throws ArgumentError tracenorm_h(sparse(rho))
@test_throws ArgumentError tracenorm_nh(sparse(rho))

# tracedistance
@test tracedistance(rho, sigma) ≈ 1.
@test tracedistance_h(rho, sigma) ≈ 1.
@test tracedistance_nh(rho, sigma) ≈ 1.

@test tracedistance(rho, rho) ≈ 0.
@test tracedistance_h(rho, rho) ≈ 0.
@test tracedistance_nh(rho, rho) ≈ 0.

@test tracedistance(sigma, sigma) ≈ 0.
@test tracedistance_h(sigma, sigma) ≈ 0.
@test tracedistance_nh(sigma, sigma) ≈ 0.

@test_throws ArgumentError tracedistance(sparse(rho), sparse(rho))
@test_throws ArgumentError tracedistance_h(sparse(rho), sparse(rho))
@test_throws ArgumentError tracedistance_nh(sparse(rho), sparse(rho))

# tracedistance
@test tracedistance(rho, sigma) ≈ 1.
@test tracedistance(rho, rho) ≈ 0.
@test tracedistance(sigma, sigma) ≈ 0.

rho = spinup(b1) ⊗ dagger(coherentstate(b2, 0.1))
@test_throws ArgumentError tracedistance(rho, rho)
@test_throws ArgumentError tracedistance_h(rho, rho)

@test tracedistance_nh(rho, rho) ≈ 0.

# entropy
rho_mix = dense(identityoperator(b1))/2.
@test entropy_vn(rho_mix)/log(2) ≈ 1
psi = coherentstate(FockBasis(20), 2.0)
@test isapprox(entropy_vn(psi), 0.0, atol=1e-8)

# fidelity
rho = tensor(psi1, dagger(psi1))
@test fidelity(rho, rho) ≈ 1
@test 1e-20 > abs2(fidelity(rho, sigma))

# ptranspose
e = spinup(b1)
g = spindown(b1)
psi3 = (e ⊗ g - g ⊗ e)/sqrt(2)

rho = dm(psi3)
rho_pT1 = ptranspose(rho, 1)
rho_pT1_an = 0.5*(dm(e ⊗ g) + dm(g ⊗ e) - (e ⊗ e) ⊗ dagger(g ⊗ g) - (g ⊗ g) ⊗ dagger(e ⊗ e))
rho_pT2 = ptranspose(rho, 2)
@test rho_pT1.data ≈ rho_pT1_an.data
@test rho_pT2.data ≈ rho_pT1_an.data

@test_throws MethodError ptranspose(e ⊗ dagger(psi1))
@test_throws MethodError ptranspose(dm(e))

rho = rho ⊗ dm(g)
@test PPT(rho, 1) == PPT(rho, 2) == false
@test PPT(rho, 3)

@test negativity(rho, 1) == negativity(rho, 2) ≈ 0.5
@test isapprox(negativity(rho, 3), 0.0, atol=1e-15)

@test logarithmic_negativity(rho, 1) == logarithmic_negativity(rho, 2) ≈ 1.0
@test isapprox(logarithmic_negativity(rho, 3), 0.0, atol=1e-15)

# Entanglement Entropy
b1 = SpinBasis(1//2)
psi = 1/sqrt(2)*(spinup(b1)⊗spindown(b1) + spindown(b1)⊗spinup(b1))
rho_ent = dm(psi)
rho_mix = DenseOperator(rho_ent.basis_l, diagm(ComplexF64[1.0,1.0,1.0,1.0]))
@test entanglement_entropy(rho_mix, 1) ≈ 0.0
@test entanglement_entropy(rho_ent, 1, entropy_vn) ≈ 2 * log(2)
@test entanglement_entropy(rho_ent, 1) ≈ 2 * entanglement_entropy(psi, 1)
@test_throws ArgumentError entanglement_entropy(rho_mix, (1,2))
@test_throws ArgumentError entanglement_entropy(rho_mix, 3)

q2 = PauliBasis(2)
CNOT = DenseOperator(q2, q2, diagm(0 => [1,1,0,0], 1 => [0,0,1], -1 => [0,0,1]))
CNOT_sop = SuperOperator(CNOT)
CNOT_chi = ChiMatrix(CNOT)
CNOT_ptm = PauliTransferMatrix(CNOT)

@test avg_gate_fidelity(CNOT_sop, CNOT_sop) == 1
@test avg_gate_fidelity(CNOT_chi, CNOT_chi) == 1
@test avg_gate_fidelity(CNOT_ptm, CNOT_ptm) == 1

@test_throws MethodError avg_gate_fidelity(CNOT_sop, CNOT_chi)
@test_throws MethodError avg_gate_fidelity(CNOT_sop, CNOT_ptm)
@test_throws MethodError avg_gate_fidelity(CNOT_chi, CNOT_ptm)

end # testset