export SolitaryWaveWhithamGreenNaghdi, SolitaryWGN

"""
    SolitaryWaveWhithamGreenNaghdi(param; kwargs)

Compute the Whitham-Green-Naghdi solitary wave with prescribed velocity.

# Arguments
- `param :: NamedTuple`: parameters of the problem containing velocity `c` \
and dimensionless parameters `ϵ` and `μ`, \
and mesh size `L` and number of collocation points `N`;
## Keywords (optional)
- `guess :: Vector{Real}`: initial guess for the surface deformation (if not provided, the exact formula for SGN is used);
- `x₀ :: Real`: center of solitary wave (if guess is not provided);
- `SGN :: Bool`: if `true` computes the Serre-Green-Naghdi (instead of Whitham-Green-Naghdi) solitary wave (consider `SolitaryWaveSerreGreenNaghdi` instead);
- `method :: Int`: equation used (between `1` and `4`);
- `iterative :: Bool`: inverts Jacobian through GMRES if `true`, LU decomposition if `false` (default is `false`);
- `verbose :: Bool`: prints numerical errors at each step if `true` (default is `false`);
- `max_iter :: Int`: maximum number of iterations of the Newton algorithm (default is `20`);
- `tol :: Real`: relative tolerance measured in ℓ∞ norm (default is `1e-10`);
- `ktol :: Real`: tolerance of the Krasny filter (default is `0`, i.e. no filtering);
- `gtol :: Real`: relative tolerance of the GMRES algorithm (default is `1e-10`);
- `dealias :: Int`: dealiasing with Orlicz rule `1-dealias/(dealias+2)` (default is `0`, i.e. no dealiasing);
- `q :: Real`: Newton algorithm modified with `u_{n+1}=q*u_{n+1}+(1-q)*u_n` (default is `1`);
- `α :: Real`: adds `α` times spectral projection onto the Kernel to the Jacobian (default is `0`).

# Return values
`(η,u,v,mesh)` with
- `η :: Vector{Float64}`: surface deformation;
- `u :: Vector{Float64}`: layer-averaged velocity;
- `v :: Vector{Float64}`: derivative of the velocity potential at the surface;
- `mesh :: Mesh`: mesh collocation points.

"""
function SolitaryWaveWhithamGreenNaghdi(
                param :: NamedTuple;
                guess = zeros(0) :: Vector{Float64},
                x₀ = 0 :: Real,
                SGN = false :: Bool,
                method = 2 :: Int,
                iterative = false :: Bool,
                verbose = false :: Bool,
                max_iter = 20 :: Int,
                tol = 1e-10 :: Real,
                ktol = 0 :: Real,
                gtol = 1e-10 :: Real,
                dealias = 0 :: Int,
                q=1 :: Real,
                α=0 :: Real
                        )

        c = param.c
        ϵ = param.ϵ
        μ = param.μ
        if abs(c)<1
                @error("The velocity must be greater than 1 (in absolute value).")
        end

        mesh = Mesh(param)

        if guess == zeros(0)
                # Using the exact formula for the SGN solitary wave as initial guess (if not provided)
                guess = (c^2-1)/ϵ*sech.(sqrt(3*(c^2-1)/(c^2)/μ)/2*(mesh.x.-x₀)).^2
        end

        k = mesh.k

        Dx       =  1im * k
        if SGN == true
                F₀ = √μ*Dx #./ (1 .+ μ/3 * k.^2 ).^(1/4)
        else
                F₁ 	= tanh.(sqrt(μ)*abs.(k))./(sqrt(μ)*abs.(k))
                F₁[1] 	= 1
                F₀       = 1im * sqrt.(3*(1 ./F₁ .- 1)).*sign.(k)
        end

        if dealias == 0
                Π = ones(size(k)) # no dealiasing (Π=Id)
        else
                K = (mesh.kmax-mesh.kmin)/(2+dealias)
                Π = abs.(k) .<= K # Dealiasing low-pass filter
        end

        krasny(k) = (abs.(k).> ktol ).*k
        krasny!(k) = k[abs.(k).< ktol ].=0

        function filter( v :: Vector{Float64} )
                real.(ifft(krasny(Π.*fft(v))))
        end
        function filter( v :: Vector{Complex{Float64}} )
                ifft(krasny(Π.*fft(v)))
        end
        function F( v , hv, Fv, F2v )
                if method == 1
                        return -1/3 ./ hv.^2 .* F2v .+
                                ϵ/2 .* (hv.*Fv).^2 .+
                                v./hv .- 1/(c^2) .*v .- ϵ/2 .* (v./hv).^2
                elseif method == 2
                        return -c/3 ./ hv.^2 .* F2v .+
                                ϵ/2 .* Fv.^2 .+
                                c*v .- 1/c .*v.*hv .- ϵ/2 .* v.^2
                elseif method == 3
                        return -1/3 ./ hv.^2 .* F2v .+
                                ϵ/2 .* Fv.^2 .+
                                v .- 1/(c^2) .*v.*hv .- ϵ/2 .* v.^2
                elseif method == 4
                        return (-1/3 .* F2v .+
                                ϵ/2 .*  hv.^4 .* Fv.^2 .+
                                hv.^2 .* (v .- ϵ/2 .* v.^2) .-
                                1/(c^2) .* hv.^3 .* v)/c^4
                end
        end

        function Fabs( v , hv, Fv, F2v )
                if method == 1
                        return abs.(1/3 ./ hv.^2 .* F2v) .+
                                abs.(ϵ/2 .* (hv.*Fv).^2 ) .+
                                abs.(v./hv) .- 1/(c^2) .*abs.(v) .+ ϵ/2 .* abs.((v./hv).^2)
                elseif method == 2
                        return abs.(c/3 ./ (hv.^2).* F2v) .+
                                abs.(ϵ/2 .* Fv.^2) .+
                                abs.(c*v) .+ abs.(1/c .*v.*hv) .+ abs.(ϵ/2 .* v.^2)
                elseif method == 3
                        return abs.(1/3 ./ (hv.^2).* F2v) .+
                                abs.(ϵ/2 .* Fv.^2) .+
                                abs.(v) .+ abs.(1/(c^2) .*v.*hv) .+ abs.(ϵ/2 .* v.^2)
                elseif method == 4
                        return (abs.(-1/3 .* F2v) .+
                                abs.(ϵ/2 .*  hv.^4 .* Fv.^2) .+
                                abs.( hv.^2 .*v) .+ abs.( ϵ/2 .* hv.^2 .* v.^2) .+
                                1/(c^2) .* abs.(hv.^3 .* v))/c^4
                end
        end
        if iterative == false
                x = mesh.x
                x₀ = mesh.x[1]
                FFT = exp.(-1im*k*(x.-x₀)');
                IFFT = exp.(1im*k*(x.-x₀)')/length(x);
                #Id = Diagonal(ones(size(x)));
                M₀ = IFFT * Diagonal( F₀ )* FFT
                M(v) = Diagonal( v )
                function JacF( v , hv, Fv , F2v, Dv, dxv )
                        if method == 1
                                return Symmetric(real.(
                                        -1/3 *M(1 ./ hv.^2)* M₀ * M(hv.^3)* ( M₀ * M( 1 ./ hv.^2 ) .+ 3* ϵ * M( Fv ./hv ) )
                                        .+ ϵ * M( hv.^2 .* Fv ) * M₀ * M( 1 ./ hv.^2 )
                                        .+ M( Dv ) .+ α*dxv*dxv' ))
                        elseif method == 2
                                return real.(
                                        -1/3 *M(1 ./ hv.^2 )* M₀ * M(hv.^3)* (c*M₀ .+ 3*ϵ * M( Fv ))
                                        .+ ϵ * M( hv .* Fv ) * M₀
                                        #.+ M(1 ./hv) .+ 2*ϵ/3 * M(F2v ./ hv) .+ ϵ^2 * M(hv .* Fv.^2) .- M((hv/c).^2)
                                        .+ M( Dv ) .+ α*M( hv.^2 )*dxv*dxv' *M(1 ./ hv.^2 ) )
                        elseif method == 3
                                return real.(
                                        -1/3 *M(1 ./ hv.^2 )* M₀ * M(hv.^3)* (M₀ .+ 3*ϵ * M( Fv ))
                                        .+ ϵ * M( hv .* Fv ) * M₀
                                        #.+ M(1 ./hv) .+ 2*ϵ/3 * M(F2v ./ hv) .+ ϵ^2 * M(hv .* Fv.^2) .- M((hv/c).^2)
                                        .+ M( Dv ) .+ α*M( hv.^2 )*dxv*dxv' *M(1 ./ hv.^2 ) )
                        elseif method == 4
                                return Symmetric(real.(
                                ( M₀ * M(hv.^3 ) * ( -1/6*M₀ .- ϵ * M( hv .* Fv ) ) .+
                                 ( -1/6*M₀ .+ ϵ * M( hv .* Fv ) ) * M( hv.^3  ) * M₀ .+
                                 M(hv.^2)*M( Dv ))/c^4 .+ α*dxv*dxv'
                                ))
                        end
                end
        else
                Four(v) = (ifft(F₀.*fft(v)))
                function JacFfast( v , hv, Fv , F2v, Dv, dxv )
                        function dF(φ)
                                if method == 1
                                 return fft( -1/3 ./ hv.^2 .* Four( hv.^3 .*
                                                ( Four( ifft(φ) ./ hv.^2 ) .+ 3* ϵ * ifft(φ) .* Fv ./hv )  )
                                        .+ ϵ .* hv.^2 .* Fv .* Four( ifft(φ) ./ hv.^2 )
                                         .+ ifft(φ) .* Dv .+ α*dot(dxv,ifft(φ))*dxv )
                                 elseif method == 2
                                  return fft( -1/3 ./ hv.^2 .* Four( hv.^3 .* ( c*Four( ifft(φ) ) .+ 3*ϵ*Fv.*ifft(φ) ))
                                          .+ ϵ .* hv .* Fv .* Four( ifft(φ) )
                                          .+ ifft(φ) .* Dv .+ α*dot(dxv, ifft(φ) ./ hv.^2)*hv.^2 .*dxv )
                                  elseif method == 3
                                   return fft( -1/3 ./ hv.^2 .* Four( hv.^3 .* ( Four( ifft(φ) ) .+ 3*ϵ*Fv.*ifft(φ) ))
                                           .+ ϵ .* hv .* Fv .* Four( ifft(φ) )
                                           .+ ifft(φ) .* Dv .+ α*dot(dxv, ifft(φ) ./ hv.^2)*hv.^2 .*dxv )
                                elseif method == 4
                                 return fft(( -1/3  .* Four( hv.^3 .* ( Four( ifft(φ) ) .+ 3*ϵ*Fv.*hv.*ifft(φ) ))
                                        .+ ϵ .* hv.^4 .* Fv .* Four( ifft(φ) )
                                        .+ ifft(φ) .* hv.^2 .* Dv)/c^4 .+ α*dot(dxv,ifft(φ))*dxv)
                                end
                         end
                if method == 1 || method == 4
                        return LinearMap(dF, length(v); issymmetric=true, ismutating=false)
                else
                        return LinearMap(dF, length(v); issymmetric=false, ismutating=false)
                end
                end

        end
#------ Iteration scheme

        # initial guess for iteration
        if method == 1
                u = filter(guess)
        elseif method == 2
                u = c*filter(guess./(1 .+ ϵ*guess))
        elseif method == 3 || method == 4
                u = filter(guess./(1 .+ ϵ*guess))
        end

        du,fu,dxu,hu,Fu,F2u,Du = (similar(u),).*ones(7)

        for i in range(0, stop=max_iter)
                dxu .= real.(ifft(Dx.*fft(u)))
                dxu ./= norm(dxu,2)
                if method == 1
                        hu .= 1 .+ϵ*u
                        Fu .= real.(ifft(F₀.*fft(u./hu)))
                        F2u .= real.(ifft(F₀.*fft( hu.^3 .* Fu ) ))
                        Du .= 1 ./ hu.^3 .+ 2*ϵ/3 ./ hu.^3 .* F2u .+ ϵ^2 * hu.* Fu.^2 .- 1/c^2
                elseif method == 2
                        hu .= c ./(c .- ϵ*u)
                        Fu .= hu.* real.(ifft(F₀.*fft(u)))
                        F2u .= real.(ifft(F₀.*fft(hu.^2 .* Fu ) ))
                        Du .= c ./hu .+ 2*ϵ/3 * F2u ./ hu .+ ϵ^2/c * hu .* Fu.^2 .- (hu.^2)/c
                elseif method == 3
                        hu .= 1 ./(1 .- ϵ*u)
                        Fu .= hu.* real.(ifft(F₀.*fft(u)))
                        F2u .= real.(ifft(F₀.*fft(hu.^2 .* Fu ) ))
                        Du .= 1 ./hu .+ 2*ϵ/3 * F2u ./ hu .+ ϵ^2 * hu .* Fu.^2 .- (hu/c).^2
                elseif method == 4
                        hu .= 1 ./(1 .- ϵ*u)
                        Fu .= real.(ifft(F₀.*fft(u)))
                        F2u .= real.(ifft(F₀.*fft(hu.^3 .* Fu ) ))
                        Du .=  1 ./ hu .+ 2*ϵ^2 * hu.^3 .* Fu.^2  .+ 2*ϵ.* hu .*(u.- ϵ/2* u.^2).-(hu .+ 3*ϵ* u.* hu.^2 )./(c^2)
                end
                fu .= F(u,hu,Fu,F2u)

                relerr = norm(fu,Inf)/norm(Fabs(u,hu,Fu,F2u),Inf)
                abserr = norm(fu,Inf)
    		if relerr < tol
    			@info string("Converged : relative error ",relerr," in ",i," steps\n")
    			break
    		elseif verbose == true
                        print(string("absolute error at step ",i,": ",abserr,"\n"))
                        print(string("relative error at step ",i,": ",relerr,"\n"))

    		end
                if i == max_iter
                        @warn string("The algorithm did not converge after ",i," steps: final relative error is ",relerr,"\n")
                        break
                end

                if iterative == false
                        du .=  JacF(u,hu,Fu,F2u,Du,dxu)  \ fu
                else
                        x = mesh.x
                        x₀ = mesh.x[1]
                        FFT = exp.(-1im*k*(x.-x₀)');
                        IFFT = exp.(1im*k*(x.-x₀)')/length(x);
                        F₁ 	= tanh.(sqrt(μ)*abs.(k))./(sqrt(μ)*abs.(k))
                        F₁[1] 	= 1
                        F₁ = 1 ./ (1 .+ μ*k.^2   )
                        #F₁ = ones(length(k))
                        Precond = Diagonal( 1 ./ F₁  )
                        du .=  real.(ifft(gmres( JacFfast(Complex.(u),hu,Fu,F2u,Du,dxu) , fft(fu) ; Pl = Precond, reltol = gtol, verbose=verbose )))
                end
    		u .-= q*filter(du)
        end

        if method == 1
                η = u
        elseif method ==2
                η = u./(c .- ϵ*u)
        elseif method ==3 || method == 4
                η = u./(1 .- ϵ*u)
        end

        h = 1 .+ ϵ*η
        u = c*η./h
        DxF(v) = real.(ifft(F₀ .* fft(v)))
	v = u - 1/3 ./h .* (DxF(h.^3 .*DxF(u)))

        return (η,u,v,mesh)

end

"""
    SolitaryWGN(param; kwargs)

Build the initial data associated with `SolitaryWaveWhithamGreenNaghdi(param; kwargs)`, of type `InitialData`,
to be used in initial-value problems `Problem(model, initial::InitialData, param)`.

---
    SolitaryWGN(c; ϵ=1,μ=1,N=2^12,kwargs)
Build the initial data with velocity `c`, dimensionless parameters `ϵ` and `μ`, and number of collocation points `N`, \
and `kwargs` the other (optional) keyword arguments as above.
"""
struct SolitaryWGN <: InitialData

	η
	v
	label :: String
	info  :: String

	function SolitaryWGN(param::NamedTuple;
                                guess = zeros(0) :: Vector{Float64},
                                x₀ = 0 :: Real,
                                SGN = false :: Bool,
                                method = 2 :: Int,
                                iterative = false :: Bool,
                                verbose = false :: Bool,
                                max_iter = 20 :: Int,
                                tol = 1e-10 :: Real,
                                ktol = 0 :: Real,
                                gtol = 1e-10 :: Real,
                                dealias = 0 :: Int,
                                q=1 :: Real,
                                α=0 :: Real)
		(η,u,v,mesh)=SolitaryWaveWhithamGreenNaghdi(param;
                                guess=guess,x₀=x₀,SGN=SGN,method=method,
                                iterative=iterative,verbose=verbose,max_iter=max_iter,
                                tol=tol,ktol=ktol,gtol=gtol,dealias=dealias,q=q,α=α)
		init = Init(mesh,η,v)
                if SGN == true
        	    model = "Serre-Green-Naghdi"
                else
                    model = "Whitham-Green-Naghdi"
                end
                label = "$model solitary wave"
		info = "Solitary travelling wave for the $model model.\n\
		├─velocity c = $(param.c)\n\
		└─maximum h₀ = $(maximum(η)) (from rest state)."

		new( init.η,init.v,label,info  )
	end

	function SolitaryWGN(
                        c::Real; ϵ=1::Real,μ=1::Real,N=2^12::Int,
                        guess = zeros(0) :: Vector{Float64},
                        x₀ = 0 :: Real,
                        SGN = false :: Bool,
                        method = 2 :: Int,
                        iterative = false :: Bool,
                        verbose = false :: Bool,
                        max_iter = 20 :: Int,
                        tol = 1e-10 :: Real,
                        ktol = 0 :: Real,
                        gtol = 1e-10 :: Real,
                        dealias = 0 :: Int,
                        q=1 :: Real,
                        α=0 :: Real
                                )
		L=200/sqrt(3*(c^2-1)/(c^2)/μ)/2
		xmin,xmax = x₀-L,x₀+L;
		param=(ϵ=ϵ,μ=μ,c=c,L=L,xmin=xmin,xmax=xmax,N=N)
		sol=SolitaryWGN(param;
                        guess=guess,x₀=x₀,SGN=SGN,method=method,
                        iterative=iterative,verbose=verbose,max_iter=max_iter,
                        tol=tol,ktol=ktol,gtol=gtol,dealias=dealias,q=q,α=α)
		new( sol.η,sol.v,sol.label,sol.info  )
	end

end


using HPAT

@acc hpat function udf_test(file_name)
    df = DataSource(DataTable{:id=Int64,:x=Float64,:y=Float64}, HDF5, file_name)
    df2 = aggregate(df, :id, :sx = sum(2*:x), :sy = sum(2*:y))
    return sum(df2[:sx])
end

udf_test(ENV["HOME"]*"/tmp/udf.hdf5")


abstract type AbstractFunctional end

mutable struct volumeAverage <: AbstractFunctional
end
function (obj::volumeAverage)(
              mesh::AbstractMesh{Tmsh},
              sbp::AbstractOperator,
              eqn::EllipticData{Tsol, Tres, Tdim},
              opts,
              val::Array{Tsol, 1}) where {Tmsh, Tsol, Tres, Tdim}
  @assert(length(val) == mesh.numDofPerNode)
  val[:] = 0.0

  w = sview(sbp.w, :)
  for elem = 1 : mesh.numEl
    jac = sview(mesh.jac, :, elem)
    q = sview(eqn.q, :, :, elem)
    for n = 1 : mesh.numNodesPerElement
      val[:] += w[n]/jac[n]*q[:, n]
    end
  end

  return nothing
end

mutable struct volumeEnergy <: AbstractFunctional
end
function (obj::volumeEnergy)(
              mesh::AbstractMesh{Tmsh},
              sbp::AbstractOperator,
              eqn::EllipticData{Tsol, Tres, Tdim},
              opts,
              val::Array{Tsol, 1}) where {Tmsh, Tsol, Tres, Tdim}
  @assert(length(val) == mesh.numDofPerNode)
  val[:] = 0.0

  w = sview(sbp.w, :)
  for elem = 1 : mesh.numEl
    jac = sview(mesh.jac, :, elem)
    q = sview(eqn.q, :, :, elem)
    for n = 1 : mesh.numNodesPerElement
      for dof = 1:mesh.numDofPerNode
        val[dof] += w[n]/jac[n]*q[dof, n]*q[dof,n]
      end
    end
  end

  return nothing
end
#

#
# IP Modification of target functional
#
function FunctionalModification(mesh::AbstractMesh{Tmsh},
                                sbp::AbstractOperator,
                                eqn::EllipticData{Tsol, Tres, Tdim},
                                otps::Array{Tsol, 1}) where {Tmsh, Tsol, Tres, Tdim}
  if haskey(opts, "FunctionalModification") && opts["FunctionalModification"] == "No"
    return nothing
  end

  for iBC = 1:mesh.numBC

    bc_func = mesh.bndry_funcs[iBC]
    if isNeumann(bc_func)
      continue
    end

    indx0 = mesh.bndry_offsets[iBC]
    indx1 = mesh.bndry_offsets[iBC+1] - 1

    for f = indx0:indx1

    end

  end
  return nothing
end

global const FunctionalDict = Dict{String, AbstractFunctional}(
  "volumeAverage" => volumeAverage(),
  "energy" => volumeEnergy()
)

function getFunctional(mesh::AbstractMesh,
                       sbp::AbstractOperator,
                       eqn::EllipticData,
                       opts)
  eqn.functional = FunctionalDict[opts["Functional"]]
end


module DeconvOptim

export deconvolution

 # for optimization
using Optim
 #mean
using Statistics
using FFTW
 # for fast array regularizers
using Tullio
 # possible up_sampling
using Interpolations
 # to check wether CUDA is enabled
using Requires
# for defining custom derivatives
using ChainRulesCore

# via require we can check whether CUDA is loaded
# to enable CUDA support simply load CUDA before load DeconvOptim
 #to_gpu_or_not(x) = x
 #function __init__()
 #    @require CUDA="052768ef-5323-5732-b1bb-66c8b64840ba" begin
 #        print("CUDA support is enabled")
 #        @eval using CUDA
 #        @eval to_gpu_or_not(x) = CuArray(x)
 #    end
 #end

include("forward_models.jl")
include("lossfunctions.jl")
include("mappings.jl")
include("regularizer.jl")
include("utils.jl")

# refresh Zygote to load the custom rrules defined with ChainRulesCore
using Zygote: @adjoint, gradient

"""
    deconvolution(measured, psf; <keyword arguments>)
Computes the deconvolution of `measured` and `psf`. Return parameter is a tuple with
two elements. The first entry is the deconvolved image. The second return parameter
is the output of the optimization of Optim.jl

Multiple keyword arguments can be specified for different loss functions,
regularizers and mappings.

# Arguments
- `loss=Poisson()`: the loss function taking a vector the same shape as measured.
- `regularizer=GR()`: A regularizer function, same form as `loss`.
- `λ=0.05`: A float indicating the total weighting of the regularizer with
    respect to the global loss function
- `background=0`: A float indicating a background intensity level.
- `mapping=Non_negative()`: Applies a mapping of the optimizer weight. Default is a
              parabola which achieves a non-negativity constraint.
- `iterations=20`: Specifies a number of iterations after the optimization.
    definitely should stop.
- `plan_fft=true`: Boolean whether plan_fft is used. Gives a slight speed improvement.
- `padding=0`: an float indicating the amount (fraction of the size in that dimension)
        of padded regions around the reconstruction. Prevents wrap around effects of the FFT.
        A array with `size(arr)=(400, 400)` with `padding=0.05` would result in reconstruction size of
        `(440, 440)`. However, we only return the reconstruction cropped to the original size.
        `padding=0` disables any padding.
- `optim_options=nothing`: Can be a options file required by Optim.jl. Will overwrite iterations.
- `optim_optimizer=LBFGS()`: The chosen Optim.jl optimizer.

# Example
```julia-repl
julia> using DeconvOptim, TestImages, Images, FFTW, Noise, ImageView;

julia> img = channelview(testimage("resolution_test_512"));

julia> psf = generate_psf(size(img), 30);

julia> img_b = conv_psf(img, psf);

julia> img_n = poisson(img_b, 300);

julia> @time res, o = deconvolution(img_n, psf);

julia> colorview(Gray, [img img_n res]) |> imshow
[...]
```
"""
function deconvolution(measured::AbstractArray{T, N}, psf;
        loss=Poisson(),
        regularizer=GR(),
        λ=0.05,
        background=0,
        mapping=Non_negative(),
        iterations=20,
        plan_fft=true,
        padding=0.00,
        optim_options=nothing,
        optim_optimizer=LBFGS(),
        ) where {T, N}

    # do some type conversion to ensure same type everywhere
    # provides speed-up
    λ = convert(eltype(measured), λ)
    psf = convert(Array{eltype(measured)}, psf)
    background = convert(eltype(measured), background)

    # Get the mapping functions to achieve constraints
    # like non negativity
    if mapping != nothing
        mf, m_invf = mapping
    else
        m_invf = identity
    end

    # rec0 will be an array storing the final reconstruction
    # we choose it larger than the measured array to reduce
    # wrap around artifacts of the Fourier Transform
    # we create a array size_padded which stores a new array size
    # our reconstruction array will be larger than measured
    # to prevent wrap around artifacts
    size_padded = []
    for i = 1:ndims(measured)
        # if the size of the i-th dimension is 1
        # don't do any padding because there won't be no
        # convolution in that dimension
        if  size(measured)[i] == 1
            push!(size_padded, 1)
        else
            # only pad, if padding is true
            if ~iszero(padding)
                # 2 * ensures symmetric padding
                # minimum padding is 2 (4 in total) on each side
                x = max(4, 2 * round(Int, size(measured)[i] * padding))
            else
                x = 0
            end
            push!(size_padded, size(measured)[i] + x)
        end
    end

    # the dimensions we do the Fourier Transform over
    fft_dims = collect(1:ndims(psf))

    # we divide by the maximum to normalize
    rescaling = maximum(measured)
    measured = measured ./ rescaling
    # create rec0 which will be the initial guess for the reconstruction
    rec0 = zeros(T, (size_padded...))

    # alternative rec0_center, unused at the moment
    #rec0_center = m_invf(abs.(conv_psf(measured, psf, fft_dims)))
    #
    # take the mean as the initial guess
    # therefore has the same total energy at the initial guess as
    # measured
    rec0_center = mean(measured) .* ones(T, size(measured))
    center_set!(rec0, rec0_center)

    # psf_n is the psf with the same size as rec0 but only in that dimensions
    # that were supported by the initial psf. Broadcasting of psf with less
    # dimensions is still supported
    # we put the small psf into the new one
    # it is important to pad the PSF instead of the OTF

    psf_new_size = Array{Int}(undef, 0)
    for i = 1:ndims(psf)
        push!(psf_new_size, size(rec0)[i])
    end

    psf_new_size = tuple(psf_new_size...)
    psf_n = zeros(eltype(rec0), psf_new_size)
    psf_n = center_set!(psf_n, fftshift(psf))
    psf = ifftshift(psf_n)

    # the psf should be normalized to 1
    psf ./= sum(psf)

    # use plan_fft (function of the FFTW.jl has the same name)
    # for speed improvement
    if plan_fft
        # otf is obtained by rfft(psf)
        # therefore size(psf) != size(otf)
        otf, conv = plan_conv_r(psf, rec0, fft_dims)
    else
        otf = rfft(psf, fft_dims)
        conv(rec, otf) = conv_otf_r(rec, otf, fft_dims)
    end

    # forward model is a convolution
    # due to numerics, we need to clip at 0
    # analytically it's a convolution psf ≥ 0 and image ≥ 0
    # so it must be conv(psf, image) ≥ 0
    forward(x) = (conv_aux(conv, x, otf)) .+ background
    # create the loss function which depends simply on the current rec
    function total_loss(rec)
        # handle if there is a provided mapping function
        if mapping != nothing
            mf_rec = mf(rec)
        else
            mf_rec = rec
        end
        forward_v = forward(mf_rec)
        loss_v = loss(forward_v, measured)
        # handle if there is a regularizer
        if regularizer != nothing
            reg_v = regularizer(mf_rec)
            out = loss_v + λ * reg_v
        else
            out = loss_v
        end
        return out
    end

    # this is the function which will be provided to Optimize
    # check Optim's documentation for the purpose of F and Get
    # but simply speaking F is the loss value and G it's gradient
    # depending whether one of them is nothing, we skip some computations
    # we need to call Base.invokelatest because the regularizer is a function
    # generated at runtime with eval.
    # This leads to the common "world age problem" in Julia
    # for more details on that check:
    # https://discourse.julialang.org/t/dynamically-create-a-function-initial-idea-with-eval-failed-due-to-world-age-issue/49139/17
    function f!(F, G, rec)
        # Zygote calculates both derivative and loss, therefore do everything in one step
        if G != nothing
            y, back = Base.invokelatest(Zygote._pullback, total_loss, rec)
            # calculate gradient
            G .= Base.invokelatest(back, 1)[2]
            if F != nothing
                return y
            end
        end
        if F != nothing
            return Base.invokelatest(total_loss, rec)
        end
    end

    # if not special options are given, just restrict iterations
    if optim_options == nothing
        optim_options = Optim.Options(iterations=iterations)
    end

    # do the optimization with LBGFS
    res = Optim.optimize(Optim.only_fg!(f!), rec0, optim_optimizer, optim_options)

    # apply the mapping to get the real result
    if mapping != nothing
        res_out = mf(Optim.minimizer(res))
    else
        res_out = Optim.minimizer(res)
    end

    res_out .*= rescaling
    # since we do some padding we need to extract the center part
    res_out = center_extract(res_out, size(measured))
    return res_out, res
end

# end module
end


using ThreeBodyProblem
using Test

sys = System(ThreeBodyProblem.JUPITER,ThreeBodyProblem.CALLISTO)
@test typeof(sys.prim) == Body
@test typeof(sys.sec) == Body
@test typeof(sys) == System

# Create the ice planet Hoth
m = 1e24 # {kg} mass
R = 1e6 # {km} mean radius
a = 1.5*AU # {km} mean semimajor axis
T = 0.6*JY # {sec} mean orbital period
name = "Hoth"
newplanet = Body(m, R, a, T, name)
@test typeof(newplanet) == Body
@test newplanet.m == m
@test newplanet.R == R
@test newplanet.a == a
@test newplanet.T == T
@test newplanet.name == name

# Create the forest moon Endor
m = 1e22 # {kg} mass
R = 1e5 # {km} mean radius
a = 0.2*AU # {km} mean semimajor axis
T = 2π*sqrt(a^3/(G*newplanet.m)) # {sec} mean orbital period
newmoon = Body(m, R, a, T)
@test typeof(newmoon) == Body
@test newmoon.m == m
@test newmoon.R == R
@test newmoon.a == a
@test newmoon.T == T
@test newmoon.name == "NewPlanet"
@test newmoon.color == :blue

sys = System(newplanet, newmoon)
@test typeof(sys.prim) == Body
@test typeof(sys.sec) == Body
@test typeof(sys) == System

sys = sun_mercury()
@test sys.prim.name == "Sun"
@test sys.sec.name == "Mercury"
@test sys.name == "Sun/Mercury"

sys = sun_venus()
@test sys.name == "Sun/Venus"
sys = sun_earth()
@test sys.name == "Sun/Earth"
sys = earth_moon()
@test sys.name == "Earth/Moon"
sys = sun_mars()
@test sys.name == "Sun/Mars"
sys = sun_jupiter()
@test sys.name == "Sun/Jupiter"
sys = jupiter_europa()
@test sys.name == "Jupiter/Europa"
sys = sun_saturn()
@test sys.name == "Sun/Saturn"
sys = saturn_enceladus()
@test sys.name == "Saturn/Enceladus"
sys = sun_uranus()
@test sys.name == "Sun/Uranus"
sys = sun_neptune()
@test sys.name == "Sun/Neptune"


module FETermsTests

using Test
using Gridap
using Gridap.SkeletonCellFields: SkeletonCellMatrix
using Gridap.SkeletonCellFields: SkeletonCellVector

ufun(x) = x[1] + x[2]
bfun(x) = x[1]
gfun(x) = x[2]
sfun(x) = x[1]

model = CartesianDiscreteModel(partition=(4,4))

order = 1
diritag = "boundary"
fespace = H1ConformingFESpace(Float64,model,order,diritag)

V = TestFESpace(fespace)
U = TrialFESpace(fespace,ufun)

trian = Triangulation(model)
quad = CellQuadrature(trian,degree=2)

tags = [7,6]
btrian = BoundaryTriangulation(model,tags)
bquad = CellQuadrature(btrian,degree=2)

tags = [9,]
strian = SkeletonTriangulation(model,tags)
squad = CellQuadrature(strian,degree=2)

v = FEBasis(V)
du = FEBasis(U)
uhd = zero(U)
uh = interpolate(U,ufun)

bfield = CellField(trian,bfun)
gfield = CellField(btrian,gfun)
sfield = CellField(strian,sfun)

a(v,u) = inner(v,u)
l(v) = inner(v,bfield)
g(v) = inner(v,gfield)

t1 = AffineFETerm(a,l,trian,quad)

sr = "AffineFETermFromIntegration object"
@test string(t1) == sr
@test sprint(show,"text/plain",t1) == sr

cm = setup_cell_matrix(t1,v,du)
@test isa(cm,CellMatrix)
@test length(cm) == ncells(trian)

cm = setup_cell_jacobian(t1,uh,v,du)
@test isa(cm,CellMatrix)
@test length(cm) == ncells(trian)

cv = setup_cell_vector(t1,v,uhd)
@test isa(cv,CellVector)
@test length(cv) == ncells(trian)

cv = setup_cell_residual(t1,uh,v)
@test isa(cv,CellVector)
@test length(cv) == ncells(trian)

cn = setup_cell_ids(t1)
@test isa(cn,CellNumber)
@test length(cn) == ncells(trian)

t2 = AffineFETerm(a,g,btrian,bquad)

cm = setup_cell_matrix(t2,v,du)
@test isa(cm,CellMatrix)
@test length(cm) == ncells(btrian)

cv = setup_cell_vector(t2,v,uhd)
@test isa(cv,CellVector)
@test length(cv) == ncells(btrian)

cn = setup_cell_ids(t2)
@test isa(cn,CellNumber)
@test length(cn) == ncells(btrian)

sa(v,u) = inner(jump(v),jump(u))
s(v) = inner(jump(v),sfield)

t6 = AffineFETerm(sa,s,strian,squad)

cm = setup_cell_matrix(t6,v,du)
@test isa(cm,SkeletonCellMatrix)

cv = setup_cell_vector(t6,v,uhd)
@test isa(cv,SkeletonCellVector)

cm = setup_cell_jacobian(t6,uh,v,du)
@test isa(cm,SkeletonCellMatrix)

cv = setup_cell_residual(t6,uh,v)
@test isa(cv,SkeletonCellVector)

cn = setup_cell_ids(t6)
@test isa(cn,Tuple{<:CellNumber,<:CellNumber})

t3 = FESource(g,btrian,bquad)

cv = setup_cell_vector(t3,v,uhd)
@test isa(cv,CellVector)
@test length(cv) == ncells(btrian)

cv = setup_cell_residual(t3,uh,v)
@test isa(cv,CellVector)
@test length(cv) == ncells(btrian)

cn = setup_cell_ids(t3)
@test isa(cn,CellNumber)
@test length(cn) == ncells(btrian)

cm = setup_cell_matrix(t3,v,du)
@test cm === nothing

t4 = LinearFETerm(a,trian,quad)

cm = setup_cell_matrix(t4,v,du)
@test isa(cm,CellMatrix)
@test length(cm) == ncells(trian)

cv = setup_cell_vector(t4,v,uhd)
@test isa(cv,CellVector)
@test length(cv) == ncells(trian)

cn = setup_cell_ids(t4)
@test isa(cn,CellNumber)
@test length(cn) == ncells(trian)

jac(uh,v,du) = a(v,du)
res(uh,v) = a(v,uh) - l(v)

t5 = NonLinearFETerm(res,jac,trian,quad)

cm = setup_cell_jacobian(t5,uh,v,du)
@test isa(cm,CellMatrix)
@test length(cm) == ncells(trian)

cv = setup_cell_residual(t5,uh,v)
@test isa(cv,CellVector)
@test length(cv) == ncells(trian)

cn = setup_cell_ids(t5)
@test isa(cn,CellNumber)
@test length(cn) == ncells(trian)

assem = SparseMatrixAssembler(V,U)

cms = setup_cell_jacobian(uh,v,du,t1)

cms = setup_cell_jacobian(uh,v,du,t1,t2,t6)

mat = assemble(assem,cms...)

cvs = setup_cell_residual(uh,v,t1,t2,t6)

vec = assemble(assem,cvs...)

cvs = setup_cell_vector(v,uhd,t1,t2,t6)

vec = assemble(assem,cvs...)

cms = setup_cell_matrix(v,du,t1,t2,t6)

mat = assemble(assem,cms...)

end #module


using LinearAlgebra: norm, I, UniformScaling

"""
    scale(a::T, P::LazySet) where {T<:Real}

 Scale the set `P` by the scalar `a`. The scaling corresponds to applying a `linear_map` to `a` multiplied with an appropriately sized unit matrix and the set `P`.

 ### Input
 - `a` -- scalar
 - `P` -- lazy set

 ### Output
 A lazy set which correspoinds to the set `P` scaled by `a`.

 ### Note
 Enables to apply concrete multiplication operator `*ᶜ` to a scalar and a set (in addition to matrix and set).
"""
LazySets.scale(α::T, P::LazySet) where {T<:Real} =
    linear_map(α*Matrix{T}(I, dim(P), dim(P)), P)

"""
    linear_map(U::UniformScaling, P::LazySet)

 Concrete linear map of the set `P` and the uniform scaling `U`.

 ### Input
 - `U` -- uniform scaling
 - `P` -- lazy set

 ### Output
 A lazy set which corresponds to the linear map of `P` with `U`.
"""
LazySets.linear_map(U::UniformScaling, P::LazySet) =
    scale(U.λ, P)

"""
    ρ_exact(d::AbstractVector, P::LazySet)

 Compute support value for the lazy set `P` in the direction `d`. Avoid numerical errors for directions `d` with small norm by normalizing `d`. See  [`ρ`](@ref) for more detail.

 ### Input
 - `d` -- vector
 - `P` -- lazy set

 ### Output
 Support value for lazy set `P` in the direction `d`.
"""
ρ_exact(d::AbstractVector, P::LazySet) =  ρ(normalize(d), P) * norm(d)

"""
    σ_exact(d::AbstractVector, P::LazySet)

 Compute support vector for the lazy set `P` in the direction `d`. Avoid numerical errors for directions `d` with small norm by normalizing `d`. See  [`ρ`](@ref) for more detail.

 ### Input
 - `d` -- vector
 - `P` -- lazy set

 ### Output
 Support vector for lazy set `P` in the direction `d`.
"""
σ_exact(d::AbstractVector, P::LazySet) =  σ(normalize(d), P)

"""
    ρ_matrix(M::AbstractArray, P::LazySet)

Compute support value for the lazy set `P` in the direction of eachrow of matrix `M`.

### Input
- `M` -- matrix with rows as normal direction of constraints
- `P` -- lazy set

### Output
Vector of support values for lazy set `P` in the direction of each row of `M`.
"""
function ρ_matrix(M::AbstractArray, P::LazySet)
    num_constr = size(M, 1)
    res = zeros(num_constr)
    for i=1:num_constr
        res[i] = ρ_exact(view(M, i, :), P)
    end
    return res
end

"""
    canonical_length(X::LazySet)

Returns the support function of the given set along the positive and negative canonical directions.

### Inputs

- `X` - convex set

### Outputs

A matrix with `n = dims(X)` rows and two columns. Each row stands for
one dimension of `X` whereas the first column is the minimum and the second
column is the maximum value of the corresponding dimension.
"""
function canonical_length(X::LazySet{N}) where {N<:Real}
    dims = LazySets.dim(X)
    x = Matrix{N}(undef, dims, 2)
    for j = 1:dims
        ej = SingleEntryVector(j, dims, one(N))
        x[j,:] = [-ρ_exact(-ej, X), ρ_exact(ej, X)]
    end
    return x
end

"""
    marginal_enlargment(P::LazySet, ε=LazySets._TOL_F64.rtol)

 Increase the size of a lazy set `P` with available H-representation marginally. Hopefully by a Ball with a radius of `ε`.

 ### Input
 - `P` -- lazy set
 - `ε` -- (optional, default=LazySets.,_TOL_F64.rtol) marginal incrase

 ### Ouput
 Lazy set with marginally larger size than `P`.

 ### Note
 This function is useful, if we want to compute the minimal RPI set according to e.g. [`minimal_RPI_set_lazy_rakovic`](@ref) for an error set where the origin lies on the boundary and not in the interior. After marginally enlarging the error set, the algorithms for computing the minimal RPI set can be applied since the origin is now contained in the interior.
"""
function marginal_enlargment(P::LazySet, ε=LazySets._TOL_F64.rtol)
    F, g = tosimplehrep(P)
    g .+= ε/norm(g)
    return HPolytope(F, g)
end


function linearSplineInterpolation(fineT,fineX,T)
    if length(T)>1
        return map(T -> linearSplineInterpolation(fineT,fineX,T),T)
    end
    i = findfirst(fineT.>=T)
    if isnothing(i)
        return fineX[:,end]
    end
    if i<=1
        return fineX[:,1]
    end
    return fineX[:,i-1]+(T-fineT[i-1])/(fineT[i]-fineT[i-1])*(fineX[:,i]-fineX[:,i-1])
end

println("Finished loading HamiltonInterpolate")


using MathOptInterface, GalacticOptim, Optim, Test, Random, Flux, ForwardDiff, Zygote
using Nonconvex

rosenbrock(x, p) =  (p[1] - x[1])^2 + p[2] * (x[2] - x[1]^2)^2
x0 = zeros(2)
_p  = [1.0, 100.0]
l1 = rosenbrock(x0, _p)

@testset "Optim, CMAEvolutionStrategy" begin
    f = OptimizationFunction(rosenbrock, GalacticOptim.AutoForwardDiff())
    prob = OptimizationProblem(f, x0, _p)
    Random.seed!(1234)
    sol = solve(prob, SimulatedAnnealing())
    @test 10*sol.minimum < l1

    Random.seed!(1234)
    prob = OptimizationProblem(f, x0, _p, lb=[-1.0, -1.0], ub=[0.8, 0.8])
    sol = solve(prob, SAMIN())
    @test 10*sol.minimum < l1

    using CMAEvolutionStrategy
    sol = solve(prob, CMAEvolutionStrategyOpt())
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(rosenbrock, x0, _p)
    sol = solve(prob, Optim.NelderMead())
    @test 10*sol.minimum < l1
end

@testset "Constrained Optimisation" begin
    cons= (x,p) -> [x[1]^2 + x[2]^2]
    optprob = OptimizationFunction(rosenbrock, GalacticOptim.AutoForwardDiff();cons= cons)

    prob = OptimizationProblem(optprob, x0, _p)

    sol = solve(prob, Flux.ADAM(0.1), maxiters = 1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, Optim.BFGS())
    @test 10*sol.minimum < l1

    sol = solve(prob, Optim.Newton())
    @test 10*sol.minimum < l1

    sol = solve(prob, Optim.KrylovTrustRegion())
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0, _p, lcons = [-Inf], ucons = [Inf])
    sol = solve(prob, IPNewton())
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0, _p, lcons = [-5.0], ucons = [10.0])
    sol = solve(prob, IPNewton())
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0, _p, lcons = [-Inf], ucons = [Inf], lb = [-500.0,-500.0], ub=[50.0,50.0])
    sol = solve(prob, IPNewton())
    @test sol.minimum < l1

    function con2_c(x,p)
        [x[1]^2 + x[2]^2, x[2]*sin(x[1])-x[1]]
    end

    optprob = OptimizationFunction(rosenbrock, GalacticOptim.AutoForwardDiff();cons= con2_c)
    prob = OptimizationProblem(optprob, x0, _p, lcons = [-Inf,-Inf], ucons = [Inf,Inf])
    sol = solve(prob, IPNewton())
    @test 10*sol.minimum < l1

    cons_circ = (x,p) -> [x[1]^2 + x[2]^2]
    optprob = OptimizationFunction(rosenbrock, GalacticOptim.AutoForwardDiff();cons= cons_circ)
    prob = OptimizationProblem(optprob, x0, _p, lcons = [-Inf], ucons = [0.25^2])
    sol = solve(prob, IPNewton())
    @test sqrt(cons(sol.u,nothing)[1]) ≈ 0.25 rtol = 1e-6

    optprob = OptimizationFunction(rosenbrock, GalacticOptim.AutoZygote())

    prob = OptimizationProblem(optprob, x0, _p)
    sol = solve(prob, Flux.ADAM(), maxiters = 1000, progress = false)
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0, _p, lb=[-1.0, -1.0], ub=[0.8, 0.8])
    sol = solve(prob, Optim.Fminbox())
    @test 10*sol.minimum < l1

    Random.seed!(1234)
    prob = OptimizationProblem(optprob, x0, _p, lb=[-1.0, -1.0], ub=[0.8, 0.8])
    sol = solve(prob, Optim.SAMIN())
    @test 10*sol.minimum < l1
end

@testset "MaxSense test" begin
    optprob = OptimizationFunction((x,p) -> -rosenbrock(x,p), GalacticOptim.AutoZygote())
    prob = OptimizationProblem(optprob, x0, _p; sense = GalacticOptim.MaxSense)

    import Ipopt
    sol = solve(prob, Ipopt.Optimizer())
    @test 10*sol.minimum < l1

    import NLopt
    sol = solve(prob, NLopt.Opt(:LN_BOBYQA, 2))
    @test 10*sol.minimum < l1

    sol = solve(prob, NelderMead())
    @test 10*sol.minimum < l1

    sol = solve(prob, BFGS())
    @test 10*sol.minimum < l1

    function g!(G, x)
        G[1] = -2.0 * (1.0 - x[1]) - 400.0 * (x[2] - x[1]^2) * x[1]
        G[2] = 200.0 * (x[2] - x[1]^2)
    end
    optprob = OptimizationFunction((x,p) -> -rosenbrock(x,p), GalacticOptim.AutoZygote(), grad = g!)
    prob = OptimizationProblem(optprob, x0, _p; sense = GalacticOptim.MaxSense)
    sol = solve(prob, BFGS())
    @test 10*sol.minimum < l1
end

@testset "MinSense test" begin
    optprob = OptimizationFunction(rosenbrock, GalacticOptim.AutoZygote())
    prob = OptimizationProblem(optprob, x0, _p; sense = GalacticOptim.MinSense)

    sol = solve(prob, Ipopt.Optimizer())
    @test 10*sol.minimum < l1

    sol = solve(prob, GalacticOptim.MOI.OptimizerWithAttributes(Ipopt.Optimizer, "max_cpu_time" => 60.0))
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0)
    sol = solve(prob, NLopt.Opt(:LN_BOBYQA, 2))
    @test 10*sol.minimum < l1

    sol = solve(prob, GalacticOptim.MOI.OptimizerWithAttributes(NLopt.Optimizer, "algorithm" => :LN_BOBYQA))
    @test 10*sol.minimum < l1

    sol = solve(prob, NLopt.Opt(:LD_LBFGS, 2))
    @test 10*sol.minimum < l1

    sol = solve(prob, GalacticOptim.MOI.OptimizerWithAttributes(NLopt.Optimizer, "algorithm" => :LD_LBFGS))
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0, lb=[-1.0, -1.0], ub=[0.8, 0.8])
    sol = solve(prob, NLopt.Opt(:LD_LBFGS, 2))
    @test 10*sol.minimum < l1

    sol = solve(prob, GalacticOptim.MOI.OptimizerWithAttributes(NLopt.Optimizer, "algorithm" => :LD_LBFGS))
    @test 10*sol.minimum < l1

    sol = solve(prob, NLopt.Opt(:G_MLSL_LDS, 2), local_method = NLopt.Opt(:LD_LBFGS, 2), maxiters=10000)
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0)
    sol = solve(prob, NLopt.LN_BOBYQA())
    @test 10*sol.minimum < l1

    sol = solve(prob, NLopt.LD_LBFGS())
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(optprob, x0, lb=[-1.0, -1.0], ub=[0.8, 0.8])
    sol = solve(prob, NLopt.LD_LBFGS())
    @test 10*sol.minimum < l1

    sol = solve(prob, NLopt.G_MLSL_LDS(), local_method = NLopt.LD_LBFGS(), local_maxiters=10000, maxiters=10000, population=10)
    @test 10*sol.minimum < l1
end

# using QuadDIRECT
# sol = solve(prob, QuadDirect(); splits = ([-0.5, 0.0, 0.5],[-0.5, 0.0, 0.5]))
# @test 10*sol.minimum < l1

@testset "Evolutionary, BlackBoxOptim, Metaheuristics, Nonconvex, GCMAES, SpeedMapping, MultistartOptimization, NOMAD" begin
    optprob = OptimizationFunction(rosenbrock, GalacticOptim.AutoZygote())
    using Evolutionary
    prob = GalacticOptim.OptimizationProblem(optprob, x0, _p)
    sol = solve(prob, CMAES(μ =40 , λ = 100),abstol=1e-15)
    @test 10*sol.minimum < l1

    using BlackBoxOptim
    prob = GalacticOptim.OptimizationProblem(optprob, x0, _p, lb=[-1.0, -1.0], ub=[0.8, 0.8])
    sol = solve(prob, BBO_adaptive_de_rand_1_bin_radiuslimited())
    @test 10*sol.minimum < l1

    using Metaheuristics
    prob = GalacticOptim.OptimizationProblem(optprob, x0, _p, lb=[-1.0, -1.0], ub=[1.5, 1.5])
    sol = solve(prob, ECA())
    @test 10*sol.minimum < l1

    sol = solve(prob, Metaheuristics.DE())
    @test 10*sol.minimum < l1

    sol = solve(prob, PSO())
    @test 10*sol.minimum < l1

    sol = solve(prob, ABC())
    @test 10*sol.minimum < l1

    sol = solve(prob, CGSA(N=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, SA())
    @test 10*sol.minimum < l1

    sol = solve(prob, WOA())
    @test 10*sol.minimum < l1

    sol = solve(prob, ECA())
    @test 10*sol.minimum < l1

    sol = solve(prob, Metaheuristics.DE(), use_initial=true)
    @test 10*sol.minimum < l1

    sol = solve(prob, PSO(), use_initial=true)
    @test 10*sol.minimum < l1

    sol = solve(prob, ABC(), use_initial=true)
    @test 10*sol.minimum < l1

    sol = solve(prob, CGSA(N=100), use_initial=true)
    @test 10*sol.minimum < l1

    sol = solve(prob, SA(), use_initial=true)
    @test 10*sol.minimum < l1

    sol = solve(prob, WOA(), use_initial=true)
    @test 10*sol.minimum < l1

    using ModelingToolkit
    f = OptimizationFunction(rosenbrock,ModelingToolkit.AutoModelingToolkit())
    prob = OptimizationProblem(f, x0, _p)
    @test_broken sol = solve(prob,Optim.Newton())

    #=
    ### Nonconvex test
    Nonconvex.@load MMA
    Nonconvex.@load Ipopt
    Nonconvex.@load NLopt
    Nonconvex.@load BayesOpt
    Nonconvex.@load Juniper
    Nonconvex.@load Pavito
    Nonconvex.@load Hyperopt
    Nonconvex.@load MTS
    prob = GalacticOptim.OptimizationProblem(optprob, x0, _p, lb = [-1.0,-1.0], ub = [1.5,1.5])

    sol = solve(prob, MMA02())
    @test 10*sol.minimum < l1

    sol = solve(prob, GCMMA())
    @test 10*sol.minimum < l1

    sol = solve(prob, IpoptAlg())
    @test 10*sol.minimum < l1

    sol = solve(prob, NLoptAlg(:LN_NELDERMEAD))
    @test 10*sol.minimum < l1

    sol = solve(prob, NLoptAlg(:LD_LBFGS))
    @test 10*sol.minimum < l1

    sol = solve(prob, MTSAlg())
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=LHSampler())
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=CLHSampler())
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=GPSampler())
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=LHSampler())
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=CLHSampler())
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=GPSampler())
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)))
    @test 10*sol.minimum < l1

    sol = solve(prob, MMA02(), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, GCMMA(), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, IpoptAlg(), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, NLoptAlg(:LN_NELDERMEAD), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, NLoptAlg(:LD_LBFGS), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, MTSAlg(), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=LHSampler(), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=CLHSampler(), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=GPSampler(), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=LHSampler(), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=CLHSampler(), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=GPSampler(), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), maxiters=1000)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), maxiters=100)
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)))
    @test 10*sol.minimum < l1

    ### suboptions
    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=LHSampler(), sub_options=(;maxeval=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=CLHSampler(), sub_options=(;maxeval=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(NLoptAlg(:LN_NELDERMEAD)), sampler=GPSampler(), sub_options=(;maxeval=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=LHSampler(), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=CLHSampler(), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, HyperoptAlg(IpoptAlg()), sampler=GPSampler(), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), sub_options=(;maxeval=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), sub_options=(;maxeval=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(IpoptAlg()), sub_options=(;max_iter=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), sub_options=(;maxeval=100))
    @test 10*sol.minimum < l1

    sol = solve(prob, BayesOptAlg(NLoptAlg(:LN_NELDERMEAD)), sub_options=(;maxeval=100))
    @test 10*sol.minimum < l1
    =#

    using NOMAD
    f = OptimizationFunction(rosenbrock)

    prob = OptimizationProblem(f, x0, _p)
    sol = GalacticOptim.solve(prob,NOMADOpt())
    @test 10*sol.minimum < l1

    prob = OptimizationProblem(f, x0, _p; lb = [-1.0,-1.0], ub = [1.5,1.5])
    sol = GalacticOptim.solve(prob,NOMADOpt())
    @test 10*sol.minimum < l1

    using GCMAES
    f_ad = OptimizationFunction(rosenbrock, GalacticOptim.AutoForwardDiff())
    f_noad = OptimizationFunction(rosenbrock)

    prob = GalacticOptim.OptimizationProblem(f_ad, x0, _p, lb=[-1.0, -1.0], ub=[1.0, 1.0])
    sol = solve(prob, GCMAESOpt(), maxiters=1000)
    @test 10*sol.minimum < l1

    prob = GalacticOptim.OptimizationProblem(f_noad, x0, _p, lb=[-1.0, -1.0], ub=[1.0, 1.0])
    sol = solve(prob, GCMAESOpt(), maxiters=1000)
    @test 10*sol.minimum < l1

    using SpeedMapping
    f = OptimizationFunction(rosenbrock, GalacticOptim.AutoForwardDiff())
    prob = OptimizationProblem(f, x0, _p)
    sol = solve(prob,SpeedMappingOpt())

    prob = OptimizationProblem(f, x0, _p;lb = [-1.0,-1.0], ub = [1.5,1.5])
    sol = solve(prob,SpeedMappingOpt())

    f = OptimizationFunction(rosenbrock)
    prob = OptimizationProblem(f, x0, _p)
    sol = solve(prob,SpeedMappingOpt())

    prob = OptimizationProblem(f, x0, _p;lb = [-1.0,-1.0], ub = [1.5,1.5])
    sol = solve(prob,SpeedMappingOpt())

    using MultistartOptimization
    f = OptimizationFunction(rosenbrock,GalacticOptim.AutoForwardDiff())
    prob = GalacticOptim.OptimizationProblem(f, x0, _p, lb = [-1.0,-1.0], ub = [1.5,1.5])
    sol = solve(prob, MultistartOptimization.TikTak(100), local_method = NLopt.LD_LBFGS())
    @test 10*sol.minimum < l1
end


function msfm2d(FSpeedImage, SourcePoints, usesecond, usecross)
# This function MSFM2D calculates the shortest distance from a list of
# points to all other pixels in an image, using the
# Multistencil Fast Marching Method (MSFM). This method gives more accurate
# distances by using second order derivatives and cross neighbours.
#
# T=msfm2d(F, SourcePoints, UseSecond, UseCross)
#
# inputs,
#   F: The speed image. The speed function must always be larger
#			than zero (min value 1e-8), otherwise some regions will
#			never be reached because the time will go to infinity.
#   SourcePoints : A list of starting points [2 x N] (distance zero)
#   UseSecond : Boolean Set to true if not only first but also second
#                order derivatives are used (default)
#   UseCross : Boolean Set to true if also cross neighbours
#                are used (default)
# outputs,
#   T : Image with distance from SourcePoints to all pixels
#
# Note:
#   Compile the c file "mex msfm2d.c"  for cpu-effective registration
#
# Literature : M. Sabry Hassouna et Al. Multistencils Fast Marching
#   Methods: A Highly Accurate Solution to the Eikonal Equation on
#   Cartesian Domains
#
# Example,
#   SourcePoint = [51; 51];
#   SpeedImage = ones([101 101]);
#   [X Y] = ndgrid(1:101, 1:101);
#   T1 = sqrt((X-SourcePoint(1)).^2 + (Y-SourcePoint(2)).^2);
#
#   # Run fast marching 1th order, 1th order multi stencil
#   # and 2th orde and 2th orde multi stencil
#
#   tic; T1_FMM1 = msfm2d(SpeedImage, SourcePoint, false, false); toc;
#   tic; T1_MSFM1 = msfm2d(SpeedImage, SourcePoint, false, true); toc;
#   tic; T1_FMM2 = msfm2d(SpeedImage, SourcePoint, true, false); toc;
#   tic; T1_MSFM2 = msfm2d(SpeedImage, SourcePoint, true, true); toc;
#
#   # Show results
#   fprintf('\nResults with T1 (Matlab)\n');
#   fprintf('Method   L1        L2        Linf\n');
#   Results = cellfun(@(x)([mean(abs(T1(:)-x(:))) mean((T1(:)-x(:)).^2) max(abs(T1(:)-x(:)))]), {T1_FMM1(:) T1_MSFM1(:) T1_FMM2(:) T1_MSFM2(:)}, 'UniformOutput',false);
#   fprintf('FMM1:   %9.5f %9.5f %9.5f\n', Results{1}(1), Results{1}(2), Results{1}(3));
#   fprintf('MSFM1:  %9.5f %9.5f %9.5f\n', Results{2}(1), Results{2}(2), Results{2}(3));
#   fprintf('FMM2:   %9.5f %9.5f %9.5f\n', Results{3}(1), Results{3}(2), Results{3}(3));
#   fprintf('MSFM2:  %9.5f %9.5f %9.5f\n', Results{4}(1), Results{4}(2), Results{4}(3));
#
# Example multiple starting points,
#   SourcePoint=rand(2,100)*255+1;
#   SpeedImage = ones([256 256]);
#   tic; T1_MSFM2 = msfm2d(SpeedImage, SourcePoint, true, true); toc;
#   figure, imshow(T1_MSFM2,[]); colormap(hot(256));
#
# Function is written by D.Kroon University of Twente (June 2009)

# Distance image, also used to store the index of narrowband pixels
# during marching process
T = zeros(size(F))-1;

# Augmented Fast Marching (For skeletonize)
Ed=nargout>1;

# Euclidian distance image
if(Ed)
	Y = zeros(size(F))
 end

# Pixels which are processed and have a final distance are frozen
Frozen   = zeros(size(F));

# Free memory to store neighbours of the (segmented) region
neg_free = 100000;
neg_pos=0;
if(Ed)
    neg_list = zeros(4,neg_free);
else
    neg_list = zeros(3,neg_free);
end

# (There are 3 pixel classes:
#   - frozen (processed)
#   - narrow band (boundary) (in list to check for the next pixel with smallest distance)
#   - far (not yet used)

# Neighbours
ne =[-1 0;
    1 0;
    0 -1;
    0 1];

SourcePoints=int32(floor(SourcePoints));

# set all starting points to distance zero and frozen
for z=1:size(SourcePoints,2)
    # starting point
    x= SourcePoints(1,z); y=SourcePoints(2,z);
    # Set starting point to frozen and distance to zero
    Frozen(x,y)=1; T(x,y)=0;
end

# Add all neighbours of the starting points to narrow list
for z=1:size(SourcePoints,2)
    # starting point
    x=SourcePoints(1,z);
	y=SourcePoints(2,z);
    for k=1:4,
        # Location of neighbour
        i=x+ne(k,1); j=y+ne(k,2);
        # Check if current neighbour is not yet frozen and inside the
        # picture
        if((i>0)&&(j>0)&&(i<=size(F,1))&&(j<=size(F,2))&&(Frozen(i,j)==0))
            Tt=1/max(F(i,j),eps);
	        Ty=1;
            # Update distance in neigbour list or add to neigbour list
            if(T(i,j)>0)
				if(neg_list(1,T(i,j))>Tt)
					neg_list(1,T(i,j))=Tt;
	            end
				if(Ed)
                    neg_list(4,T(i,j))=min(Ty,neg_list(4,T(i,j)));
                end
            else
	            neg_pos=neg_pos+1;
                # If running out of memory at a new block
                if(neg_pos>neg_free)
			 neg_free = neg_free +100000;
			 neg_list(1,neg_free)=0;
	         end
                if(Ed)
                    neg_list(:,neg_pos)=[Tt;i;j;Ty];
                else
                    neg_list(:,neg_pos)=[Tt;i;j];
                end
                T(i,j)=neg_pos;
            end
        end
    end
end

# Loop through all pixels of the image
for itt=1:numel(F)
    # Get the pixel from narrow list (boundary list) with smallest
    # distance value and set it to current pixel location
    [t,index]=min(neg_list(1,1:neg_pos));
    if(neg_pos==0)
	 break;
    end
    x=neg_list(2,index); y=neg_list(3,index);
    Frozen(x,y)=1;
    T(x,y)=neg_list(1,index);

    if(Ed)
	 Y(x,y)=neg_list(4,index);
    end

    # Remove min value by replacing it with the last value in the array
    if(index<neg_pos)
        neg_list(:,index)=neg_list(:,neg_pos);
        x2=neg_list(2,index); y2=neg_list(3,index);
        T(x2,y2)=index;
    end
    neg_pos =neg_pos-1;

    # Loop through all 4 neighbours of current pixel
    for k=1:4,
        # Location of neighbour
        i=x+ne(k,1); j=y+ne(k,2);

        # Check if current neighbour is not yet frozen and inside the
        # picture
        if((i>0)&&(j>0)&&(i<=size(F,1))&&(j<=size(F,2))&&(Frozen(i,j)==0))

            Tt=CalculateDistance(T,F(i,j),size(F),i,j,usesecond,usecross,Frozen);
            if(Ed)
                Ty=CalculateDistance(Y,1,size(F),i,j,usesecond,usecross,Frozen);
            end

            # Update distance in neigbour list or add to neigbour list
            if(T(i,j)>0)
                neg_list(1,T(i,j))=min(Tt,neg_list(1,T(i,j)));
                if(Ed)
                    neg_list(4,T(i,j))=min(Ty,neg_list(4,T(i,j)));
                end
            else
                neg_pos=neg_pos+1;
                # If running out of memory at a new block
                if(neg_pos>neg_free)
			 neg_free = neg_free +100000;
			 neg_list(1,neg_free)=0;
	        end
                if(Ed)
                    neg_list(:,neg_pos)=[Tt;i;j;Ty];
                else
                    neg_list(:,neg_pos)=[Tt;i;j];
                end
                T(i,j)=neg_pos;
            end
        end
    end
end

function CalculateDistance(T,Fij,sizeF,i,j,usesecond,usecross,Frozen)
# Boundary and frozen check -> current patch
Tpatch=inf(5,5);
for nx=-2:2
    for ny=-2:2
        in=i+nx; jn=j+ny;
        if((in>0)&&(jn>0)&&(in<=sizeF(1))&&(jn<=sizeF(2))&&(Frozen(in,jn)==1))
            Tpatch(nx+3,ny+3)=T(in,jn);
        end
    end
end
end

# The values in order is 0 if no neighbours in that direction
# 1 if 1e order derivatives is used and 2 if second order
# derivatives are used
Order=zeros(1,4);

# Make 1e order derivatives in x and y direction
Tm(1) = min( Tpatch(2,3) , Tpatch(4,3));
if(isfinite(Tm(1)))
 Order(1)=1;
 end
Tm(2) = min( Tpatch(3,2) , Tpatch(3,4));
if(isfinite(Tm(2)))
 Order(2)=1;
end
# Make 1e order derivatives in cross directions
if(usecross)
    Tm(3) = min( Tpatch(2,2) , Tpatch(4,4));
     if(isfinite(Tm(3)))
      Order(3)=1;
      end
    Tm(4) = min( Tpatch(2,4) , Tpatch(4,2));
    	  if(isfinite(Tm(4)))
		 Order(4)=1;
	   end
end

# Make 2e order derivatives
if(usesecond)
    Tm2=zeros(1,4);
    # pixels with a pixeldistance 2 from the center must be
    # lower in value otherwise use other side or first order
    ch1=(Tpatch(1,3)<Tpatch(2,3))&&isfinite(Tpatch(2,3)); ch2=(Tpatch(5,3)<Tpatch(4,3))&&isfinite(Tpatch(4,3));

    if(ch1&&ch2)
	Tm2(1) =min( (4*Tpatch(2,3)-Tpatch(1,3))/3 , (4*Tpatch(4,3)-Tpatch(5,3))/3);
	 Order(1)=2;
    elseif(ch1)
	 Tm2(1) =(4*Tpatch(2,3)-Tpatch(1,3))/3; Order(1)=2;
    elseif(ch2)
	 Tm2(1) =(4*Tpatch(4,3)-Tpatch(5,3))/3; Order(1)=2;
    end

    ch1=(Tpatch(3,1)<Tpatch(3,2))&&isfinite(Tpatch(3,2)); ch2=(Tpatch(3,5)<Tpatch(3,4))&&isfinite(Tpatch(3,4));

    if(ch1&&ch2)
	Tm2(2) =min( (4*Tpatch(3,2)-Tpatch(3,1))/3 , (4*Tpatch(3,4)-Tpatch(3,5))/3);
	Order(2)=2;
    elseif(ch1)
	 Tm2(2)=(4*Tpatch(3,2)-Tpatch(3,1))/3; Order(2)=2;
    elseif(ch2)
	 Tm2(2)=(4*Tpatch(3,4)-Tpatch(3,5))/3; Order(2)=2;
    end

    if(usecross)
        ch1=(Tpatch(1,1)<Tpatch(2,2))&&isfinite(Tpatch(2,2)); ch2=(Tpatch(5,5)<Tpatch(4,4))&&isfinite(Tpatch(4,4));
        if(ch1&&ch2)
		Tm2(3) =min( (4*Tpatch(2,2)-Tpatch(1,1))/3 , (4*Tpatch(4,4)-Tpatch(5,5))/3); Order(3)=2;
        elseif(ch1)
		 Tm2(3)=(4*Tpatch(2,2)-Tpatch(1,1))/3; Order(3)=2;
        elseif(ch2)
		 Tm2(3)=(4*Tpatch(4,4)-Tpatch(5,5))/3; Order(3)=2;
        end

        ch1=(Tpatch(1,5)<Tpatch(2,4))&&isfinite(Tpatch(2,4)); ch2=(Tpatch(5,1)<Tpatch(4,2))&&isfinite(Tpatch(4,2));
        if(ch1&&ch2)
		Tm2(4) =min( (4*Tpatch(2,4)-Tpatch(1,5))/3 , (4*Tpatch(4,2)-Tpatch(5,1))/3); Order(4)=2;
        elseif(ch1)
		 Tm2(4)=(4*Tpatch(2,4)-Tpatch(1,5))/3; Order(4)=2;
        elseif(ch2)
		 Tm2(4)=(4*Tpatch(4,2)-Tpatch(5,1))/3; Order(4)=2;
        end
    end
else
    Tm2=zeros(1,4);
end

# Calculate the distance using x and y direction
Coeff = [0 0 -1/(max(Fij^2,eps))];
for t=1:2
    if Order(t) == 1
            Coeff=Coeff+[1 -2*Tm(t) Tm(t)^2];
    elseif Order(t) ==2
            Coeff=Coeff+[1 -2*Tm2(t) Tm2(t)^2]*(2.2500);
    end
end

Tt=roots(Coeff); Tt=max(Tt);
# Calculate the distance using the cross directions
if(usecross)
    Coeff = Coeff + [0 0 -1/(max(Fij^2,eps))];
    for t=3:4;
    	if Order(t) == 1
                Coeff=Coeff+0.5*[1 -2*Tm(t) Tm(t)^2];
	elseif Order(t) == 2
                Coeff=Coeff+0.5*[1 -2*Tm2(t) Tm2(t)^2]*(2.2500);
        end
    end
    Tt2=roots(Coeff); Tt2=max(Tt2);
    # Select minimum distance value of both stensils
    if(~isempty(Tt2))
	 Tt=min(Tt,Tt2);
    end
end

# Upwind condition check, current distance must be larger
# then direct neighbours used in solution
DirectNeigbInSol=Tm(isfinite(Tm));
if(nnz(DirectNeigbInSol>=Tt)>0) # Will this ever happen?
    Tt=min(DirectNeigbInSol)+(1/(max(Fij,eps)));
end

#function roots(Coeff)
#    a=Coeff(1);
#    b=Coeff(2);
#    c=Coeff(3);
#    d=max((b*b)-4.0*a*c,0);
#    if a != 0
#        z(1)= (-b - sqrt(d)) / (2.0*a);
#        z(2)= (-b + sqrt(d)) / (2.0*a);
#    else
#        z(1)= (2.0*c)/(-b - sqrt(d));
#        z(2)= (2.0*c)/(-b + sqrt(d));
#    end
#end

end
end


using Manifolds
using ApproxManifoldProducts
using TensorCast
using LinearAlgebra
using Test

##

@testset "test calcProductGaussians" begin
##

M = TranslationGroup(2)
u = [[1;1.0], [0.0;0]]
c = [([1.0;1]), ([1.0;1])]

u_, C_ = calcProductGaussians(M, u, c)
@test isapprox( u_, [0.5, 0.5])
@test isapprox( C_, [0.5 0.0; 0.0 0.5])

##
end

@testset "comparison test with basic product" begin
## simply multiply two beliefs, sim2

N = 10
d = 2
M = TranslationGroup(d)

#densities to multiply
pts1 = [randn(d) for _ in 1:N]
P1 = manikde!(M, pts1, bw=[1;1.0])

pts2 = [randn(d)  for _ in 1:N]
P2 = manikde!(M, pts2, bw=[1;1.0])

##

sl = Vector{Vector{Int}}()

P12 = manifoldProduct([P1;P2], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P12)
@test P12._partial === nothing
# @test isapprox( mean(P12)[1], 0, atol=1 )
# @test isapprox( mean(P12)[2], 0, atol=1 )

(x->println()).(1:5)
@show sl;

P12

## validate selected labels are working properly, with addEntropy=false

for sidx in 1:N

  bw1 = getBW(P1)[:,1] .^ 2
  bw2 = getBW(P2)[:,1] .^ 2

  u1 = pts1[sl[sidx][1]]
  u2 = pts2[sl[sidx][2]]

  u12, = calcProductGaussians(M, [u1,u2], [bw1,bw2]);

  @test isapprox( u12, getPoints(P12)[sidx] )

end

##
end

@testset "test dim=2 product with one partial/marginal" begin
## basic test one full with one partial

N = 50
d = 2
M = TranslationGroup(d)

#densities to multiply
pts1 = [randn(d) for _ in 1:N]
P1 = manikde!(M, pts1)

pts2 = [randn(d) for _ in 1:N]
(x->(x[2]+=100)).(pts2)
P2_ = manikde!(M, pts2, partial=[1;])

##

sl = Vector{Vector{Int}}()

P12_ = manifoldProduct([P1;P2_], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P12_)

@test isapprox( mean(P12_)[1], 0, atol=1 )
@test isapprox( mean(P12_)[2], 0, atol=1 )

(x->println()).(1:5)
# @show sl

P12_

##

for sidx in 1:N

  bw1 = getBW(P1, false)[:,1] .^2
  bw2 = getBW(P2_, false)[:,1] .^2

  u1 = pts1[sl[sidx][1]]
  u2 = pts2[sl[sidx][2]]

  u12, = calcProductGaussians(M, [u1,u2], [bw1,bw2]);

  @test isapprox( u12[1], getPoints(P12_)[sidx][1] , atol=0.1)
  @test isapprox( pts1[sl[sidx][1]][2], getPoints(P12_)[sidx][2] )

end

##
end

@testset "test dim=2 product with one full and two similar marginals" begin
## basic test one full with one partial

N = 50
d = 2
M = TranslationGroup(d)

#densities to multiply
pts1 = [randn(d) for _ in 1:N]
P1 = manikde!(M, pts1)

pts2 = [randn(d) for _ in 1:N]
(x->(x[2]+=100)).(pts2)
P2_ = manikde!(M, pts2, partial=[1;])

pts3 = [randn(d) for _ in 1:N]
(x->(x[2]+=100)).(pts3)
P3_ = manikde!(M, pts3, partial=[1;])

##

sl = Vector{Vector{Int}}()

P123_ = manifoldProduct([P1;P2_;P3_], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P123_)

@test isapprox( mean(P123_)[1], 0, atol=1 )
@test isapprox( mean(P123_)[2], 0, atol=1 )

(x->println()).(1:5)
# @show sl

P123_

##

for sidx in 1:N

  bw1 = getBW(P1, false)[:,1] .^2
  bw2 = getBW(P2_, false)[:,1] .^2
  bw3 = getBW(P3_, false)[:,1] .^2

  u1 = pts1[sl[sidx][1]]
  u2 = pts2[sl[sidx][2]]
  u3 = pts3[sl[sidx][3]]

  u123, = calcProductGaussians(M, [u1,u2,u3], [bw1,bw2,bw3]);

  @test isapprox( u123[1], getPoints(P123_)[sidx][1] , atol=0.1)
  @test isapprox( pts1[sl[sidx][1]][2], getPoints(P123_)[sidx][2] )

end

##
end

@testset "test dim=2 product with one full and two different marginals" begin
## random data

N = 50
M = TranslationGroup(2)

pts1 = [randn(2) .- 10.0 for _ in 1:N]
pts2 = [randn(2) for _ in 1:N]
pts3 = [randn(2) .+ 10.0 for _ in 1:N]

# get different marginals

P1 = marginal(manikde!(M, pts1), [1;])
P2 =          manikde!(M, pts2)
P3 = marginal(manikde!(M, pts3), [2;])

##

sl = Vector{Vector{Int}}()
P = manifoldProduct([P2;P1;P3], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P)

(x->println()).(1:5)
# @show sl;
P

## check the results

pts = getPoints(P)
@cast pGM[i,j] := pts[j][i]

@test 0.7*N < sum(-10 .< pGM[1,:] .< 0)
@test 0.7*N < sum(0 .< pGM[2,:] .< 10 )

## check the selection of labels and resulting Gaussian products are correct

for sidx in 1:N

  bw1 = getBW(P1, false)[:,1] .^2
  bw2 = getBW(P2, false)[:,1] .^2
  bw3 = getBW(P3, false)[:,1] .^2

  # full density first
  u2 = pts2[sl[sidx][1]]
  u1 = pts1[sl[sidx][2]]
  u3 = pts3[sl[sidx][3]]

  u12, = calcProductGaussians(M, [u1,u2], [bw1,bw2]);
  u23, = calcProductGaussians(M, [u2,u3], [bw2,bw3]);

  @test isapprox( u12[1], getPoints(P)[sidx][1])
  @test isapprox( u23[2], getPoints(P)[sidx][2])

end

##
end

@testset "product of only one marginal per each of two dimensions" begin
## random data

N = 50
M = TranslationGroup(2)

pts1 = [randn(2) .- 10.0 for _ in 1:N]
pts3 = [randn(2) .+ 10.0 for _ in 1:N]

# different marginals

P1 = marginal(manikde!(M, pts1), [1;])
P3 = marginal(manikde!(M, pts3), [2;])

##

sl = Vector{Vector{Int}}()

P_ = manifoldProduct([P1;P3], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P_)

(x->println()).(1:5)
# @show sl

##

pts = getPoints(P_)
@cast pGM[i,j] := pts[j][i]

@test 0.7*N < sum(-13 .< pGM[1,:] .< -7)
@test 0.7*N < sum(  7 .< pGM[2,:] .< 13 )

## check the selection of labels and resulting Gaussian products are correct

for sidx in 1:N

  bw1 = getBW(P1, false)[:,1] .^2
  bw3 = getBW(P3, false)[:,1] .^2

  u1 = pts1[sl[sidx][1]]
  u3 = pts3[sl[sidx][2]]

  @test isapprox( u1[1], pts[sidx][1])
  @test isapprox( u3[2], pts[sidx][2])

end

##
end

@testset "test dim=2 product of only marginals, two per dimension, 4 factors total" begin
##

N = 50
d = 2
M = TranslationGroup(d)

pts4 = [randn(d) .- 10.0 for _ in 1:N]
(x->x[2]-=90.0).(pts4)
pts5 = [randn(d) .+ 10.0 for _ in 1:N]
(x->x[1]+=90.0).(pts5)

P4 = marginal(manikde!(M, pts4), [1;])
P5 = marginal(manikde!(M, pts5), [d;])

# test duplication
pts4_ = [randn(d) .- 10.0 for _ in 1:N]
(x->x[2]-=90.0).(pts4_)
pts5_ = [randn(d) .+ 10.0 for _ in 1:N]
(x->x[1]+=90.0).(pts5_)

P4_ = marginal(manikde!(M, pts4_), [1;])
P5_ = marginal(manikde!(M, pts5_), [d;])

##

sl = Vector{Vector{Int}}()

P45__ = manifoldProduct([P4;P4_;P5;P5_], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P45__)

(x->println()).(1:5)
# @show sl;

P45__

## check the selection of labels and resulting Gaussian products are correct

# println("getPoints(P45__) = ")
# getPoints(P45__) .|> println
# println()

# sidx = 1
for sidx in 1:N

  bw1 = getBW(P4, false)[:,1] .^2
  bw2 = getBW(P4_, false)[:,1] .^2
  bw3 = getBW(P5, false)[:,1] .^2
  bw4 = getBW(P5_, false)[:,1] .^2

  u1 = pts4[ sl[sidx][1]]
  u2 = pts4_[sl[sidx][2]]
  u3 = pts5[ sl[sidx][3]]
  u4 = pts5_[sl[sidx][4]]

  u12, = calcProductGaussians(M, [u1,u2], [bw1,bw2]);
  u34, = calcProductGaussians(M, [u3,u4], [bw3,bw4]);

  # @info "DEBU" string(u1) string(u2) string(u3) string(u4) u12[1], u34[2]
  @test isapprox( u12[1], getPoints(P45__)[sidx][1])
  @test isapprox( u34[2], getPoints(P45__)[sidx][2])

end

##
end

@testset "test dim=3 product with one full and two different marginals" begin
## random data

d = 3
N = 50
M = TranslationGroup(3)

pts1 = [randn(d) .- 10.0 for _ in 1:N]
pts2 = [randn(d) for _ in 1:N]
pts3 = [randn(d) .+ 10.0 for _ in 1:N]

## get different marginals

P1 = marginal(manikde!(M, pts1), [1;])
P2 =          manikde!(M, pts2)
P3 = marginal(manikde!(M, pts3), [d;])

##

# @test_broken false
# # P = manifoldProduct([P1;P2;P3])
# @error "manifoldProduct needs complete point type for keyword `oldPoints`, current tests assume no marginal beliefs at front of product array"
sl = Vector{Vector{Int}}()
P = manifoldProduct([P2;P1;P3], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P)

(x->println()).(1:5)
# @show sl;
P

## check the results

pts = getPoints(P)
@cast pGM[i,j] := pts[j][i]

@test 0.6*N < sum(-10 .< pGM[1,:] .< 0)
@test 0.6*N < sum(  0 .< pGM[3,:] .< 10 )

## check the selection of labels and resulting Gaussian products are correct

for sidx in 1:N

  bw1 = getBW(P1, false)[:,1] .^2
  bw2 = getBW(P2, false)[:,1] .^2
  bw3 = getBW(P3, false)[:,1] .^2

  # full density first
  u2 = pts2[sl[sidx][1]]
  u1 = pts1[sl[sidx][2]]
  u3 = pts3[sl[sidx][3]]

  u12, = calcProductGaussians(M, [u1,u2], [bw1,bw2]);
  u23, = calcProductGaussians(M, [u2,u3], [bw2,bw3]);

  @test isapprox( u12[1], getPoints(P)[sidx][1])
  @test isapprox(  u2[2], getPoints(P)[sidx][2] )
  @test isapprox( u23[3], getPoints(P)[sidx][3])

end

##
end

@testset "test dim=3 product with one full and two different marginals (marginal first in product)" begin
## random data

d = 3
N = 50
M = TranslationGroup(3)

pts1 = [randn(d) .- 10.0 for _ in 1:N]
pts2 = [randn(d) for _ in 1:N]
pts3 = [randn(d) .+ 10.0 for _ in 1:N]

## get different marginals

P1 = marginal(manikde!(M, pts1), [1;])
P2 =          manikde!(M, pts2)
P3 = marginal(manikde!(M, pts3), [d;])

##

sl = Vector{Vector{Int}}()
P = manifoldProduct([P1;P2;P3], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test !isPartial(P)

(x->println()).(1:5)
# @show sl;
P

## check the results

pts = getPoints(P)
@cast pGM[i,j] := pts[j][i]

@test 0.8*N < sum(-10 .< pGM[1,:] .<  0 )
@test 0.8*N < sum(  0 .< pGM[3,:] .< 10 )

## check the selection of labels and resulting Gaussian products are correct

for sidx in 1:N

  bw1 = getBW(P1, false)[:,1] .^2
  bw2 = getBW(P2, false)[:,1] .^2
  bw3 = getBW(P3, false)[:,1] .^2

  # full density first
  u1 = pts1[sl[sidx][1]]
  u2 = pts2[sl[sidx][2]]
  u3 = pts3[sl[sidx][3]]

  u12, = calcProductGaussians(M, [u1,u2], [bw1,bw2]);
  u23, = calcProductGaussians(M, [u2,u3], [bw2,bw3]);

  @test isapprox( u12[1], getPoints(P)[sidx][1])
  @test isapprox(  u2[2], getPoints(P)[sidx][2])
  @test isapprox( u23[3], getPoints(P)[sidx][3])

end

##
end

@testset "test dim=3 product with two different marginals and one open dimension" begin
## random data

d = 3
N = 50
M = TranslationGroup(3)

pts1 = [randn(d) .- 10.0 for _ in 1:N]
pts3 = [randn(d) .+ 10.0 for _ in 1:N]

## get different marginals

P1 = marginal(manikde!(M, pts1), [1;])
P3 = marginal(manikde!(M, pts3), [d;])

##

sl = Vector{Vector{Int}}()
P = manifoldProduct([P1;P3], recordLabels=true, selectedLabels=sl, addEntropy=false)

@test isPartial(P)
@test P._partial == [1;3]

(x->println()).(1:5)
# @show sl;
P

## check the results

pts = getPoints(P, false)
@cast pGM[i,j] := pts[j][i]

@test 0.7*N < sum(-13 .< pGM[1,:] .< -7 )
@test 0.7*N < sum(  7 .< pGM[3,:] .< 13 )

## check the selection of labels and resulting Gaussian products are correct

for sidx in 1:N

  bw1 = getBW(P1)[:,1] .^2
  bw3 = getBW(P3)[:,1] .^2

  # full density first
  u1 = pts1[sl[sidx][1]]
  u3 = pts3[sl[sidx][2]]

  @test isapprox( u1[1], getPoints(P, false)[sidx][1])
  @test isapprox( u3[3], getPoints(P, false)[sidx][3])

end

##
end

#


#= The famous Tower of Hanoi question where there are 3 towers with discs in them. You need
to transfer all the discs from A to C =#

## Function

function towerOfHanoi(discs, source = "A", destination = "C", helper = "B")
    if (discs == 0)
        return
    end
    towerOfHanoi(discs - 1, source, helper, destination)
    println("Move from $(source) to $(destination)")
    towerOfHanoi(discs - 1, helper, destination, source)
end

## Input

println("Enter the number of discs: ")
n = readline()
n = parse(Int64, n)

## Calling the function

towerOfHanoi(n)

#=
Sample Test Case:
Input:
    Enter the number of discs: 3
Output:
    Move from A to C
    Move from A to B
    Move from C to B
    Move from A to C
    Move from B to A
    Move from B to C
    Move from A to C
Time complexity: O(2^N)
=#


# # Temperature and metallic systems
#
# In this example we consider the modeling of a magnesium lattice
# as a simple example for a metallic system.
# For our treatment we will use the PBE exchange-correlation functional.
# First we import required packages and setup the lattice.
# Again notice that DFTK uses the convention that lattice vectors are
# specified column by column.

using DFTK
using Plots
using Unitful
using UnitfulAtomic

a = 3.01794  # bohr
b = 5.22722  # bohr
c = 9.77362  # bohr
lattice = [[-a -a  0]; [-b  b  0]; [0   0 -c]]
Mg = ElementPsp(:Mg, psp=load_psp("hgh/pbe/Mg-q2"))
atoms = [Mg => [[2/3, 1/3, 1/4], [1/3, 2/3, 3/4]]];

# Next we build the PBE model and discretize it.
# Since magnesium is a metal we apply a small smearing
# temperature to ease convergence using the Fermi-Dirac
# smearing scheme. Note that both the `Ecut` is too small
# as well as the minimal ``k``-point spacing
# `kspacing` far too large to give a converged result.
# These have been selected to obtain a fast execution time.
# By default `PlaneWaveBasis` chooses a `kspacing`
# of `2π * 0.022` inverse Bohrs, which is much more reasonable.

kspacing = 0.945 / u"angstrom"        # Minimal spacing of k-points,
##                                      in units of wavevectors (inverse Bohrs)
Ecut = 5                              # Kinetic energy cutoff in Hartree
temperature = 0.01                    # Smearing temperature in Hartree
smearing = DFTK.Smearing.FermiDirac() # Smearing method
##                                      also supported: Gaussian,
##                                      MarzariVanderbilt,
##                                      and MethfesselPaxton(order)

model = model_DFT(lattice, atoms, [:gga_x_pbe, :gga_c_pbe];
                  temperature=temperature,
                  smearing=smearing)
kgrid = kgrid_from_minimal_spacing(lattice, kspacing)
basis = PlaneWaveBasis(model; Ecut, kgrid);

# Finally we run the SCF. Two magnesium atoms in
# our pseudopotential model result in four valence electrons being explicitly
# treated. Nevertheless this SCF will solve for eight bands by default
# in order to capture partial occupations beyond the Fermi level due to
# the employed smearing scheme. In this example we use a damping of `0.8`.
# The default `LdosMixing` should be suitable to converge metallic systems
# like the one we model here. For the sake of demonstration we still switch to
# Kerker mixing here.
scfres = self_consistent_field(basis, damping=0.8, mixing=KerkerMixing());
#-
scfres.occupation[1]
#-
scfres.energies

# The fact that magnesium is a metal is confirmed
# by plotting the density of states around the Fermi level.
plot_dos(scfres)


in = "128392-643281"
sta, sto = parse.(Int, split(in, '-'))

ispass(dif) = any(==(0), dif) && all(>=(0), dif)
ispassword(num, pred) = pred(diff(reverse(digits(num))))
count(x->ispassword(x, ispass), sta:sto)

function ispass2(dif)
    all(>=(0), dif) || return false
    reps = findall(==(0), dif)
    isempty(reps) && return false
    for r ∈ reps
        r-1 ∈ reps || r + 1 ∈ reps || return true
    end
    false
end

count(x->ispassword(x, ispass2), sta:sto)


#=
Copyright (c) 2015, Intel Corporation

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of Intel Corporation nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=#

using FactCheck
using Latte

function convolution_forward(filter::Array, bias::Array, input::Array,
                             output::Array, stride, pad, kernel)
    width, height, channel, num = size(input)
    width_out, height_out, n_filter, _ = size(output)

    for n = 1:num, o = 1:n_filter, y = 1:height_out, x = 1:width_out,
        k = 1:channel, p = 1:kernel, q = 1:kernel

        in_y = (y-1) * stride - pad + p
        in_x = (x-1) * stride - pad + q
        if (in_y >= 1 && in_y <= height && in_x >= 1 && in_x <= width)
            filter_idx = ((k - 1) * kernel + (p - 1)) * kernel + q
            output[x, y, o, n] += input[in_x, in_y, k, n] *
                                  filter[filter_idx, o]
        end
    end

    # add bias
    for n = 1:num, o = 1:n_filter, y = 1:height_out, x = 1:width_out
        output[x, y, o, n] += bias[o]
    end

    return output
end

function convolution_backward(filter::Array, bias::Array, input::Array,
                              top_diff::Array, stride, pad, kernel,
                              ∇filter::Array, ∇bias::Array, ∇input::Array)
    width, height, channels, num = size(input)
    width_out, height_out, n_filter, _ = size(top_diff)

    # ∇bias
    for n = 1:num, o = 1:n_filter, y = 1:height_out, x = 1:width_out
        ∇bias[o] += top_diff[x, y, o, n]
    end

    # ∇filter and ∇input
    for n = 1:num, o = 1:n_filter, k = 1:channels, y = 1:height_out,
        x = 1:width_out, p = 1:kernel, q = 1:kernel

        in_y = (y-1) * stride - pad + p
        in_x = (x-1) * stride - pad + q
        if (in_y >= 1 && in_y <= height && in_x >= 1 && in_x <= width)
            filter_idx = ((k - 1) * kernel + (p - 1)) * kernel + q
            ∇filter[filter_idx, o] += top_diff[x,y,o,n] * input[in_x,in_y,k,n]
            ∇input[in_x,in_y,k,n] += top_diff[x,y,o,n] * filter[filter_idx,o]
        end
    end

    return (∇filter, ∇bias, ∇input)
end

pad = 1

srand(1234)
net = Net(8)
data,  data_value   = MemoryDataLayer(net, :data, (227, 227, 3))
label, label_value = MemoryDataLayer(net, :label, (1,))
data_value[:]  = rand(Float32, size(data_value)...) * 256
label_value[:] = map(floor, rand(Float32, size(label_value)...) * 10)
conv1        = ConvolutionLayer(:conv1, net, data, 10, 3, 1, pad)
conv2        = ConvolutionLayer(:conv2, net, conv1, 10, 3, 1, pad)
fc1          = InnerProductLayer(:fc1, net, conv2, 10)
loss         = SoftmaxLossLayer(:loss, net, fc1, label)

init(net)
ϵ = 1e-3

input    = get_buffer(net, :conv1value)
∇input   = get_buffer(net, :conv1∇)
filters  = get_buffer(net, :conv2weights)
∇filters = get_buffer(net, :conv2∇weights)
bias     = get_buffer(net, :conv2bias)
rand!(bias)
∇bias    = get_buffer(net, :conv2∇bias)
top_diff = get_buffer(net, :conv2∇)
# rand!(top_diff)
expected = zeros(get_buffer(net, :conv2value))
∇input_expected   = zeros(∇input)
∇filters_expected = zeros(size(∇filters)[1:end-1])
∇bias_expected    = zeros(size(∇bias)[1:end-1])

params = SolverParameters(
    lr_policy    = LRPolicy.Decay(.01f0, 5.0f-7),
    mom_policy   = MomPolicy.Fixed(0.9),
    max_epoch    = 300,
    regu_coef    = .0005)
sgd = SGD(params)

facts("Testing Convolution Layer") do
    context("Forward") do
        forward(net; solver=sgd)
        convolution_forward(filters, bias, input, expected, 1, pad, 3)
        # @fact expected --> roughly(net.buffers[:conv2value])
        @fact all(-ϵ .< expected - get_buffer(net, :conv2value) .< ϵ) --> true
    end
    context("Backward") do
        clear_∇(net)
        backward(net)
        convolution_backward(filters, bias, input, top_diff, 1, pad, 3,
                             ∇filters_expected, ∇bias_expected,
                             ∇input_expected)
        @fact all(-ϵ .< ∇input - ∇input_expected .< ϵ) --> true
        @fact all(-ϵ .< sum(∇filters, 3)[:,:,1] - ∇filters_expected .< ϵ) --> true
        @fact all(-ϵ .< sum(∇bias, 3)[:,:,1] - ∇bias_expected .< ϵ) --> true
    end
end

FactCheck.exitstatus()


using DataStructures

randint(μ, σ) = max(σ, round(Int, randn()σ + μ))

"""
Generate testdata that NIST sclite can read.  Run sclite like:
```
sclite -r ref.txt -h hyp.txt -i rm -o pralign
```
"""
function gentestdata(N::Integer; nw=100, nsub=10, nins=10, ndel=10, V=25, σ=10)
	srand(N)
	reffd = open("test/ref.txt", "w")
	hypfd = open("test/hyp.txt", "w")
	res = DefaultOrderedDict{AbstractString, Vector}(Vector)
	for i in 1:N
		ref = rand(1:V, randint(nw, σ))
		hyp = ref[:]
		for j in 1:randint(nsub, σ)
			hyp[rand(1:length(hyp))] = rand(1:V)
		end
		for j in 1:randint(nins, σ)
			insert!(hyp, rand(1:length(hyp)), rand(1:V))
		end
		for j in 1:randint(ndel, σ)
			deleteat!(hyp, rand(1:length(hyp)))
		end
		id = "a-$i"
		println(reffd, join([string(x) for x in ref], " "), " ($id)")
		println(hypfd, join([string(x) for x in hyp], " "), " ($id)")
		push!(res[id], [string(x) for x in ref])
		push!(res[id], [string(x) for x in hyp])
	end
	close(reffd)
	close(hypfd)
	return res
end

function readtestdata()
	res = DefaultOrderedDict{AbstractString, Vector}(Vector)
	for file in ["ref.txt", "hyp.txt"]
		open(file) do fd
			for line in eachline(fd)
				words = line |> chomp |> split
				id = strip(pop!(words), ['(', ')'])
				push!(res[id], words)
			end
		end
	end
	return res
end

function readsclitepralign(file="hyp.txt.pra")
	res = OrderedDict()
	open(file) do fd
		while !eof(fd)
			m = match(r"id: \(([^()]+)\)", readline(fd))
			if m != nothing
				id = m[1]
				res[id] = join([readline(fd) for i in 1:4], "\n")
			end
		end
	end
	return res
end


"""
    V(N)

Vocabulary size. (Tweedie & Baayen, 1998: p. 326)
"""
V(N::Int) = unique() # TODO

"""
    V(i,N)

Frequency spectrum
"""
V(i::Int,N::Int) = unique() # TODO

# function frequency_spectrum(tokens)
#     fl = Dict{String,Int64}()
#     fs = Dict{Int64,Int64}()
#     for token in tokens
#         fl[token] = get(fl, token, 0) + 1
#     end
#     for f in values(fl)
#         fs[f] = get(fs, f, 0) + 1
#     end
#     fs
# end

"""
    P(N)

Vocabulary growth rate. (Tweedie & Baayen, 1998: p. 325)
"""
P(N::Int) = V(1, N)/N

"""
    MWF(N)

Mean word frequency (Eq. 2)

```math
MWF(N) = \\frac{N}{V(N)}
```
"""
MWF(N) = N/V(N)

"""
    TTR(N)

Type token ratio (Eq. 3)

```math
TTR(N) = \\frac{V(N)}{N}
```
"""
ttr(tokens::Array{T}) where T = length(unique(tokens))/length(tokens)

"""
    guiraud_r(N)

Guiraud (1954) (T&B Eq. 4)

```math
r = \\frac{V(N)}{\\sqrt{N}}
```
"""
guiraud_r(N) = V(N)/sqrt(N)

"""
    herdan_c(N,c)

Herdan (1960; 1964) (T&B Eq. 5)

```math
c = 0.0
```
"""
herdan_c(N,c) = log(N^c)/log(N)

"""
    dugast_k(N)

Dugast (1979) (T&B Eq. 6)

```math
k = \\frac{\\log V(N)}{\\log(\\log N)}
```
"""
dugast_k(N) = log(log(k,N))/log(log(N))

"""
    a²(N)

Maas (1972)

```math
V(N) = N \\log^{-a^2} N
```
"""
a²(N) = (log(N)-log(V(N)))/log(2,N)

"""
    dugast_U(N)

Dugast U (1978, 1979) (Eq. 8)

```math
V(N) = N \\log^{-1/U} N
```
"""
dugast_U(N) = 1/a²(N)

"""
    LN(N)

Lukjanenkov & Nesitoj (1975; in Tulvada, 1977) (Eq. 9)

```math
V(N) = \\frac{1}{\\sqrt{1 + LN \\log N}}
```
"""
LN(N) = (1-V(N)^2)/(V(N)^2*log(N))

"""
    brunet_W(N)

Brunet's parametric expression W (1978) (Eq. 10)

```math
W = N^{V(N)^{-a}},
```
where ```a = -0.172``` and
```math
V(N) = (\\frac{\\log W}{\\log N})^a = \\log^a_N W.
```
"""
brunet_W(N) = N^(V(N)^(-0.172))


struct CellQuadratures
    quads::Any
    celltoquad::Any
    ncells::Any
    function CellQuadratures(quads, celltoquad)
        nphase, ncells = size(celltoquad)
        @assert nphase == 2
        @assert all(celltoquad .>= 0)
        @assert all(celltoquad .<= length(quads))
        new(quads, celltoquad, ncells)
    end
end

function CellQuadratures(mesh, levelset, numuniformqp, numcutqp)
    polyorder = order(levelset)
    dim = dimension(mesh)
    interpgrad = InterpolatingPolynomial(dim, dim, polyorder)
    numcells = number_of_cells(mesh)
    tpq = tensor_product_quadrature(dim, numuniformqp)

    quads = [tpq]

    celltoquad = zeros(Int, 2, numcells)
    xL, xR = [-1.0, -1.0], [1.0, 1.0]

    for cellid = 1:numcells
        s = cell_sign(mesh, cellid)
        if s == +1
            celltoquad[1, cellid] = 1
        elseif s == -1
            celltoquad[2, cellid] = 1
        elseif s == 0
            nodeids = nodal_connectivity(background_mesh(mesh), cellid)
            load_coefficients!(levelset, cellid)
            update_interpolating_gradient!(interpgrad, interpolater(levelset))

            try
                pquad = area_quadrature(
                    interpolater(levelset),
                    interpgrad,
                    +1,
                    xL,
                    xR,
                    numcutqp,
                    numsplits = 2,
                )
                push!(quads, pquad)
                celltoquad[1, cellid] = length(quads)

                nquad = area_quadrature(
                    interpolater(levelset),
                    interpgrad,
                    -1,
                    xL,
                    xR,
                    numcutqp,
                    numsplits = 2,
                )
                push!(quads, nquad)
                celltoquad[2, cellid] = length(quads)
            catch e
                pquad = area_quadrature(
                    interpolater(levelset),
                    interpgrad,
                    +1,
                    xL,
                    xR,
                    numcutqp,
                    numsplits = 3,
                )
                push!(quads, pquad)
                celltoquad[1, cellid] = length(quads)

                nquad = area_quadrature(
                    interpolater(levelset),
                    interpgrad,
                    -1,
                    xL,
                    xR,
                    numcutqp,
                    numsplits = 3,
                )
                push!(quads, nquad)
                celltoquad[2, cellid] = length(quads)
            end
        else
            error("Expected cellsign ∈ {-1,0,+1}, got cellsign = $s")
        end
    end
    return CellQuadratures(quads, celltoquad)
end

function CellQuadratures(cutmesh, levelset, numqp)
    return CellQuadratures(cutmesh, levelset, numqp, numqp)
end

function Base.getindex(vquads::CellQuadratures, s, cellid)
    row = cell_sign_to_row(s)
    idx = vquads.celltoquad[row, cellid]
    idx > 0 ||
        error("Cell $cellid, cellsign $s, does not have a cell quadrature")
    return vquads.quads[vquads.celltoquad[row, cellid]]
end

function Base.show(io::IO, cellquads::CellQuadratures)
    ncells = cellquads.ncells
    nuniquequads = length(cellquads.quads)
    str = "CellQuadratures\n\tNum. Cells: $ncells\n\tNum. Unique Quadratures: $nuniquequads"
    print(io, str)
end

function uniform_cell_quadrature(vquads::CellQuadratures)
    return vquads.quads[1]
end

function update_cell_quadrature!(cellquads::CellQuadratures, s, cellid, quad)
    row = cell_sign_to_row(s)
    idx = cellquads.celltoquad[row, cellid]
    cellquads.quads[idx] = quad
end


module Lib

export ackermann

function ackermann(m, n)
  if m == 0
    return n + 1
  elseif n == 0
    return ackermann(m-1, 1)
  else
    return ackermann(m-1, ackermann(m, n-1))
  end
end

end


line_pattern = r"(\d+)-(\d+) (\w): (\w+)"

input = map(eachline("a2.txt")) do line
    m = match(line_pattern, line)
    first, last, required, word = m.captures
    parse(Int16, first), parse(Int16, last), required[1], collect(word)
end

part1 = count(input) do (min, max, required, word)
  min <= count(==(required), word) <= max
end
print("$part1\n")

part2 = count(input) do (first, last, required, word)
  (word[first] == required) ⊻ (word[last] == required)
end
print("$part2\n")


module CellDofBasesTests

using Test
using Gridap.Geometry
using Gridap.FESpaces
using Gridap.Arrays
using Gridap.Fields
using Gridap.CellData

domain =(0,1,0,1)
partition = (3,3)
model = CartesianDiscreteModel(domain,partition)

order = 1

V = TestFESpace(
  reffe = :Lagrangian,
  conformity = :H1,
  valuetype=Float64,
  order = order,
  model = model,
  dirichlet_tags = [1,6],
  dof_space = :physical)

cell_dof_basis = get_cell_dof_basis(V)
@test is_in_physical_space(cell_dof_basis)

indices = [2,3,3,1]

f = reindex(cell_dof_basis,indices)
@test length(get_array(f)) == length(indices)
@test is_in_physical_space(f)

cell_basis = get_cell_basis(V)
@test is_in_physical_space(cell_basis)

test_cell_dof_basis(cell_dof_basis,cell_basis)

#display(evaluate(cell_dof_basis,cell_basis))

V = TestFESpace(
  reffe = :Lagrangian,
  conformity = :H1,
  valuetype=Float64,
  order = order,
  model = model,
  dirichlet_tags = [1,6])

cell_dof_basis = get_cell_dof_basis(V)
@test is_in_ref_space(cell_dof_basis)

cell_basis = get_cell_basis(V)
@test is_in_ref_space(cell_basis)

test_cell_dof_basis(cell_dof_basis,cell_basis)

#display(evaluate(cell_dof_basis,cell_basis))

end # module


using ModelingToolkit

@variables t x(t) v(t) u(t)
@parameters p[1:2]
D = Differential(t)

loss = (4-x)^2 + 2v^2 + u^2
eqs = [
    D(x) ~ v - p[2]*x
    D(v) ~ p[1]*u^3 + v
]

sys = ControlSystem(loss,eqs,t,[x,v],[u],p)
dt = 0.1
tspan = (0.0,1.0)
sys = runge_kutta_discretize(sys,dt,tspan)

u0 = rand(112) # guess for the state values
prob = OptimizationProblem(sys,u0,[0.1],grad=true)


@testset "ff f4 reduce sort invariant" begin
    R, (x, y, z) = PolynomialRing(GF(2^31 - 1), ["x","y","z"], ordering=:lex)

    @test Groebner.groebner([x, y]) == Groebner.groebner([y, x]) == [y, x]

    fs = [x^2 + y, x*y]
    @test Groebner.groebner(fs) == [y^2, x*y, x^2 + y]

end

@testset "ff f4 yesreduce" begin

    root = Groebner.change_ordering(Groebner.rootn(3, ground=GF(2^31 - 1)), :degrevlex)
    x1, x2, x3 = gens(parent(first(root)))
    gb = Groebner.groebner(root, reduced=true)
    @test gb == [x1 + x2 + x3, x2^2 + x2*x3 + x3^2, x3^3 + 2147483646]

    root = Groebner.change_ordering(Groebner.rootn(6, ground=GF(2^31 - 1)), :degrevlex)
    x1, x2, x3, x4, x5, x6 = gens(parent(first(root)))
    gb = Groebner.groebner(root, reduced=true)
    @test gb == [
        x1 + x2 + x3 + x4 + x5 + x6,
        x2^2 + x2*x3 + x3^2 + x2*x4 + x3*x4 + x4^2 +
        x2*x5 + x3*x5 + x4*x5 + x5^2 + x2*x6 + x3*x6 + x4*x6 +
        x5*x6 + x6^2, x3^3 + x3^2*x4 + x3*x4^2 + x4^3 +
        x3^2*x5 + x3*x4*x5 + x4^2*x5 + x3*x5^2 + x4*x5^2 +
        x5^3 + x3^2*x6 + x3*x4*x6 + x4^2*x6 + x3*x5*x6 +
        x4*x5*x6 + x5^2*x6 + x3*x6^2 + x4*x6^2 + x5*x6^2 +
        x6^3, x4^4 + x4^3*x5 + x4^2*x5^2 + x4*x5^3 + x5^4 +
        x4^3*x6 + x4^2*x5*x6 + x4*x5^2*x6 + x5^3*x6 + x4^2*x6^2 +
        x4*x5*x6^2 + x5^2*x6^2 + x4*x6^3 + x5*x6^3 + x6^4, x5^5 +
        x5^4*x6 + x5^3*x6^2 + x5^2*x6^3 + x5*x6^4 + x6^5,
        x6^6 + 2147483646]

    ku = Groebner.change_ordering(Groebner.ku10(ground=GF(2^31-1)), :degrevlex)
    x1, x2, x3, x4, x5, x6, x7, x8, x9, x10 = gens(parent(first(ku)))
    gb = Groebner.groebner(ku, reduced=true)

    @test gb == [
        x9 + 1272065637*x10 + 875418006,
        x8 + 1529540685*x10 + 617942964,
        x7 + 1539832471*x10 + 607651173,
        x6 + 1314302432*x10 + 833181218,
        x5 + 1453635454*x10 + 693848197,
        x4 + 673118236*x10 + 1474365406,
        x3 + 269783061*x10 + 1877700587,
        x2 + 1042807874*x10 + 1104675778,
        x1 + 389079675*x10 + 1758403970,
        x10^2 + 1222705397*x10 + 924778249]

end


@snn_kw struct HHParameter{FT=Float32}
    Cm::FT = 1uF * cm^(-2) * 20000um^2
    gl::FT = 5e-5siemens * cm^(-2) * 20000um^2
    El::FT = -65mV
    Ek::FT = -90mV
    En::FT = 50mV
    gn::FT = 100msiemens * cm^(-2) * 20000um^2
    gk::FT = 30msiemens * cm^(-2) * 20000um^2
    Vt::FT = -63mV
    τe::FT = 5ms
    τi::FT = 10ms
    Ee::FT = 0mV
    Ei::FT = -80mV
end

@snn_kw mutable struct HH{VFT=Vector{Float32},VBT=Vector{Bool}}
    param::HHParameter = HHParameter()
    N::Int32 = 100
    v::VFT = param.El .+ 5(randn(N) .- 1)
    m::VFT = zeros(N)
    n::VFT = zeros(N)
    h::VFT = ones(N)
    ge::VFT  = (1.5randn(N) .+ 4) .* 10nS
    gi::VFT  = (12randn(N) .+ 20) .* 10nS
    fire::VBT = zeros(Bool, N)
    I::VFT = zeros(N)
    records::Dict = Dict()
end

"""
[Hodgkin–Huxley Neuron](https://en.wikipedia.org/wiki/Hodgkin%E2%80%93Huxley_model)
"""
HH

function integrate!(p::HH, param::HHParameter, dt::Float32)
    @unpack N, v, m, n, h, ge, gi, fire, I = p
    @unpack Cm, gl, El, Ek, En, gn, gk, Vt, τe, τi, Ee, Ei = param
    @inbounds for i = 1:N
        m[i] += dt * (0.32f0 * (13f0 - v[i] + Vt) / (exp((13f0 - v[i] + Vt) / 4f0) - 1f0) * (1f0 - m[i]) -
        0.28f0 * (v[i] - Vt - 40f0) / (exp((v[i] - Vt - 40f0) / 5f0) - 1f0) * m[i])
        n[i] += dt * (0.032f0 * (15f0 - v[i] + Vt) / (exp((15f0 - v[i] + Vt) / 5f0) - 1f0) * (1f0 - n[i]) -
        0.5f0 * exp((10f0 - v[i] + Vt) / 40f0) * n[i])
        h[i] += dt * (0.128f0 * exp((17f0 - v[i] + Vt) / 18f0) * (1f0 - h[i]) -
        4f0 / (1f0 + exp((40f0 - v[i] + Vt) / 5f0)) * h[i])
        v[i] += dt / Cm * ( I[i] + gl * (El - v[i]) + ge[i] * (Ee - v[i]) + gi[i] * (Ei - v[i]) +
        gn * m[i]^3 * h[i] * (En - v[i]) + gk * n[i]^4 * (Ek - v[i]) )
        ge[i] += dt * -ge[i] / τe
        gi[i] += dt * -gi[i] / τi
    end
    @inbounds for i = 1:N
        fire[i] = v[i] > -20f0
    end
end


using SignatureGB
using Singular
P, (x1, x2, x3, x4)  = Singular.PolynomialRing(Fp(65521), ["x1", "x2", "x3", "x4"]);
gb = SignatureGB.f5([x1, x2, x3, x4]);F = [3136*x1^4+6944*x1^2*x2^2-5823*x1^2*x3^2+8176*x1^2*x4^2-2414*x1*x2^2*x3-10650*x1*x2*x3^2+4133*x2^4+2550*x2^3*x3-6403*x2^2*x3^2+9052*x2^2*x4^2+9409*x3^4-14162*x3^2*x4^2+5329*x4^4-9744*x1^3+11360*x1^2*x3-13508*x1*x2^2-12000*x1*x2*x3+16878*x1*x3^2-6248*x1*x3*x4-12702*x1*x4^2+1496*x2^2*x4+6600*x2*x3*x4+13969*x1^2-11644*x1*x3-7040*x1*x4+2788*x2^2+12300*x2*x3+1936*x4^2-13120*x1+7216*x4+6724, 13888*x1^2*x2-4828*x1*x2*x3-10650*x1*x3^2+16532*x2^3+7650*x2^2*x3-12806*x2*x3^2+18104*x2*x4^2-27016*x1*x2-12000*x1*x3+2992*x2*x4+6600*x3*x4+5576*x2+12300*x3, -11646*x1^2*x3-2414*x1*x2^2-21300*x1*x2*x3+2550*x2^3-12806*x2^2*x3+37636*x3^3-28324*x3*x4^2+11360*x1^2-12000*x1*x2+33756*x1*x3-6248*x1*x4+6600*x2*x4-11644*x1+12300*x2, 16352*x1^2*x4+18104*x2^2*x4-28324*x3^2*x4+21316*x4^3-6248*x1*x3-25404*x1*x4+1496*x2^2+6600*x2*x3-7040*x1+3872*x4+7216];
sort!(F, by = p -> leading_monomial(p))
println(@elapsed SignatureGB.f5(F, verbose=true));exit()