LShapeEigenfunction(domain::LShape; stride::Int = 1) =
    LShapeEigenfunction(domain, stride, arb[])

function Base.show(io::IO, u::LShapeEigenfunction)
    println(io, "Eigenfunction for the L-shaped domain")
    if !haskey(io, :compact) || !io[:compact]
        println(io, "domain: $(u.domain)")
        print(io, "number of set coefficients: $(length(u.coefficients))")
    end
end

active_boundaries(::LShape, ::LShapeEigenfunction) = 1:4

function (u::LShapeEigenfunction)(
    (r, θ)::Tuple{T,T},
    λ::arb,
    ks::UnitRange{Int};
    boundary = nothing,
) where {T<:Union{arb,arb_series}}
    rsqrtλ = r * sqrt(λ)
    res = similar(ks, T)
    for i in eachindex(ks)
        k = 1 + (ks[i] - 1) * u.stride

        ν = u.domain.parent(2k // 3)

        res[i] = bessel_j(ν, rsqrtλ) * sin(ν * θ)
    end

    return res
end

function (u::LShapeEigenfunction)(
    (r, θ)::Tuple{T,T},
    λ::arb;
    boundary = nothing,
) where {T<:Union{arb,arb_series}}
    coeffs = coefficients(u)
    return sum(coeffs .* u((r, θ), λ, 1:length(coeffs), boundary = boundary))
end

function norm(domain::LShape, u::LShapeEigenfunction, λ::arb)
    θ_integral = 3 // 4 * domain.parent(π)

    CC = ComplexField(domain.parent.prec)
    # The integrals goes from zero to the lower bound for θ. However
    # the function has a branch cut at zero and Arb has problem
    # handling this. We therefore integrate a small distance away from
    # zero.
    a = CC(1e-1)
    b = CC(1)

    r_integral = domain.parent(0)
    for k = 1:min(4, length(u.coefficients))
        ν::arb = domain.parent(2k // 3)
        c2 = u.coefficients[k]^2

        f = r -> begin
            r = real(r)
            if contains_negative(r)
                return CC(NaN)
            end

            CC(c2 * r * bessel_j(ν, sqrt(λ) * r)^2)
        end
        y = Nemo.integrate(CC, f, a, b)
        r_integral += real(y)
    end

    return sqrt(θ_integral * r_integral)
end


# This file was generated, do not modify it.

using Pkg # hideall
Pkg.activate("_literate/A-composing-models/Project.toml")
Pkg.update()
macro OUTPUT()
    return isdefined(Main, :Franklin) ? Franklin.OUT_PATH[] : "/tmp/"
end;

using MLJ
using PrettyPrinting
MLJ.color_off() # hide

KNNRegressor = @load KNNRegressor
# input
X = (age    = [23, 45, 34, 25, 67],
     gender = categorical(['m', 'm', 'f', 'm', 'f']))
# target
height = [178, 194, 165, 173, 168];

scitype(X.age)

pipe = Pipeline(
    coercer = X -> coerce(X, :age=>Continuous),
    one_hot_encoder = OneHotEncoder(),
    transformed_target_model = TransformedTargetModel(
        model = KNNRegressor(K=3);
        target=UnivariateStandardizer()
    )
)

pipe.transformed_target_model.model.K = 2
pipe.one_hot_encoder.drop_last = true;

evaluate(
    pipe,
    X,
    height,
    resampling=Holdout(),
    measure=rms
) |> pprint


#
# Some small helpfers to hande (the product manifold of) manifold-valued images,
# namely \(\mathcal M^{n,m}\) of an manifold-valued array M
#
export constructImageGraph

"""
    constructImageGraph(img,type)

construct the graph that can be used within the algorithms to model forward
differences
# Arguments
* `img` – the manifold-valued (MPoint-entries) image
* `type` – the type of graph, here `firstOrderDifference` and `secondOrderDifference`.
 """
function constructImageGraph(data::Array{T}, graphtype::String)::Array{Tuple} where {T<:MPoint}
  if graphtype == "firstOrderDifference"
    numdims = ndims(data)
    dims = size(data)
    newnumel = 0
    for i=1:numdims
      ithDiffs = 1
      for j=1:numdims
        if i==j
          ithDiffs = ithDiffs*(dims[j]-1)
        else
          ithDiffs = ithDiffs*dims[j]
        end
      end
      newnumel = newnumel + ithDiffs
    end
    edges = Array{Tuple{Int,Int}}(undef, newnumel)
    # fill with differences
    n=1
    for i=1:numdims
      # generate forward differences along ith dimension
      for index in eachindex(data) #for each index (linear)
        ind = CartesianIndices(dims)[index] # get index (sub)
        if ind[i] < dims[i]     # forward difference in ith direction possible
          ind2 = [Tuple(ind)...]       # extract into array
          ind2[i] = ind2[i] + 1 # generate neighbor
          edges[n] = ( (LinearIndices(dims))[Tuple(ind)...],(LinearIndices(dims))[Tuple(ind2)...] )
          n=n+1
        end
      end
    end
  elseif graphtype == "secondOrderDifference"
    numdims = ndims(data)
    dims = size(data)
    newnumel = 0
    for i=1:numdims
      ithDiffs = 1
      for j=1:numdims
        if i==j
          ithDiffs = ithDiffs*(dims[j]-2)
        else
          ithDiffs = ithDiffs*dims[j]
        end
      end
      newnumel = newnumel + ithDiffs
    end
    edges = Array{Tuple{Int,Int,Int}}(undef,newnumel)
    # fill with differences
    n=1
    for i=1:numdims
      # generate forward differences along ith dimension
      for index in eachindex(data) #for each index (linear)
        ind = CartesianIndices(dims)[index] # get index (sub)
        if (ind[i] < dims[i]) && (1<ind[i])     # left and right neighbot in ith direction within array
          ind2 = [Tuple(ind)...]       # extract into array
          ind2[i] = ind2[i] + 1 # generate neighbor
          ind3 = [Tuple(ind)...]       # extract into array
          ind3[i] = ind3[i] - 1 # generate neighbor
          edges[n] = ( (LinearIndices(dims))[Tuple(ind3)...], (LinearIndices(dims))[Tuple(ind)...], (LinearIndices(dims))[Tuple(ind2)...] )
          n=n+1
        end
      end
    end
  else # end of type checks
      throw( ErrorException(" constructImageGraph – unknown graph type: $graphtype " ) )
  end
  return edges
end


include("activate.jl")

import LinearAlgebra

n = Int(1e5)
k = 1000

xpi = rand(n);
total = sum(xpi);
const xp = xpi ./ total;

f(x) = LinearAlgebra.norm(x - xp)^2

function grad!(storage, x)
    @. storage = 2 * (x - xp)
end

lmo = FrankWolfe.KSparseLMO(40, 1.0);
x00 = FrankWolfe.compute_extreme_point(lmo, zeros(n));

FrankWolfe.benchmark_oracles(x -> f(x), (str, x) -> grad!(str, x), () -> randn(n), lmo; k=100)

println("\n==> Short Step rule - if you know L.\n")

x0 = deepcopy(x00)

@time x, v, primal, dual_gap, trajectorySs = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Shortstep(),
    L=2,
    print_iter=k / 10,
    emphasis=FrankWolfe.memory,
    verbose=true,
    trajectory=true,
);

println("\n==> Adaptive if you do not know L.\n")

x0 = deepcopy(x00)

@time x, v, primal, dual_gap, trajectoryAda = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Adaptive(),
    print_iter=k / 10,
    emphasis=FrankWolfe.memory,
    verbose=true,
    trajectory=true,
);

println("\n==> Agnostic if function is too expensive for adaptive.\n")

x0 = deepcopy(x00)

@time x, v, primal, dual_gap, trajectoryAg = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Agnostic(),
    print_iter=k / 10,
    emphasis=FrankWolfe.memory,
    verbose=true,
    trajectory=true,
);

data = [trajectorySs, trajectoryAda, trajectoryAg]
label = ["short step", "adaptive", "agnostic"]

FrankWolfe.plot_trajectories(data, label, xscalelog=true)


function solve_lin_system!(A_mat, b_vec)
    # passing Val(false) to the second argument turns off pivoting. it seems to
    # improve speed for the small matrices that we typically consider. we can
    # revisit this (or make it a parameter) if needed.
    A_fact = lu!(A_mat, Val(false))
    ldiv!(A_fact, b_vec)        # b_vec is overwritten to store the result
    nothing
end

function solve_lin_system_pivot!(A_mat, b_vec)
    # for the larger matrices, in the coupled equations, it's better to pivot
    A_fact = lu!(A_mat, Val(true))
    ldiv!(A_fact, b_vec)        # b_vec is overwritten to store the result
    nothing
end

struct Aux{T<:Real}
    A_mat   :: Matrix{T}
    b_vec   :: Vector{T}
    ABCS    :: Vector{T}
    A_mat2  :: Matrix{T}
    b_vec2  :: Vector{T}
    AA      :: Matrix{T}
    BB      :: Matrix{T}
    CC      :: Matrix{T}
    SS      :: Vector{T}
    function Aux{T}(N::Int) where {T<:Real}
        A_mat   = zeros(T, N, N)
        b_vec   = zeros(T, N)
        ABCS    = zeros(T, 4)
        A_mat2  = zeros(T, 2*N, 2*N)
        b_vec2  = zeros(T, 2*N)
        AA      = zeros(T, 2,2)
        BB      = zeros(T, 2,2)
        CC      = zeros(T, 2,2)
        SS      = zeros(T, 2)
        new(A_mat, b_vec, ABCS, A_mat2, b_vec2, AA, BB, CC, SS)
    end
end
function Aux{T}(sys::System) where {T}
    Nu, Nx, Ny = size(sys)
    nt = Threads.nthreads()
    # pre-allocate thread-local aux quantities
    [Aux{T}(Nu) for _ in 1:nt]
end

struct BC{T}
    S    :: Array{T,2}
    Fx   :: Array{T,2}
    Fy   :: Array{T,2}
    B1d  :: Array{T,2}
    B2d  :: Array{T,2}
    Gd   :: Array{T,2}
    phid :: Array{T,2}
    Sd   :: Array{T,2}
    A    :: Array{T,2}
    S_u  :: Array{T,2}
    Fx_u :: Array{T,2}
    Fy_u :: Array{T,2}
    A_u  :: Array{T,2}
end
function BC{T}(Nx::Int, Ny::Int) where {T<:Real}
    S    = Array{T}(undef, Nx, Ny)
    Fx   = Array{T}(undef, Nx, Ny)
    Fy   = Array{T}(undef, Nx, Ny)
    B1d  = Array{T}(undef, Nx, Ny)
    B2d  = Array{T}(undef, Nx, Ny)
    Gd   = Array{T}(undef, Nx, Ny)
    phid = Array{T}(undef, Nx, Ny)
    Sd   = Array{T}(undef, Nx, Ny)
    A    = Array{T}(undef, Nx, Ny)
    S_u  = Array{T}(undef, Nx, Ny)
    Fx_u = Array{T}(undef, Nx, Ny)
    Fy_u = Array{T}(undef, Nx, Ny)
    A_u  = Array{T}(undef, Nx, Ny)
    BC{T}(S, Fx, Fy, B1d, B2d, Gd, phid, Sd, A, S_u, Fx_u, Fy_u, A_u)
end

function BCs(systems::SystemPartition)
    sys1  = systems[1]
    T     = Jecco.coord_eltype(sys1.ucoord)
    _, Nx, Ny = size(sys1)

    [BC{T}(Nx, Ny) for sys in systems]
end

#= Notes

for each metric function there are radial ODEs at each z point. since they are
x,y-independent, they can all be solved independently and simultaneously. this is
achieved via the trivial Threads.@thread parallelisation below.

the matrix A_mat is obtained, for each equation, through

  A_mat = A D_uu + B D_u + C Id

which builds the differential operator, and then the top and bottom lines are
replaced to enforce the boundary conditions, as shown below. the vector b_vec is
simply built through

  b_vec = -S

the first and last entries are replaced to enforce the boundary conditions,
leading to the schematic form

  (  1   0   0  ...  0  ) ( x0 )   (  u0  )
  ( a10 a11 a12 ... a1N ) ( x1 )   (  b1  )
  ( ................... ) ( x2 ) = (  b2  )                               (*)
  ( ................... ) ( .. )   (  ..  )
  ( d00 d01 d02 ... d0N ) ( xN )   (  u'0 )

where N = Nu and d00, d01, d02, ..., d0N are the coefficients of the first
line in the first derivative operator D_u:

         ( d00 d01 d02 d03 ... d0N )
         ( d10 d11 d12 d13 ... d1N )
  D_u =  ( d20 d21 d22 d23 ... d2N )
         ( ....................... )
         ( dN0 dN1 dN2 dN3 ... dNN )

the first equation from the system (*) then enforces x0 = u0 while the last
equation enforces dx/du_{u=u0} = u'0, where u0 and u'0 need to be given for each
equation. the remaining equations from (*) enforce the differential equations
themselves.

note that this example is valid only for the second order ODEs. for the first
order ones, obviously, just one BC is needed. thus, we accordingly skip the step
of replacing the last line of the A_mat matrix and last entry of b_vec vector.

=#

function solve_S!(bulk::Bulk, bc::BC, gauge::Gauge, deriv::BulkDeriv, aux_acc,
                  sys::System, evoleq::AffineNull)
    Nu, Nx, Ny = size(sys)

    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    # Duu_B1  = deriv.Duu_B1
    # Duu_B2  = deriv.Duu_B2
    # Duu_G   = deriv.Duu_G

    Du  = sys.Du
    Duu = sys.Duu
    # Dx  = sys.Dx
    # Dxx = sys.Dxx
    # Dy  = sys.Dy
    # Dyy = sys.Dyy

    phi0  = evoleq.phi0

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds for i in 1:Nx
            id  = Threads.threadid()
            aux = aux_acc[id]

            xi  = gauge.xi[1,i,j]

            @inbounds @simd for a in 1:Nu
                u     = sys.ucoord[a]

                B1    = bulk.B1[a,i,j]
                B2    = bulk.B2[a,i,j]
                G     = bulk.G[a,i,j]
                phi   = bulk.phi[a,i,j]

                B1p   = -u*u * Du_B1[a,i,j]
                B2p   = -u*u * Du_B2[a,i,j]
                Gp    = -u*u * Du_G[a,i,j]
                phip  = -u*u * Du_phi[a,i,j]

                vars = (phi0, u, xi, B1, B1p, B2, B2p, G, Gp, phi, phip)

                S_eq_coeff!(aux.ABCS, vars, sys.gridtype)

                aux.b_vec[a]   = -aux.ABCS[4]
                @inbounds @simd for aa in 1:Nu
                    aux.A_mat[a,aa] = aux.ABCS[1] * Duu[a,aa] + aux.ABCS[2] * Du[a,aa]
                end
                aux.A_mat[a,a] += aux.ABCS[3]
            end

            # BC

            aux.b_vec[1]    = bc.S[i,j]
            aux.A_mat[1,:] .= 0.0
            aux.A_mat[1,1]  = 1.0

            aux.b_vec[end]    = bc.S_u[i,j]
            @inbounds @simd for aa in 1:Nu
                aux.A_mat[end,aa]  = Du[1,aa]
            end

            solve_lin_system!(aux.A_mat, aux.b_vec)

            @inbounds @simd for aa in 1:Nu
                bulk.S[aa,i,j] = aux.b_vec[aa]
            end

        end
    end

    nothing
end

function solve_Fxy!(bulk::Bulk, bc::BC, gauge::Gauge, deriv::BulkDeriv, aux_acc,
                    sys::System, evoleq::AffineNull)
    Nu, Nx, Ny = size(sys)

    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    Du_S    = deriv.Du_S
    Duu_B1  = deriv.Duu_B1
    Duu_B2  = deriv.Duu_B2
    Duu_G   = deriv.Duu_G
    Duu_phi = deriv.Duu_phi
    Duu_S   = deriv.Duu_S

    Du  = sys.Du
    Duu = sys.Duu
    Dx  = sys.Dx
    Dxx = sys.Dxx
    Dy  = sys.Dy
    Dyy = sys.Dyy

    phi0  = evoleq.phi0

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds for i in 1:Nx
            id  = Threads.threadid()
            aux = aux_acc[id]

            xi    = gauge.xi[1,i,j]
            xi_x  = Dx(gauge.xi, 1,i,j)
            xi_y  = Dy(gauge.xi, 1,i,j)

            @inbounds @simd for a in 1:Nu
                u     = sys.ucoord[a]
                u2    = u * u
                u3    = u * u2
                u4    = u2 * u2

                B1    = bulk.B1[a,i,j]
                B1p   = -u2 * Du_B1[a,i,j]
                B1_x  = Dx(bulk.B1, a,i,j)
                B1_y  = Dy(bulk.B1, a,i,j)
                B1pp  = 2*u3 * Du_B1[a,i,j] + u4 * Duu_B1[a,i,j]
                B1p_x = -u2 * Dx(Du_B1, a,i,j)
                B1p_y = -u2 * Dy(Du_B1, a,i,j)

                B2    = bulk.B2[a,i,j]
                B2p   = -u2 * Du_B2[a,i,j]
                B2_x  = Dx(bulk.B2, a,i,j)
                B2_y  = Dy(bulk.B2, a,i,j)
                B2pp  = 2*u3 * Du_B2[a,i,j] + u4 * Duu_B2[a,i,j]
                B2p_x = -u2 * Dx(Du_B2, a,i,j)
                B2p_y = -u2 * Dy(Du_B2, a,i,j)

                G     = bulk.G[a,i,j]
                Gp    = -u2 * Du_G[a,i,j]
                G_x   = Dx(bulk.G, a,i,j)
                G_y   = Dy(bulk.G, a,i,j)
                Gpp   = 2*u3 * Du_G[a,i,j] + u4 * Duu_G[a,i,j]
                Gp_x  = -u2 * Dx(Du_G, a,i,j)
                Gp_y  = -u2 * Dy(Du_G, a,i,j)

                phi   = bulk.phi[a,i,j]
                phip  = -u2 * Du_phi[a,i,j]
                phi_x = Dx(bulk.phi, a,i,j)
                phi_y = Dy(bulk.phi, a,i,j)
                # phipp   = 2*u3 * Du_phi[a,i,j] + u4 * Duu_phi[a,i,j]
                # phip_x  = -u2 * Dx(Du_phi, a,i,j)
                # phip_y  = -u2 * Dy(Du_phi, a,i,j)

                S     = bulk.S[a,i,j]
                Sp    = -u2 * Du_S[a,i,j]
                S_x   = Dx(bulk.S, a,i,j)
                S_y   = Dy(bulk.S, a,i,j)
                Spp   = 2*u3 * Du_S[a,i,j] + u4 * Duu_S[a,i,j]
                Sp_x  = -u2 * Dx(Du_S, a,i,j)
                Sp_y  = -u2 * Dy(Du_S, a,i,j)

                vars = (
                    phi0, u, xi, xi_x, xi_y,
                    B1     ,    B2     ,    G      ,    phi    ,    S      ,
                    B1p    ,    B2p    ,    Gp     ,    phip   ,    Sp     ,
                    B1pp   ,    B2pp   ,    Gpp    ,                Spp    ,
                    B1_x   ,    B2_x   ,    G_x    ,    phi_x  ,    S_x    ,
                    B1_y   ,    B2_y   ,    G_y    ,    phi_y  ,    S_y    ,
                    B1p_x  ,    B2p_x  ,    Gp_x   ,                Sp_x   ,
                    B1p_y  ,    B2p_y  ,    Gp_y   ,                Sp_y
                )

                Fxy_eq_coeff!(aux.AA, aux.BB, aux.CC, aux.SS, vars, sys.gridtype)

                aux.b_vec2[a]    = -aux.SS[1]
                aux.b_vec2[a+Nu] = -aux.SS[2]
                @inbounds @simd for aa in 1:Nu
                    aux.A_mat2[a,aa]         = aux.AA[1,1] * Duu[a,aa] + aux.BB[1,1] * Du[a,aa]
                    aux.A_mat2[a,aa+Nu]      = aux.AA[1,2] * Duu[a,aa] + aux.BB[1,2] * Du[a,aa]
                    aux.A_mat2[a+Nu,aa]      = aux.AA[2,1] * Duu[a,aa] + aux.BB[2,1] * Du[a,aa]
                    aux.A_mat2[a+Nu,aa+Nu]   = aux.AA[2,2] * Duu[a,aa] + aux.BB[2,2] * Du[a,aa]
                end
                aux.A_mat2[a,a]       += aux.CC[1,1]
                aux.A_mat2[a,a+Nu]    += aux.CC[1,2]
                aux.A_mat2[a+Nu,a]    += aux.CC[2,1]
                aux.A_mat2[a+Nu,a+Nu] += aux.CC[2,2]
            end

            # BC

            aux.b_vec2[1]     = bc.Fx[i,j]
            aux.A_mat2[1,:]  .= 0.0
            aux.A_mat2[1,1]   = 1.0

            aux.b_vec2[1+Nu]      = bc.Fy[i,j]
            aux.A_mat2[1+Nu,:]   .= 0.0
            aux.A_mat2[1+Nu,1+Nu] = 1.0

            aux.b_vec2[Nu]   = bc.Fx_u[i,j]
            aux.b_vec2[2*Nu] = bc.Fy_u[i,j]

            aux.A_mat2[Nu,:]   .= 0.0
            aux.A_mat2[2*Nu,:] .= 0.0
            @inbounds @simd for aa in 1:Nu
                aux.A_mat2[Nu,aa]      = Du[1,aa]
                aux.A_mat2[2*Nu,aa+Nu] = Du[1,aa]
            end

            solve_lin_system_pivot!(aux.A_mat2, aux.b_vec2)

            @inbounds @simd for aa in 1:Nu
                bulk.Fx[aa,i,j] = aux.b_vec2[aa]
                bulk.Fy[aa,i,j] = aux.b_vec2[aa+Nu]
            end

        end
    end

    nothing
end

function solve_Sd!(bulk::Bulk, bc::BC, gauge::Gauge, deriv::BulkDeriv, aux_acc,
                   sys::System, evoleq::AffineNull)
    Nu, Nx, Ny = size(sys)

    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    Du_S    = deriv.Du_S
    Du_Fx   = deriv.Du_Fx
    Du_Fy   = deriv.Du_Fy
    Duu_B1  = deriv.Duu_B1
    Duu_B2  = deriv.Duu_B2
    Duu_G   = deriv.Duu_G
    Duu_phi = deriv.Duu_phi
    Duu_S   = deriv.Duu_S
    Duu_Fx  = deriv.Duu_Fx
    Duu_Fy  = deriv.Duu_Fy

    Du  = sys.Du
    Duu = sys.Duu
    Dx  = sys.Dx
    Dxx = sys.Dxx
    Dy  = sys.Dy
    Dyy = sys.Dyy

    potential = evoleq.potential
    phi0      = evoleq.phi0

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds for i in 1:Nx
            id   = Threads.threadid()
            aux  = aux_acc[id]

            xi    = gauge.xi[1,i,j]
            xi_x  = Dx(gauge.xi, 1,i,j)
            xi_y  = Dy(gauge.xi, 1,i,j)
            xi_xx = Dxx(gauge.xi, 1,i,j)
            xi_yy = Dyy(gauge.xi, 1,i,j)
            xi_xy = Dx(Dy, gauge.xi, 1,i,j)

            @inbounds @simd for a in 1:Nu
                u     = sys.ucoord[a]
                u2    = u * u
                u3    = u * u2
                u4    = u2 * u2

                B1    = bulk.B1[a,i,j]
                B2    = bulk.B2[a,i,j]
                G     = bulk.G[a,i,j]
                phi   = bulk.phi[a,i,j]
                S     = bulk.S[a,i,j]
                Fx    = bulk.Fx[a,i,j]
                Fy    = bulk.Fy[a,i,j]

                # r derivatives

                B1p        = -u2 * Du_B1[a,i,j]
                B2p        = -u2 * Du_B2[a,i,j]
                Gp         = -u2 * Du_G[a,i,j]
                phip       = -u2 * Du_phi[a,i,j]
                Sp         = -u2 * Du_S[a,i,j]
                Fxp        = -u2 * Du_Fx[a,i,j]
                Fyp        = -u2 * Du_Fy[a,i,j]

                B1pp       = 2*u3 * Du_B1[a,i,j]  + u4 * Duu_B1[a,i,j]
                B2pp       = 2*u3 * Du_B2[a,i,j]  + u4 * Duu_B2[a,i,j]
                Gpp        = 2*u3 * Du_G[a,i,j]   + u4 * Duu_G[a,i,j]
                phipp      = 2*u3 * Du_phi[a,i,j] + u4 * Duu_phi[a,i,j]
                Spp        = 2*u3 * Du_S[a,i,j]   + u4 * Duu_S[a,i,j]
                Fxpp       = 2*u3 * Du_Fx[a,i,j]  + u4 * Duu_Fx[a,i,j]
                Fypp       = 2*u3 * Du_Fy[a,i,j]  + u4 * Duu_Fy[a,i,j]

                # x and y derivatives

                B1_x       = Dx(bulk.B1, a,i,j)
                B2_x       = Dx(bulk.B2, a,i,j)
                G_x        = Dx(bulk.G,  a,i,j)
                phi_x      = Dx(bulk.phi,a,i,j)
                S_x        = Dx(bulk.S,  a,i,j)
                Fx_x       = Dx(bulk.Fx, a,i,j)
                Fy_x       = Dx(bulk.Fy, a,i,j)

                B1_y       = Dy(bulk.B1, a,i,j)
                B2_y       = Dy(bulk.B2, a,i,j)
                G_y        = Dy(bulk.G,  a,i,j)
                phi_y      = Dy(bulk.phi,a,i,j)
                S_y        = Dy(bulk.S,  a,i,j)
                Fx_y       = Dy(bulk.Fx, a,i,j)
                Fy_y       = Dy(bulk.Fy, a,i,j)

                B1p_x      = -u2 * Dx(Du_B1, a,i,j)
                B2p_x      = -u2 * Dx(Du_B2, a,i,j)
                Gp_x       = -u2 * Dx(Du_G,  a,i,j)
                phip_x     = -u2 * Dx(Du_phi,a,i,j)
                Sp_x       = -u2 * Dx(Du_S,  a,i,j)
                Fxp_x      = -u2 * Dx(Du_Fx,  a,i,j)
                Fyp_x      = -u2 * Dx(Du_Fy,  a,i,j)

                B1p_y      = -u2 * Dy(Du_B1, a,i,j)
                B2p_y      = -u2 * Dy(Du_B2, a,i,j)
                Gp_y       = -u2 * Dy(Du_G,  a,i,j)
                phip_y     = -u2 * Dy(Du_phi,a,i,j)
                Sp_y       = -u2 * Dy(Du_S,  a,i,j)
                Fxp_y      = -u2 * Dy(Du_Fx, a,i,j)
                Fyp_y      = -u2 * Dy(Du_Fy, a,i,j)

                B1_xx      = Dxx(bulk.B1, a,i,j)
                B2_xx      = Dxx(bulk.B2, a,i,j)
                G_xx       = Dxx(bulk.G,  a,i,j)
                phi_xx     = Dxx(bulk.phi,a,i,j)
                S_xx       = Dxx(bulk.S,  a,i,j)

                B1_yy      = Dyy(bulk.B1, a,i,j)
                B2_yy      = Dyy(bulk.B2, a,i,j)
                G_yy       = Dyy(bulk.G,  a,i,j)
                phi_yy     = Dyy(bulk.phi,a,i,j)
                S_yy       = Dyy(bulk.S,  a,i,j)

                B2_xy      = Dx(Dy, bulk.B2, a,i,j)
                G_xy       = Dx(Dy, bulk.G,  a,i,j)
                S_xy       = Dx(Dy, bulk.S,  a,i,j)

                vars = (
                    potential, phi0, u, xi, xi_x, xi_y, xi_xx, xi_yy, xi_xy,
                    B1     ,    B2     ,    G      ,    phi    ,    S      ,    Fx     ,    Fy     ,
                    B1p    ,    B2p    ,    Gp     ,    phip   ,    Sp     ,    Fxp    ,    Fyp    ,
                    B1pp   ,    B2pp   ,    Gpp    ,    phipp  ,    Spp    ,    Fxpp   ,    Fypp   ,
                    B1_x   ,    B2_x   ,    G_x    ,    phi_x  ,    S_x    ,    Fx_x   ,    Fy_x   ,
                    B1_y   ,    B2_y   ,    G_y    ,    phi_y  ,    S_y    ,    Fx_y   ,    Fy_y   ,
                    B1p_x  ,    B2p_x  ,    Gp_x   ,    phip_x ,    Sp_x   ,    Fxp_x  ,    Fyp_x  ,
                    B1p_y  ,    B2p_y  ,    Gp_y   ,    phip_y ,    Sp_y   ,    Fxp_y  ,    Fyp_y  ,
                    B1_xx  ,    B2_xx  ,    G_xx   ,    phi_xx ,    S_xx   ,
                    B1_yy  ,    B2_yy  ,    G_yy   ,    phi_yy ,    S_yy   ,
                                B2_xy  ,    G_xy   ,                S_xy
                )

                Sd_eq_coeff!(aux.ABCS, vars, sys.gridtype)

                aux.b_vec[a]   = -aux.ABCS[4]
                @inbounds @simd for aa in 1:Nu
                    aux.A_mat[a,aa] = aux.ABCS[1] * Duu[a,aa] + aux.ABCS[2] * Du[a,aa]
                end
                aux.A_mat[a,a] += aux.ABCS[3]
            end

            # BC (first order equation)

            aux.b_vec[1]    = bc.Sd[i,j]
            aux.A_mat[1,:] .= 0.0
            aux.A_mat[1,1]  = 1.0

            solve_lin_system!(aux.A_mat, aux.b_vec)

            @inbounds @simd for aa in 1:Nu
                bulk.Sd[aa,i,j] = aux.b_vec[aa]
            end

        end
    end

    nothing
end

function solve_B2d!(bulk::Bulk, bc::BC, gauge::Gauge, deriv::BulkDeriv, aux_acc,
                    sys::System, evoleq::AffineNull)
    Nu, Nx, Ny = size(sys)

    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    Du_S    = deriv.Du_S
    Du_Fx   = deriv.Du_Fx
    Du_Fy   = deriv.Du_Fy
    Duu_B1  = deriv.Duu_B1
    Duu_B2  = deriv.Duu_B2
    Duu_G   = deriv.Duu_G
    Duu_phi = deriv.Duu_phi
    Duu_S   = deriv.Duu_S
    Duu_Fx  = deriv.Duu_Fx
    Duu_Fy  = deriv.Duu_Fy

    Du  = sys.Du
    Duu = sys.Duu
    Dx  = sys.Dx
    Dxx = sys.Dxx
    Dy  = sys.Dy
    Dyy = sys.Dyy

    phi0  = evoleq.phi0

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds for i in 1:Nx
            id  = Threads.threadid()
            aux = aux_acc[id]

            xi    = gauge.xi[1,i,j]
            xi_x  = Dx(gauge.xi, 1,i,j)
            xi_y  = Dy(gauge.xi, 1,i,j)
            xi_xx = Dxx(gauge.xi, 1,i,j)
            xi_yy = Dyy(gauge.xi, 1,i,j)
            xi_xy = Dx(Dy, gauge.xi, 1,i,j)

            @inbounds @simd for a in 1:Nu
                u     = sys.ucoord[a]
                u2    = u * u
                u3    = u * u2
                u4    = u2 * u2

                B1    = bulk.B1[a,i,j]
                B2    = bulk.B2[a,i,j]
                G     = bulk.G[a,i,j]
                phi   = bulk.phi[a,i,j]
                S     = bulk.S[a,i,j]
                Fx    = bulk.Fx[a,i,j]
                Fy    = bulk.Fy[a,i,j]
                Sd    = bulk.Sd[a,i,j]

                # r derivatives

                B1p        = -u2 * Du_B1[a,i,j]
                B2p        = -u2 * Du_B2[a,i,j]
                Gp         = -u2 * Du_G[a,i,j]
                phip       = -u2 * Du_phi[a,i,j]
                Sp         = -u2 * Du_S[a,i,j]
                Fxp        = -u2 * Du_Fx[a,i,j]
                Fyp        = -u2 * Du_Fy[a,i,j]

                B1pp       = 2*u3 * Du_B1[a,i,j]  + u4 * Duu_B1[a,i,j]
                B2pp       = 2*u3 * Du_B2[a,i,j]  + u4 * Duu_B2[a,i,j]
                Gpp        = 2*u3 * Du_G[a,i,j]   + u4 * Duu_G[a,i,j]
                phipp      = 2*u3 * Du_phi[a,i,j] + u4 * Duu_phi[a,i,j]
                Spp        = 2*u3 * Du_S[a,i,j]   + u4 * Duu_S[a,i,j]
                Fxpp       = 2*u3 * Du_Fx[a,i,j]  + u4 * Duu_Fx[a,i,j]
                Fypp       = 2*u3 * Du_Fy[a,i,j]  + u4 * Duu_Fy[a,i,j]

                # x and y derivatives

                B1_x       = Dx(bulk.B1, a,i,j)
                B2_x       = Dx(bulk.B2, a,i,j)
                G_x        = Dx(bulk.G,  a,i,j)
                phi_x      = Dx(bulk.phi,a,i,j)
                S_x        = Dx(bulk.S,  a,i,j)
                Fx_x       = Dx(bulk.Fx, a,i,j)
                Fy_x       = Dx(bulk.Fy, a,i,j)

                B1_y       = Dy(bulk.B1, a,i,j)
                B2_y       = Dy(bulk.B2, a,i,j)
                G_y        = Dy(bulk.G,  a,i,j)
                phi_y      = Dy(bulk.phi,a,i,j)
                S_y        = Dy(bulk.S,  a,i,j)
                Fx_y       = Dy(bulk.Fx, a,i,j)
                Fy_y       = Dy(bulk.Fy, a,i,j)

                B1p_x      = -u2 * Dx(Du_B1, a,i,j)
                B2p_x      = -u2 * Dx(Du_B2, a,i,j)
                Gp_x       = -u2 * Dx(Du_G,  a,i,j)
                phip_x     = -u2 * Dx(Du_phi,a,i,j)
                Sp_x       = -u2 * Dx(Du_S,  a,i,j)
                Fxp_x      = -u2 * Dx(Du_Fx,  a,i,j)
                Fyp_x      = -u2 * Dx(Du_Fy,  a,i,j)

                B1p_y      = -u2 * Dy(Du_B1, a,i,j)
                B2p_y      = -u2 * Dy(Du_B2, a,i,j)
                Gp_y       = -u2 * Dy(Du_G,  a,i,j)
                phip_y     = -u2 * Dy(Du_phi,a,i,j)
                Sp_y       = -u2 * Dy(Du_S,  a,i,j)
                Fxp_y      = -u2 * Dy(Du_Fx, a,i,j)
                Fyp_y      = -u2 * Dy(Du_Fy, a,i,j)

                B1_xx      = Dxx(bulk.B1, a,i,j)
                B2_xx      = Dxx(bulk.B2, a,i,j)
                G_xx       = Dxx(bulk.G,  a,i,j)
                phi_xx     = Dxx(bulk.phi,a,i,j)
                S_xx       = Dxx(bulk.S,  a,i,j)

                B1_yy      = Dyy(bulk.B1, a,i,j)
                B2_yy      = Dyy(bulk.B2, a,i,j)
                G_yy       = Dyy(bulk.G,  a,i,j)
                phi_yy     = Dyy(bulk.phi,a,i,j)
                S_yy       = Dyy(bulk.S,  a,i,j)

                B2_xy      = Dx(Dy, bulk.B2, a,i,j)
                G_xy       = Dx(Dy, bulk.G,  a,i,j)
                S_xy       = Dx(Dy, bulk.S,  a,i,j)

                vars = (
                    phi0, u, xi, xi_x, xi_y, xi_xx, xi_yy, xi_xy,
                    B1     ,    B2     ,    G      ,    phi    ,    S      ,    Fx     ,    Fy     ,  Sd,
                    B1p    ,    B2p    ,    Gp     ,    phip   ,    Sp     ,    Fxp    ,    Fyp    ,
                    B1pp   ,    B2pp   ,    Gpp    ,    phipp  ,    Spp    ,    Fxpp   ,    Fypp   ,
                    B1_x   ,    B2_x   ,    G_x    ,    phi_x  ,    S_x    ,    Fx_x   ,    Fy_x   ,
                    B1_y   ,    B2_y   ,    G_y    ,    phi_y  ,    S_y    ,    Fx_y   ,    Fy_y   ,
                    B1p_x  ,    B2p_x  ,    Gp_x   ,    phip_x ,    Sp_x   ,    Fxp_x  ,    Fyp_x  ,
                    B1p_y  ,    B2p_y  ,    Gp_y   ,    phip_y ,    Sp_y   ,    Fxp_y  ,    Fyp_y  ,
                    B1_xx  ,    B2_xx  ,    G_xx   ,    phi_xx ,    S_xx   ,
                    B1_yy  ,    B2_yy  ,    G_yy   ,    phi_yy ,    S_yy   ,
                                B2_xy  ,    G_xy   ,                S_xy
                )

                B2d_eq_coeff!(aux.ABCS, vars, sys.gridtype)

                aux.b_vec[a]   = -aux.ABCS[4]
                @inbounds @simd for aa in 1:Nu
                    aux.A_mat[a,aa] = aux.ABCS[1] * Duu[a,aa] + aux.ABCS[2] * Du[a,aa]
                end
                aux.A_mat[a,a] += aux.ABCS[3]
            end

            # BC (first order equation)

            aux.b_vec[1]    = bc.B2d[i,j]
            aux.A_mat[1,:] .= 0.0
            aux.A_mat[1,1]  = 1.0

            solve_lin_system!(aux.A_mat, aux.b_vec)

            @inbounds @simd for aa in 1:Nu
                bulk.B2d[aa,i,j] = aux.b_vec[aa]
            end

        end
    end

    nothing
end

function solve_B1dGd!(bulk::Bulk, bc::BC, gauge::Gauge, deriv::BulkDeriv, aux_acc,
                      sys::System, evoleq::AffineNull)
    Nu, Nx, Ny = size(sys)

    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    Du_S    = deriv.Du_S
    Du_Fx   = deriv.Du_Fx
    Du_Fy   = deriv.Du_Fy
    Duu_B1  = deriv.Duu_B1
    Duu_B2  = deriv.Duu_B2
    Duu_G   = deriv.Duu_G
    Duu_phi = deriv.Duu_phi
    Duu_S   = deriv.Duu_S
    Duu_Fx  = deriv.Duu_Fx
    Duu_Fy  = deriv.Duu_Fy

    Du  = sys.Du
    Duu = sys.Duu
    Dx  = sys.Dx
    Dxx = sys.Dxx
    Dy  = sys.Dy
    Dyy = sys.Dyy

    phi0  = evoleq.phi0

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds for i in 1:Nx
            id  = Threads.threadid()
            aux = aux_acc[id]

            xi    = gauge.xi[1,i,j]
            xi_x  = Dx(gauge.xi, 1,i,j)
            xi_y  = Dy(gauge.xi, 1,i,j)
            xi_xx = Dxx(gauge.xi, 1,i,j)
            xi_yy = Dyy(gauge.xi, 1,i,j)
            xi_xy = Dx(Dy, gauge.xi, 1,i,j)

            @inbounds @simd for a in 1:Nu
                u     = sys.ucoord[a]
                u2    = u * u
                u3    = u * u2
                u4    = u2 * u2

                B1    = bulk.B1[a,i,j]
                B2    = bulk.B2[a,i,j]
                G     = bulk.G[a,i,j]
                phi   = bulk.phi[a,i,j]
                S     = bulk.S[a,i,j]
                Fx    = bulk.Fx[a,i,j]
                Fy    = bulk.Fy[a,i,j]
                Sd    = bulk.Sd[a,i,j]

                # r derivatives

                B1p        = -u2 * Du_B1[a,i,j]
                B2p        = -u2 * Du_B2[a,i,j]
                Gp         = -u2 * Du_G[a,i,j]
                phip       = -u2 * Du_phi[a,i,j]
                Sp         = -u2 * Du_S[a,i,j]
                Fxp        = -u2 * Du_Fx[a,i,j]
                Fyp        = -u2 * Du_Fy[a,i,j]

                B1pp       = 2*u3 * Du_B1[a,i,j]  + u4 * Duu_B1[a,i,j]
                B2pp       = 2*u3 * Du_B2[a,i,j]  + u4 * Duu_B2[a,i,j]
                Gpp        = 2*u3 * Du_G[a,i,j]   + u4 * Duu_G[a,i,j]
                phipp      = 2*u3 * Du_phi[a,i,j] + u4 * Duu_phi[a,i,j]
                Spp        = 2*u3 * Du_S[a,i,j]   + u4 * Duu_S[a,i,j]
                Fxpp       = 2*u3 * Du_Fx[a,i,j]  + u4 * Duu_Fx[a,i,j]
                Fypp       = 2*u3 * Du_Fy[a,i,j]  + u4 * Duu_Fy[a,i,j]

                # x and y derivatives

                B1_x       = Dx(bulk.B1, a,i,j)
                B2_x       = Dx(bulk.B2, a,i,j)
                G_x        = Dx(bulk.G,  a,i,j)
                phi_x      = Dx(bulk.phi,a,i,j)
                S_x        = Dx(bulk.S,  a,i,j)
                Fx_x       = Dx(bulk.Fx, a,i,j)
                Fy_x       = Dx(bulk.Fy, a,i,j)

                B1_y       = Dy(bulk.B1, a,i,j)
                B2_y       = Dy(bulk.B2, a,i,j)
                G_y        = Dy(bulk.G,  a,i,j)
                phi_y      = Dy(bulk.phi,a,i,j)
                S_y        = Dy(bulk.S,  a,i,j)
                Fx_y       = Dy(bulk.Fx, a,i,j)
                Fy_y       = Dy(bulk.Fy, a,i,j)

                B1p_x      = -u2 * Dx(Du_B1, a,i,j)
                B2p_x      = -u2 * Dx(Du_B2, a,i,j)
                Gp_x       = -u2 * Dx(Du_G,  a,i,j)
                phip_x     = -u2 * Dx(Du_phi,a,i,j)
                Sp_x       = -u2 * Dx(Du_S,  a,i,j)
                Fxp_x      = -u2 * Dx(Du_Fx,  a,i,j)
                Fyp_x      = -u2 * Dx(Du_Fy,  a,i,j)

                B1p_y      = -u2 * Dy(Du_B1, a,i,j)
                B2p_y      = -u2 * Dy(Du_B2, a,i,j)
                Gp_y       = -u2 * Dy(Du_G,  a,i,j)
                phip_y     = -u2 * Dy(Du_phi,a,i,j)
                Sp_y       = -u2 * Dy(Du_S,  a,i,j)
                Fxp_y      = -u2 * Dy(Du_Fx, a,i,j)
                Fyp_y      = -u2 * Dy(Du_Fy, a,i,j)

                B1_xx      = Dxx(bulk.B1, a,i,j)
                B2_xx      = Dxx(bulk.B2, a,i,j)
                G_xx       = Dxx(bulk.G,  a,i,j)
                phi_xx     = Dxx(bulk.phi,a,i,j)
                S_xx       = Dxx(bulk.S,  a,i,j)

                B1_yy      = Dyy(bulk.B1, a,i,j)
                B2_yy      = Dyy(bulk.B2, a,i,j)
                G_yy       = Dyy(bulk.G,  a,i,j)
                phi_yy     = Dyy(bulk.phi,a,i,j)
                S_yy       = Dyy(bulk.S,  a,i,j)

                B2_xy      = Dx(Dy, bulk.B2, a,i,j)
                G_xy       = Dx(Dy, bulk.G,  a,i,j)
                S_xy       = Dx(Dy, bulk.S,  a,i,j)

                vars = (
                    phi0, u, xi, xi_x, xi_y, xi_xx, xi_yy, xi_xy,
                    B1     ,    B2     ,    G      ,    phi    ,    S      ,    Fx     ,    Fy     ,  Sd,
                    B1p    ,    B2p    ,    Gp     ,    phip   ,    Sp     ,    Fxp    ,    Fyp    ,
                    B1pp   ,    B2pp   ,    Gpp    ,    phipp  ,    Spp    ,    Fxpp   ,    Fypp   ,
                    B1_x   ,    B2_x   ,    G_x    ,    phi_x  ,    S_x    ,    Fx_x   ,    Fy_x   ,
                    B1_y   ,    B2_y   ,    G_y    ,    phi_y  ,    S_y    ,    Fx_y   ,    Fy_y   ,
                    B1p_x  ,    B2p_x  ,    Gp_x   ,    phip_x ,    Sp_x   ,    Fxp_x  ,    Fyp_x  ,
                    B1p_y  ,    B2p_y  ,    Gp_y   ,    phip_y ,    Sp_y   ,    Fxp_y  ,    Fyp_y  ,
                    B1_xx  ,    B2_xx  ,    G_xx   ,    phi_xx ,    S_xx   ,
                    B1_yy  ,    B2_yy  ,    G_yy   ,    phi_yy ,    S_yy   ,
                                B2_xy  ,    G_xy   ,                S_xy
                )

                B1dGd_eq_coeff!(aux.AA, aux.BB, aux.CC, aux.SS, vars, sys.gridtype)

                aux.b_vec2[a]    = -aux.SS[1]
                aux.b_vec2[a+Nu] = -aux.SS[2]
                @inbounds @simd for aa in 1:Nu
                    aux.A_mat2[a,aa]         = aux.AA[1,1] * Duu[a,aa] + aux.BB[1,1] * Du[a,aa]
                    aux.A_mat2[a,aa+Nu]      = aux.AA[1,2] * Duu[a,aa] + aux.BB[1,2] * Du[a,aa]
                    aux.A_mat2[a+Nu,aa]      = aux.AA[2,1] * Duu[a,aa] + aux.BB[2,1] * Du[a,aa]
                    aux.A_mat2[a+Nu,aa+Nu]   = aux.AA[2,2] * Duu[a,aa] + aux.BB[2,2] * Du[a,aa]
                end
                aux.A_mat2[a,a]       += aux.CC[1,1]
                aux.A_mat2[a,a+Nu]    += aux.CC[1,2]
                aux.A_mat2[a+Nu,a]    += aux.CC[2,1]
                aux.A_mat2[a+Nu,a+Nu] += aux.CC[2,2]
            end

            # BC (first order system)

            aux.b_vec2[1]     = bc.B1d[i,j]
            aux.A_mat2[1,:]  .= 0.0
            aux.A_mat2[1,1]   = 1.0

            aux.b_vec2[1+Nu]      = bc.Gd[i,j]
            aux.A_mat2[1+Nu,:]   .= 0.0
            aux.A_mat2[1+Nu,1+Nu] = 1.0

            solve_lin_system_pivot!(aux.A_mat2, aux.b_vec2)

            @inbounds @simd for aa in 1:Nu
                bulk.B1d[aa,i,j] = aux.b_vec2[aa]
                bulk.Gd[aa,i,j]  = aux.b_vec2[aa+Nu]
            end

        end
    end

    nothing
end

function solve_phid!(bulk::Bulk, bc::BC, gauge::Gauge, deriv::BulkDeriv, aux_acc,
                     sys::System, evoleq::AffineNull)
    Nu, Nx, Ny = size(sys)

    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    Du_S    = deriv.Du_S
    Du_Fx   = deriv.Du_Fx
    Du_Fy   = deriv.Du_Fy
    Duu_B1  = deriv.Duu_B1
    Duu_B2  = deriv.Duu_B2
    Duu_G   = deriv.Duu_G
    Duu_phi = deriv.Duu_phi
    Duu_S   = deriv.Duu_S
    Duu_Fx  = deriv.Duu_Fx
    Duu_Fy  = deriv.Duu_Fy

    Du  = sys.Du
    Duu = sys.Duu
    Dx  = sys.Dx
    Dxx = sys.Dxx
    Dy  = sys.Dy
    Dyy = sys.Dyy

    potential = evoleq.potential
    phi0      = evoleq.phi0

    # if phi0 = 0 set phid to zero and return
    if abs(phi0) < 1e-9
        fill!(bulk.phid, 0)
        return
    end

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds for i in 1:Nx
            id  = Threads.threadid()
            aux = aux_acc[id]

            xi    = gauge.xi[1,i,j]
            xi_x  = Dx(gauge.xi, 1,i,j)
            xi_y  = Dy(gauge.xi, 1,i,j)
            xi_xx = Dxx(gauge.xi, 1,i,j)
            xi_yy = Dyy(gauge.xi, 1,i,j)
            xi_xy = Dx(Dy, gauge.xi, 1,i,j)

            @inbounds @simd for a in 1:Nu
                u     = sys.ucoord[a]
                u2    = u * u
                u3    = u * u2
                u4    = u2 * u2

                B1    = bulk.B1[a,i,j]
                B2    = bulk.B2[a,i,j]
                G     = bulk.G[a,i,j]
                phi   = bulk.phi[a,i,j]
                S     = bulk.S[a,i,j]
                Fx    = bulk.Fx[a,i,j]
                Fy    = bulk.Fy[a,i,j]
                Sd    = bulk.Sd[a,i,j]

                # r derivatives

                B1p        = -u2 * Du_B1[a,i,j]
                B2p        = -u2 * Du_B2[a,i,j]
                Gp         = -u2 * Du_G[a,i,j]
                phip       = -u2 * Du_phi[a,i,j]
                Sp         = -u2 * Du_S[a,i,j]
                Fxp        = -u2 * Du_Fx[a,i,j]
                Fyp        = -u2 * Du_Fy[a,i,j]

                B1pp       = 2*u3 * Du_B1[a,i,j]  + u4 * Duu_B1[a,i,j]
                B2pp       = 2*u3 * Du_B2[a,i,j]  + u4 * Duu_B2[a,i,j]
                Gpp        = 2*u3 * Du_G[a,i,j]   + u4 * Duu_G[a,i,j]
                phipp      = 2*u3 * Du_phi[a,i,j] + u4 * Duu_phi[a,i,j]
                Spp        = 2*u3 * Du_S[a,i,j]   + u4 * Duu_S[a,i,j]
                Fxpp       = 2*u3 * Du_Fx[a,i,j]  + u4 * Duu_Fx[a,i,j]
                Fypp       = 2*u3 * Du_Fy[a,i,j]  + u4 * Duu_Fy[a,i,j]

                # x and y derivatives

                B1_x       = Dx(bulk.B1, a,i,j)
                B2_x       = Dx(bulk.B2, a,i,j)
                G_x        = Dx(bulk.G,  a,i,j)
                phi_x      = Dx(bulk.phi,a,i,j)
                S_x        = Dx(bulk.S,  a,i,j)
                Fx_x       = Dx(bulk.Fx, a,i,j)
                Fy_x       = Dx(bulk.Fy, a,i,j)

                B1_y       = Dy(bulk.B1, a,i,j)
                B2_y       = Dy(bulk.B2, a,i,j)
                G_y        = Dy(bulk.G,  a,i,j)
                phi_y      = Dy(bulk.phi,a,i,j)
                S_y        = Dy(bulk.S,  a,i,j)
                Fx_y       = Dy(bulk.Fx, a,i,j)
                Fy_y       = Dy(bulk.Fy, a,i,j)

                B1p_x      = -u2 * Dx(Du_B1, a,i,j)
                B2p_x      = -u2 * Dx(Du_B2, a,i,j)
                Gp_x       = -u2 * Dx(Du_G,  a,i,j)
                phip_x     = -u2 * Dx(Du_phi,a,i,j)
                Sp_x       = -u2 * Dx(Du_S,  a,i,j)
                Fxp_x      = -u2 * Dx(Du_Fx,  a,i,j)
                Fyp_x      = -u2 * Dx(Du_Fy,  a,i,j)

                B1p_y      = -u2 * Dy(Du_B1, a,i,j)
                B2p_y      = -u2 * Dy(Du_B2, a,i,j)
                Gp_y       = -u2 * Dy(Du_G,  a,i,j)
                phip_y     = -u2 * Dy(Du_phi,a,i,j)
                Sp_y       = -u2 * Dy(Du_S,  a,i,j)
                Fxp_y      = -u2 * Dy(Du_Fx, a,i,j)
                Fyp_y      = -u2 * Dy(Du_Fy, a,i,j)

                B1_xx      = Dxx(bulk.B1, a,i,j)
                B2_xx      = Dxx(bulk.B2, a,i,j)
                G_xx       = Dxx(bulk.G,  a,i,j)
                phi_xx     = Dxx(bulk.phi,a,i,j)
                S_xx       = Dxx(bulk.S,  a,i,j)

                B1_yy      = Dyy(bulk.B1, a,i,j)
                B2_yy      = Dyy(bulk.B2, a,i,j)
                G_yy       = Dyy(bulk.G,  a,i,j)
                phi_yy     = Dyy(bulk.phi,a,i,j)
                S_yy       = Dyy(bulk.S,  a,i,j)

                B2_xy      = Dx(Dy, bulk.B2, a,i,j)
                G_xy       = Dx(Dy, bulk.G,  a,i,j)
                phi_xy     = Dx(Dy, bulk.phi,a,i,j)
                S_xy       = Dx(Dy, bulk.S,  a,i,j)

                vars = (
                    potential, phi0, u, xi, xi_x, xi_y, xi_xx, xi_yy, xi_xy,
                    B1     ,    B2     ,    G      ,    phi    ,    S      ,    Fx     ,    Fy     ,  Sd,
                    B1p    ,    B2p    ,    Gp     ,    phip   ,    Sp     ,    Fxp    ,    Fyp    ,
                    B1pp   ,    B2pp   ,    Gpp    ,    phipp  ,    Spp    ,    Fxpp   ,    Fypp   ,
                    B1_x   ,    B2_x   ,    G_x    ,    phi_x  ,    S_x    ,    Fx_x   ,    Fy_x   ,
                    B1_y   ,    B2_y   ,    G_y    ,    phi_y  ,    S_y    ,    Fx_y   ,    Fy_y   ,
                    B1p_x  ,    B2p_x  ,    Gp_x   ,    phip_x ,    Sp_x   ,    Fxp_x  ,    Fyp_x  ,
                    B1p_y  ,    B2p_y  ,    Gp_y   ,    phip_y ,    Sp_y   ,    Fxp_y  ,    Fyp_y  ,
                    B1_xx  ,    B2_xx  ,    G_xx   ,    phi_xx ,    S_xx   ,
                    B1_yy  ,    B2_yy  ,    G_yy   ,    phi_yy ,    S_yy   ,
                                B2_xy  ,    G_xy   ,    phi_xy,     S_xy
                )

                phid_eq_coeff!(aux.ABCS, vars, sys.gridtype)

                aux.b_vec[a]   = -aux.ABCS[4]
                @inbounds @simd for aa in 1:Nu
                    aux.A_mat[a,aa] = aux.ABCS[1] * Duu[a,aa] + aux.ABCS[2] * Du[a,aa]
                end
                aux.A_mat[a,a] += aux.ABCS[3]
            end

            # BC (first order equation)

            aux.b_vec[1]    = bc.phid[i,j]
            aux.A_mat[1,:] .= 0.0
            aux.A_mat[1,1]  = 1.0

            solve_lin_system!(aux.A_mat, aux.b_vec)

            @inbounds @simd for aa in 1:Nu
                bulk.phid[aa,i,j] = aux.b_vec[aa]
            end

        end
    end

    nothing
end

function solve_A!(bulk::Bulk, bc::BC, gauge::Gauge, deriv::BulkDeriv, aux_acc,
                  sys::System, evoleq::AffineNull)
    Nu, Nx, Ny = size(sys)

    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    Du_S    = deriv.Du_S
    Du_Fx   = deriv.Du_Fx
    Du_Fy   = deriv.Du_Fy
    Duu_B1  = deriv.Duu_B1
    Duu_B2  = deriv.Duu_B2
    Duu_G   = deriv.Duu_G
    Duu_phi = deriv.Duu_phi
    Duu_S   = deriv.Duu_S
    Duu_Fx  = deriv.Duu_Fx
    Duu_Fy  = deriv.Duu_Fy

    Du  = sys.Du
    Duu = sys.Duu
    Dx  = sys.Dx
    Dxx = sys.Dxx
    Dy  = sys.Dy
    Dyy = sys.Dyy

    potential = evoleq.potential
    phi0      = evoleq.phi0

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds for i in 1:Nx
            id  = Threads.threadid()
            aux = aux_acc[id]

            xi    = gauge.xi[1,i,j]
            xi_x  = Dx(gauge.xi, 1,i,j)
            xi_y  = Dy(gauge.xi, 1,i,j)
            xi_xx = Dxx(gauge.xi, 1,i,j)
            xi_yy = Dyy(gauge.xi, 1,i,j)
            xi_xy = Dx(Dy, gauge.xi, 1,i,j)

            @inbounds @simd for a in 1:Nu
                u     = sys.ucoord[a]
                u2    = u * u
                u3    = u * u2
                u4    = u2 * u2

                B1    = bulk.B1[a,i,j]
                B2    = bulk.B2[a,i,j]
                G     = bulk.G[a,i,j]
                phi   = bulk.phi[a,i,j]
                S     = bulk.S[a,i,j]
                Fx    = bulk.Fx[a,i,j]
                Fy    = bulk.Fy[a,i,j]
                Sd    = bulk.Sd[a,i,j]
                B1d   = bulk.B1d[a,i,j]
                B2d   = bulk.B2d[a,i,j]
                Gd    = bulk.Gd[a,i,j]
                phid  = bulk.phid[a,i,j]

                # r derivatives

                B1p        = -u2 * Du_B1[a,i,j]
                B2p        = -u2 * Du_B2[a,i,j]
                Gp         = -u2 * Du_G[a,i,j]
                phip       = -u2 * Du_phi[a,i,j]
                Sp         = -u2 * Du_S[a,i,j]
                Fxp        = -u2 * Du_Fx[a,i,j]
                Fyp        = -u2 * Du_Fy[a,i,j]

                B1pp       = 2*u3 * Du_B1[a,i,j]  + u4 * Duu_B1[a,i,j]
                B2pp       = 2*u3 * Du_B2[a,i,j]  + u4 * Duu_B2[a,i,j]
                Gpp        = 2*u3 * Du_G[a,i,j]   + u4 * Duu_G[a,i,j]
                phipp      = 2*u3 * Du_phi[a,i,j] + u4 * Duu_phi[a,i,j]
                Spp        = 2*u3 * Du_S[a,i,j]   + u4 * Duu_S[a,i,j]
                Fxpp       = 2*u3 * Du_Fx[a,i,j]  + u4 * Duu_Fx[a,i,j]
                Fypp       = 2*u3 * Du_Fy[a,i,j]  + u4 * Duu_Fy[a,i,j]

                # x and y derivatives

                B1_x       = Dx(bulk.B1, a,i,j)
                B2_x       = Dx(bulk.B2, a,i,j)
                G_x        = Dx(bulk.G,  a,i,j)
                phi_x      = Dx(bulk.phi,a,i,j)
                S_x        = Dx(bulk.S,  a,i,j)
                Fx_x       = Dx(bulk.Fx, a,i,j)
                Fy_x       = Dx(bulk.Fy, a,i,j)

                B1_y       = Dy(bulk.B1, a,i,j)
                B2_y       = Dy(bulk.B2, a,i,j)
                G_y        = Dy(bulk.G,  a,i,j)
                phi_y      = Dy(bulk.phi,a,i,j)
                S_y        = Dy(bulk.S,  a,i,j)
                Fx_y       = Dy(bulk.Fx, a,i,j)
                Fy_y       = Dy(bulk.Fy, a,i,j)

                B1p_x      = -u2 * Dx(Du_B1, a,i,j)
                B2p_x      = -u2 * Dx(Du_B2, a,i,j)
                Gp_x       = -u2 * Dx(Du_G,  a,i,j)
                phip_x     = -u2 * Dx(Du_phi,a,i,j)
                Sp_x       = -u2 * Dx(Du_S,  a,i,j)
                Fxp_x      = -u2 * Dx(Du_Fx,  a,i,j)
                Fyp_x      = -u2 * Dx(Du_Fy,  a,i,j)

                B1p_y      = -u2 * Dy(Du_B1, a,i,j)
                B2p_y      = -u2 * Dy(Du_B2, a,i,j)
                Gp_y       = -u2 * Dy(Du_G,  a,i,j)
                phip_y     = -u2 * Dy(Du_phi,a,i,j)
                Sp_y       = -u2 * Dy(Du_S,  a,i,j)
                Fxp_y      = -u2 * Dy(Du_Fx, a,i,j)
                Fyp_y      = -u2 * Dy(Du_Fy, a,i,j)

                B1_xx      = Dxx(bulk.B1, a,i,j)
                B2_xx      = Dxx(bulk.B2, a,i,j)
                G_xx       = Dxx(bulk.G,  a,i,j)
                phi_xx     = Dxx(bulk.phi,a,i,j)
                S_xx       = Dxx(bulk.S,  a,i,j)

                B1_yy      = Dyy(bulk.B1, a,i,j)
                B2_yy      = Dyy(bulk.B2, a,i,j)
                G_yy       = Dyy(bulk.G,  a,i,j)
                phi_yy     = Dyy(bulk.phi,a,i,j)
                S_yy       = Dyy(bulk.S,  a,i,j)

                B2_xy      = Dx(Dy, bulk.B2, a,i,j)
                G_xy       = Dx(Dy, bulk.G,  a,i,j)
                phi_xy     = Dx(Dy, bulk.phi,a,i,j)
                S_xy       = Dx(Dy, bulk.S,  a,i,j)

                vars = (
                    potential, phi0, u, xi, xi_x, xi_y, xi_xx, xi_yy, xi_xy,
                    B1   , B2   , G   , phi   , S    , Fx    , Fy    , Sd, B1d, B2d, Gd, phid,
                    B1p  , B2p  , Gp  , phip  , Sp   , Fxp   , Fyp   ,
                    B1pp , B2pp , Gpp , phipp , Spp  , Fxpp  , Fypp  ,
                    B1_x , B2_x , G_x , phi_x , S_x  , Fx_x  , Fy_x  ,
                    B1_y , B2_y , G_y , phi_y , S_y  , Fx_y  , Fy_y  ,
                    B1p_x, B2p_x, Gp_x, phip_x, Sp_x , Fxp_x , Fyp_x ,
                    B1p_y, B2p_y, Gp_y, phip_y, Sp_y , Fxp_y , Fyp_y ,
                    B1_xx, B2_xx, G_xx, phi_xx, S_xx ,
                    B1_yy, B2_yy, G_yy, phi_yy, S_yy ,
                           B2_xy, G_xy, phi_xy, S_xy
                )

                A_eq_coeff!(aux.ABCS, vars, sys.gridtype)

                aux.b_vec[a]   = -aux.ABCS[4]
                @inbounds @simd for aa in 1:Nu
                    aux.A_mat[a,aa] = aux.ABCS[1] * Duu[a,aa] + aux.ABCS[2] * Du[a,aa]
                end
                aux.A_mat[a,a] += aux.ABCS[3]
            end

            # BC

            aux.b_vec[1]    = bc.A[i,j]
            aux.A_mat[1,:] .= 0.0
            aux.A_mat[1,1]  = 1.0

            aux.b_vec[end]    = bc.A_u[i,j]
            @inbounds @simd for aa in 1:Nu
                aux.A_mat[end,aa]  = Du[1,aa]
            end

            solve_lin_system!(aux.A_mat, aux.b_vec)

            @inbounds @simd for aa in 1:Nu
                bulk.A[aa,i,j] = aux.b_vec[aa]
            end

        end
    end

    nothing
end

function solve_nested!(bulkconstrain::BulkConstrained, bulkevol::BulkEvolved, bc::BC,
                       gauge::Gauge, deriv::BulkDeriv, aux_acc,
                       sys::System, evoleq::AffineNull)
    Du_B1   = deriv.Du_B1
    Du_B2   = deriv.Du_B2
    Du_G    = deriv.Du_G
    Du_phi  = deriv.Du_phi
    Du_S    = deriv.Du_S
    Du_Fx   = deriv.Du_Fx
    Du_Fy   = deriv.Du_Fy
    Du_A    = deriv.Du_A
    Duu_B1  = deriv.Duu_B1
    Duu_B2  = deriv.Duu_B2
    Duu_G   = deriv.Duu_G
    Duu_phi = deriv.Duu_phi
    Duu_S   = deriv.Duu_S
    Duu_Fx  = deriv.Duu_Fx
    Duu_Fy  = deriv.Duu_Fy
    Duu_A   = deriv.Duu_A

    Du  = sys.Du
    Duu = sys.Duu
    Dx  = sys.Dx
    Dxx = sys.Dxx
    Dy  = sys.Dy
    Dyy = sys.Dyy

    bulk = Bulk(bulkevol, bulkconstrain)

    # solve for S
    vprint("INFO: solve_S")
    solve_S!(bulk, bc, gauge, deriv, aux_acc, sys, evoleq)

    # take u-derivatives of S
    vprint("INFO: S derivatives")
    @sync begin
        @spawn mul!(Du_S,   Du,  bulk.S)
        @spawn mul!(Duu_S,  Duu, bulk.S)
    end

    # solve for Fx and Fy
    vprint("INFO: solve_Fxy")
    solve_Fxy!(bulk, bc, gauge, deriv, aux_acc, sys, evoleq)

    # take u-derivatives of Fx and Fy
    vprint("INFO: Fxy derivatives")
    @sync begin
        @spawn mul!(Du_Fx,   Du,  bulk.Fx)
        @spawn mul!(Du_Fy,   Du,  bulk.Fy)
        @spawn mul!(Duu_Fx,  Duu, bulk.Fx)
        @spawn mul!(Duu_Fy,  Duu, bulk.Fy)
    end

    # solve for Sd
    vprint("INFO: solve_Sd")
    solve_Sd!(bulk, bc, gauge, deriv, aux_acc, sys, evoleq)

    # equations for B2d, (B1d, Gd) and phid are actually independent of each
    # other and could be solved in parallel. however, it seems that use of
    # @spawn here is actually harmful for scaling. since the loops in each
    # function are already threaded, it seems better not to @spawn.

    vprint("INFO: solve_B2d")
    solve_B2d!(bulk, bc, gauge, deriv, aux_acc, sys, evoleq)

    vprint("INFO: solve_B1dGd")
    solve_B1dGd!(bulk, bc, gauge, deriv, aux_acc, sys, evoleq)

    vprint("INFO: solve_phid")
    solve_phid!(bulk, bc, gauge, deriv, aux_acc, sys, evoleq)

    # solve for A
    vprint("INFO: solve_A")
    solve_A!(bulk, bc, gauge, deriv, aux_acc, sys, evoleq)

    # take u-derivatives of A. they will be needed for syncing the domains and
    # also for the xi_t function
    vprint("INFO: A derivatives")
    mul!(Du_A,   Du,  bulk.A)
    mul!(Duu_A,  Duu, bulk.A)

    nothing
end

function syncBCs!(bc::BC, bulk::BulkConstrained, deriv::BulkDeriv)
    Nu, Nx, Ny = size(bulk.S)

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds @simd for i in 1:Nx
            bc.S[i,j]    = bulk.S[end,i,j]
            bc.Fx[i,j]   = bulk.Fx[end,i,j]
            bc.Fy[i,j]   = bulk.Fy[end,i,j]
            bc.Sd[i,j]   = bulk.Sd[end,i,j]
            bc.B1d[i,j]  = bulk.B1d[end,i,j]
            bc.B2d[i,j]  = bulk.B2d[end,i,j]
            bc.Gd[i,j]   = bulk.Gd[end,i,j]
            bc.phid[i,j] = bulk.phid[end,i,j]
            bc.A[i,j]    = bulk.A[end,i,j]

            bc.S_u[i,j]   = deriv.Du_S[end,i,j]
            bc.Fx_u[i,j]  = deriv.Du_Fx[end,i,j]
            bc.Fy_u[i,j]  = deriv.Du_Fy[end,i,j]
            bc.A_u[i,j]   = deriv.Du_A[end,i,j]
        end
    end

    nothing
end

function set_innerBCs!(bc::BC, bulk::BulkEvolved, boundary::Boundary,
                       gauge::Gauge, deriv::BulkDeriv, sys::System{Inner},
                       evoleq::AffineNull)
    _, Nx, Ny = size(sys)

    Dx  = sys.Dx
    Dy  = sys.Dy

    phi0  = evoleq.phi0
    phi02 = phi0 * phi0
    phi03 = phi0 * phi02
    phi04 = phi02 * phi02

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds @simd for i in 1:Nx
            xi      = gauge.xi[1,i,j]
            xi3     = xi*xi*xi

            phi     = bulk.phi[1,i,j]
            phi_u   = deriv.Du_phi[1,i,j]

            phi_x   = Dx(bulk.phi, 1,i,j)
            phi_y   = Dy(bulk.phi, 1,i,j)

            xi_x    = Dx(gauge.xi, 1,i,j)
            xi_y    = Dy(gauge.xi, 1,i,j)

            b14     = bulk.B1[1,i,j]
            b24     = bulk.B2[1,i,j]
            g4      = bulk.G[1,i,j]

            a4      = boundary.a4[1,i,j]

            fx2     = boundary.fx2[1,i,j]
            fy2     = boundary.fy2[1,i,j]

            fx2_x   = Dx(boundary.fx2, 1,i,j)
            fy2_y   = Dy(boundary.fy2, 1,i,j)

            b14_x   = Dx(bulk.B1,1,i,j)
            b24_x   = Dx(bulk.B2,1,i,j)
            phi_x   = Dx(bulk.phi,1,i,j)
            g4_x    = Dx(bulk.G,1,i,j)

            b14_y   = Dy(bulk.B1,1,i,j)
            b24_y   = Dy(bulk.B2,1,i,j)
            phi_y   = Dy(bulk.phi,1,i,j)
            g4_y    = Dy(bulk.G,1,i,j)

            phi2    = phi03 * phi - phi0 * xi * xi
            phi2_x  = phi03 * phi_x - 2 * phi0 * xi * xi_x
            phi2_y  = phi03 * phi_y - 2 * phi0 * xi * xi_y

            bc.S[i,j]   = phi04 * (1 - 18 * phi) / 54
            bc.S_u[i,j] = phi02 * (-12 * xi3 +
                                   phi02 * xi * (18 * phi - 5) -
                                   24 * phi02 * phi_u) / 90

            bc.Fx[i,j]   = fx2
            bc.Fx_u[i,j] = -2 * fx2 * xi - 12 / 15 * (b14_x + b24_x - g4_y) +
                4/15 * phi0 * phi2_x

            bc.Fy[i,j]   = fy2
            bc.Fy_u[i,j] = -2 * fy2 * xi - 12 / 15 * (-b14_y + b24_y - g4_x) +
                4/15 * phi0 * phi2_y

            bc.Sd[i,j] = a4 / 2 - 5 * phi04/36 + phi0 * phi2/2

            bc.B2d[i,j] = -2 * b24
            bc.B1d[i,j] = -2 * b14
            bc.Gd[i,j]  = -2 * g4

            bc.A[i,j]   = a4
            bc.A_u[i,j] = -2 * xi * a4 - 2 * phi0 * xi * phi2 -
                2/3 * (phi02 * xi3 + fx2_x + fy2_y + phi04 * phi_u)
        end
    end

    # separate the phid case, since the if statement may prevent loop
    # vectorization

    if abs(phi0) < 1e-9
        fill!(bc.phid, 0)
        return
    end

    @fastmath @inbounds for j in 1:Ny
        @inbounds @simd for i in 1:Nx
            xi      = gauge.xi[1,i,j]
            phi     = bulk.phi[1,i,j]
            phi2    = phi03 * phi - phi0 * xi * xi

            bc.phid[i,j] = 1/3 - 3/2 * phi2 / phi03
        end
    end

    nothing
end

function set_outerBCs!(bc::BC, bulk::BulkConstrained, gauge::Gauge,
                       deriv::BulkDeriv, sys::System, evoleq::AffineNull)
    _, Nx, Ny = size(sys)

    phi0 = evoleq.phi0

    # we are here assuming that the inner and outer grids merely touch at the
    # interface, so we pass the values at this point without any interpolation
    u0 = sys.ucoord[end]

    @fastmath @inbounds @threads for j in 1:Ny
        @inbounds @simd for i in 1:Nx
            xi     = gauge.xi[1,i,j]

            S      = bulk.S[end,i,j]
            Fx     = bulk.Fx[end,i,j]
            Fy     = bulk.Fy[end,i,j]
            Sd     = bulk.Sd[end,i,j]
            B1d    = bulk.B1d[end,i,j]
            B2d    = bulk.B2d[end,i,j]
            Gd     = bulk.Gd[end,i,j]
            phid   = bulk.phid[end,i,j]
            A      = bulk.A[end,i,j]

            S_u    = deriv.Du_S[end,i,j]
            Fx_u   = deriv.Du_Fx[end,i,j]
            Fy_u   = deriv.Du_Fy[end,i,j]
            A_u    = deriv.Du_A[end,i,j]

            bc.S[i,j]   = S_inner_to_outer(S, u0, xi, phi0)
            bc.S_u[i,j] = S_u_inner_to_outer(S_u, S, u0, xi, phi0)

            bc.Fx[i,j]   = F_inner_to_outer(Fx, u0)
            bc.Fy[i,j]   = F_inner_to_outer(Fy, u0)
            bc.Fx_u[i,j] = F_u_inner_to_outer(Fx_u, Fx, u0)
            bc.Fy_u[i,j] = F_u_inner_to_outer(Fy_u, Fy, u0)

            bc.Sd[i,j]  = Sd_inner_to_outer(Sd, u0, xi, phi0)

            # B1d, B2d, and Gd transform in the same way
            bc.B2d[i,j] = Bd_inner_to_outer(B2d, u0)
            bc.B1d[i,j] = Bd_inner_to_outer(B1d, u0)
            bc.Gd[i,j]  = Bd_inner_to_outer(Gd, u0)

            bc.phid[i,j] = phid_inner_to_outer(phid, u0, phi0)

            bc.A[i,j]   = A_inner_to_outer(A, u0, xi, phi0)
            bc.A_u[i,j] = A_u_inner_to_outer(A_u, A, u0, xi, phi0)
        end
    end

    nothing
end

# We assume that the first entry on these arrays is the inner grid, and that
# there is only one domain spanning this grid. If we ever change this
# construction we must remember to make the appropriate changes here.
function solve_nesteds!(bulkconstrains, bulkevols, boundary::Boundary, gauge::Gauge,
                        bcs, derivs, aux_accs,
                        systems::SystemPartition, evoleq::AffineNull)
    Nsys = length(systems)

    # take all u-derivatives of the bulkevols functions
    vprint("INFO: bulkevols derivatives")
    @sync begin
        @inbounds for i in 1:Nsys
            @spawn mul!(derivs[i].Du_B1,  systems[i].Du,  bulkevols[i].B1)
            @spawn mul!(derivs[i].Du_B2,  systems[i].Du,  bulkevols[i].B2)
            @spawn mul!(derivs[i].Du_G,   systems[i].Du,  bulkevols[i].G)
            @spawn mul!(derivs[i].Du_phi, systems[i].Du,  bulkevols[i].phi)
            @spawn mul!(derivs[i].Duu_B1, systems[i].Duu, bulkevols[i].B1)
            @spawn mul!(derivs[i].Duu_B2, systems[i].Duu, bulkevols[i].B2)
            @spawn mul!(derivs[i].Duu_G,  systems[i].Duu, bulkevols[i].G)
            @spawn mul!(derivs[i].Duu_phi,systems[i].Duu, bulkevols[i].phi)
        end
    end

    vprint("INFO: innerBCs")
    set_innerBCs!(bcs[1], bulkevols[1], boundary, gauge, derivs[1], systems[1], evoleq)

    vprint("INFO: solve_nested 1")
    solve_nested!(bulkconstrains[1], bulkevols[1], bcs[1], gauge,
                  derivs[1], aux_accs[1], systems[1], evoleq)

    vprint("INFO: outerBCs")
    set_outerBCs!(bcs[2], bulkconstrains[1], gauge, derivs[1], systems[1], evoleq)

    @inbounds for i in 2:Nsys-1
        vprint("INFO: solve_nested $i")
        solve_nested!(bulkconstrains[i], bulkevols[i], bcs[i], gauge,
                      derivs[i], aux_accs[i], systems[i], evoleq)
        syncBCs!(bcs[i+1], bulkconstrains[i], derivs[i])
    end
    vprint("INFO: solve_nested $Nsys")
    solve_nested!(bulkconstrains[Nsys], bulkevols[Nsys], bcs[Nsys], gauge,
                  derivs[Nsys], aux_accs[Nsys], systems[Nsys], evoleq)

    nothing
end

# for testing only: set all constrained variables to zero
function solve_nesteds!(bulkconstrains, bulkevols, boundary::Boundary, gauge::Gauge,
                        bcs, derivs, aux_accs,
                        systems, evoleq::EvolTest0)
    Nsys = length(systems)

    for i in 1:Nsys
        fill!(bulkconstrains[i], 0)
    end
    nothing
end

struct Nested{S,C,D,B,A}
    systems        :: S
    bulkconstrains :: C
    derivs         :: D
    bcs            :: B
    aux_accs       :: A
end

function Nested(systems::SystemPartition, bulkconstrains::BulkPartition,
                derivs::BulkPartition)
    T        = Jecco.coord_eltype(systems[1].ucoord)
    bcs      = BCs(systems)
    aux_accs = [Aux{T}(sys) for sys in systems]

    Nested{typeof(systems),typeof(bulkconstrains),typeof(derivs),
           typeof(bcs),typeof(aux_accs)}(systems, bulkconstrains, derivs, bcs,
                                         aux_accs)
end

function Nested(systems::SystemPartition, bulkconstrains::BulkPartition)
    T        = Jecco.coord_eltype(systems[1].ucoord)
    bcs      = BCs(systems)
    aux_accs = [Aux{T}(sys) for sys in systems]
    derivs   = BulkDerivPartition(systems)

    Nested(systems, bulkconstrains, derivs)
end

function Nested(systems::SystemPartition)
    T        = Jecco.coord_eltype(systems[1].ucoord)
    bcs      = BCs(systems)
    aux_accs = [Aux{T}(sys) for sys in systems]

    bulkconstrains = BulkConstrainedPartition(systems)
    derivs         = BulkDerivPartition(systems)

    Nested(systems, bulkconstrains, derivs)
end

function (nested::Nested)(bulkevols::BulkPartition, boundary::Boundary,
                          gauge::Gauge, evoleq::EvolutionEquations)
    solve_nesteds!(nested.bulkconstrains, bulkevols, boundary, gauge, nested.bcs,
                   nested.derivs, nested.aux_accs, nested.systems, evoleq)
end


using TensorNetworkAD
using Test
using Zygote, OMEinsum
using TensorNetworkAD: model_tensor, tensorfromclassical, trg_svd

@testset "unit" begin
    t = randn(10,10,10,10)
    u, v = trg_svd(t, 100, 0)
    @test t ≈ ein"ija,akl -> ijkl"(u,v)
end
@testset "real" begin
    χ, niter = 5, 5
    foo = β -> trg(model_tensor(Ising(),β), χ, niter)
    # the pytorch result with tensorgrad
    # https://github.com/wangleiphy/tensorgrad
    # clone this repo and type
    # $ python 1_ising_TRG/ising.py -chi 5 -Niter 5
    @test foo(0.4) ≈ 0.8919788686747141
    @test num_grad(foo, 0.4, δ=1e-6) ≈ Zygote.gradient(foo, 0.4)[1]
end

@testset "complex" begin
    β, χ, niter = 0.4, 12, 3
    @test trg(model_tensor(Ising(),β), χ, niter) ≈
        real(trg(model_tensor(Ising(),β) .+ 0im, χ, niter))
    @test Zygote.gradient(β -> trg(model_tensor(Ising(),β), χ, niter), 0.4)[1] ≈
        real(Zygote.gradient(β -> real(trg(model_tensor(Ising(),β) .+ 0im, χ, niter)), 0.4)[1])
end


using StanModels, CSV

df = CSV.read(joinpath(@__DIR__, "..", "..", "data", "chimpanzees.csv"), delim=';');

# Define the Stan language model

m10_4sl = "
data {
  int N;
  int<lower=0, upper=1> L[N];
  vector[N] P;
  vector[N] C;

  int<lower=1, upper=N> N_chimps;
  int<lower=1, upper=N_chimps> chimp[N];
}
parameters {
  real a_chimp[N_chimps];
  real bp;
  real bpc;
}
model {
  vector[N] p;
  target += normal_lpdf(a_chimp | 0, 10);
  target += normal_lpdf(bp | 0, 10);
  target += normal_lpdf(bpc | 0, 10);
  for (i in 1:N) p[i] = a_chimp[chimp[i]] + (bp + bpc * C[i]) * P[i];
  target += binomial_logit_lpmf(L | 1, p);
}
generated quantities {
  vector[N] log_lik;
  {
    vector[N] p;
    for(n in 1:N) {
      p[n] = a_chimp[chimp[n]] + (bp + bpc * C[n]) * P[n];
      log_lik[n] = binomial_logit_lpmf(L[n] | 1, p[n]);
    }
  }
}
";

# Define the Stanmodel and set the output format to :mcmcchains.

sm = SampleModel("m10.4sl", m10_4sl);

# Input data for cmdstan

m10_4_data = Dict("N" => size(df, 1), "N_chimps" => length(unique(df[!, :actor])),
"chimp" => df[!, :actor], "L" => df[!, :pulled_left],
"P" => df[!, :prosoc_left], "C" => df[!, :condition]);

# Sample using cmdstan

(sample_file, log_file) = stan_sample(sm, data=m10_4_data);

# Result rethinking

rethinking = "
      mean   sd  5.5% 94.5% n_eff Rhat
bp    0.84 0.26  0.43  1.26  2271    1
bpC  -0.13 0.29 -0.59  0.34  2949    1

a[1] -0.74 0.27 -1.16 -0.31  3310    1
a[2] 10.88 5.20  4.57 20.73  1634    1
a[3] -1.05 0.28 -1.52 -0.59  4206    1
a[4] -1.05 0.28 -1.50 -0.60  4133    1
a[5] -0.75 0.27 -1.18 -0.32  4049    1
a[6]  0.22 0.27 -0.22  0.65  3877    1
a[7]  1.81 0.39  1.22  2.48  3807    1
";

# Update sections

if !(sample_file == nothing)
  chn = read_samples(sm)

  chn2 = set_section(chn, Dict(
    :parameters => ["bp", "bpc"],
    :pooled => ["a_chimp.$i" for i in 1:7],
    :generated => ["log_lik.$i" for i in 1:504],
    :internals => ["lp__", "accept_stat__", "stepsize__", "treedepth__", "n_leapfrog__",
      "divergent__", "energy__"]
    )
  )

  describe(chn2)
  describe(chn2, sections=[:pooled])
end


using Test

using ClimateMachine.SurfaceFluxes
using ClimateMachine.SurfaceFluxes.Nishizawa2018
using ClimateMachine.SurfaceFluxes.Byun1990
using ClimateMachine.Thermodynamics
using RootSolvers
using CLIMAParameters
struct EarthParameterSet <: AbstractEarthParameterSet end
const param_set = EarthParameterSet()

# FIXME: Use realistic values / test for correctness
# These tests have been run to ensure they do not fail,
# but they need further testing for correctness.

FT = Float32
rtol = 10 * eps(FT)

@testset "SurfaceFluxes" begin
    shf, lhf, T_b, q_pt, α_0 =
        FT(60), FT(50), FT(350), PhasePartition{FT}(0.01, 0.002, 0.0001), FT(1)
    buoyancy_flux =
        SurfaceFluxes.compute_buoyancy_flux(param_set, shf, lhf, T_b, q_pt, α_0)
    @test buoyancy_flux ≈ 0.0017808608107074118
    @test buoyancy_flux isa FT
end

@testset "SurfaceFluxes.Byun1990" begin
    u, flux = FT(0.1), FT(0.2)
    MO_len = Byun1990.monin_obukhov_len(param_set, u, flux)
    @test MO_len ≈ -0.0125

    u_ave, buoyancy_flux, z_0, z_1 = FT(0.1), FT(0.2), FT(2), FT(5)
    γ_m, β_m = FT(15), FT(4.8)
    tol_abs, iter_max = FT(1e-3), 10
    u_star = Byun1990.compute_friction_velocity(
        param_set,
        u_ave,
        buoyancy_flux,
        z_0,
        z_1,
        β_m,
        γ_m,
        tol_abs,
        iter_max,
    )
    @test u_star ≈ 0.201347256193615 atol = tol_abs
    @test u_star isa FT

    Ri, z_b, z_0, Pr_0 = FT(10), FT(2), FT(5.0), FT(0.74)
    γ_m, γ_h, β_m, β_h = FT(15), FT(9), FT(4.8), FT(7.8)
    cm, ch, L_mo = Byun1990.compute_exchange_coefficients(
        param_set,
        Ri,
        z_b,
        z_0,
        γ_m,
        γ_h,
        β_m,
        β_h,
        Pr_0,
    )
    @test cm ≈ 19.700348427787368
    @test ch ≈ 3.3362564728997803
    @test L_mo ≈ -14.308268023583906
    @test cm isa FT
    @test ch isa FT
    @test L_mo isa FT

    Ri, z_b, z_0, Pr_0 = FT(-10), FT(10.0), FT(1.0), FT(0.74)
    γ_m, γ_h, β_m, β_h = FT(15.0), FT(9.0), FT(4.8), FT(7.8)
    cm, ch, L_mo = Byun1990.compute_exchange_coefficients(
        param_set,
        Ri,
        z_b,
        z_0,
        γ_m,
        γ_h,
        β_m,
        β_h,
        Pr_0,
    )
    @test cm ≈ 0.33300280321092746 rtol = rtol
    @test ch ≈ 1.131830939627489 rtol = rtol
    @test L_mo ≈ -0.3726237964444814 rtol = rtol
    @test cm isa FT
    @test ch isa FT
    @test L_mo isa FT

end

@testset "SurfaceFluxes.Nishizawa2018" begin
    FT = Float32
    rtol = 10 * eps(FT)
    u, θ, flux = FT(2), FT(350), FT(20)
    MO_len = Nishizawa2018.monin_obukhov_len(param_set, u, θ, flux)
    @test MO_len ≈ -35.67787971457696

    u_ave, θ, flux, Δz, z_0, a =
        FT(110.0), FT(350.0), FT(20.0), FT(100.0), FT(0.01), FT(5.0)
    Ψ_m_tol, tol_abs, iter_max = FT(1e-3), FT(1e-3), 10
    u_star = Nishizawa2018.compute_friction_velocity(
        param_set,
        u_ave,
        θ,
        flux,
        Δz,
        z_0,
        a,
        Ψ_m_tol,
        tol_abs,
        iter_max,
    )
    @test u_star ≈ 5.526644550864822 atol = tol_abs
    @test u_star isa FT

    FT = Float64
    rtol = 10 * eps(FT)
    z, F_m, F_h, a, u_star, θ, flux, Pr =
        FT(1), FT(2), FT(3), FT(5), FT(110), FT(350), FT(20), FT(0.74)

    K_m, K_h, L_mo = Nishizawa2018.compute_exchange_coefficients(
        param_set,
        z,
        F_m,
        F_h,
        a,
        u_star,
        θ,
        flux,
        Pr,
    )
    @test K_m ≈ -11512.071612359368 rtol = rtol
    @test K_h ≈ -6111.6196776263805 rtol = rtol
    @test L_mo ≈ -5.935907237512742e6 rtol = rtol

    # Test type-stability:
    FT = Float32
    rtol = 10 * eps(FT)
    z, F_m, F_h, a, u_star, θ, flux, Pr =
        FT(1), FT(2), FT(3), FT(5), FT(110), FT(350), FT(20), FT(0.74)

    K_m, K_h, L_mo = Nishizawa2018.compute_exchange_coefficients(
        param_set,
        z,
        F_m,
        F_h,
        a,
        u_star,
        θ,
        flux,
        Pr,
    )
    @test K_m isa FT
    @test K_h isa FT
    @test L_mo isa FT

end


"""
	FrustumMap(viewport::Matrix4,projection::Matrix4,modelview::Matrix4)

`mutable struct` to contain the current values of matrices of viewing pipeline.

The constructor `FrustumMap()` returns the `viewport`, `projection`, `modelview` matrices and their inverses.

# Example
```
function projectPoint(map::FrustumMap,p3::Point3d)
	p4=(map.viewport * (map.projection * (map.modelview * Point4d(p3[1],p3[2],p3[3],1.0))))
	return Point3d(p4[1]/p4[4],p4[2]/p4[4],p4[3]/p4[4])
end
```
"""
mutable struct FrustumMap

	viewport::Matrix4
	projection::Matrix4
	modelview::Matrix4

	inv_viewport::Matrix4
	inv_projection::Matrix4
	inv_modelview::Matrix4

	# constructor
	function FrustumMap(viewport,projection::Matrix4,modelview::Matrix4)
		x,y,w,h = viewport
		viewport_T=Matrix4(
			w/2.0,   0.0,   0.0, x+w/2.0,
			  0.0, h/2.0,   0.0, y+h/2.0,
			  0.0,   0.0, 1/2.0,   1/2.0,
			  0.0,   0.0,   0.0,     1.0)
		new(viewport_T,projection,modelview,inv(viewport_T),inv(projection),inv(modelview))
	end

end

"""
	projectPoint(map::FrustumMap,p3::Point3d)

Return in 3D screen coordinates the *point of projection*, given in 3D world coordinates.
"""
function projectPoint(map::FrustumMap,p3::Point3d)
	p4=(map.viewport * (map.projection * (map.modelview * Point4d(p3[1],p3[2],p3[3],1.0))))
	return Point3d(p4[1]/p4[4],p4[2]/p4[4],p4[3]/p4[4])
end

"""
	nprojectPoint(map::FrustumMap,x::Float32,y::Float32, z::Float32)

Return in 3D world coordinates a `GL.Point3d`, given in 3D screen coordinates.
Use the inverse matrices of viewing transformation, contained in `map::FrustumMap`.
"""
function unprojectPoint(map::FrustumMap,x::Float64,y::Float64, z::Float64)
	p4 = (map.inv_modelview * (map.inv_projection * (map.inv_viewport * Point4d(x,y,z, 1.0))))
	return Point3d(p4[1]/p4[4],p4[2]/p4[4],p4[3]/p4[4])
end


@testset "Infinite" begin
    @testset "Base" begin
        @test_throws MethodError Infinite()
        @inferred Infinite(true)
        @inferred Infinite(false)

        @test Infinite(∞) == ∞

        buf = IOBuffer()
        showerror(buf, InfMinusInfError())
        msg = String(take!(buf))
        @test msg == "∞-∞ is undefined"
    end

    @testset "IO" begin
        inf = Infinite(false)
        ninf = Infinite(true)

        @test string(inf) == "∞"
        @test sprint(show, inf, context=:compact=>true) == "∞"
        @test sprint(show, inf) == string(inf)

        @test string(ninf) == "-∞"
        @test sprint(show, ninf, context=:compact=>true) == "-∞"
        @test sprint(show, ninf) == string(ninf)
    end

    @testset "Conversion" begin
        @test_throws InexactError convert(Int, ∞)
        @test convert(Float64, ∞) == Inf
        @test convert(Rational{Int}, ∞) == 1//0

        @test_throws InexactError convert(Infinite, 2)
        @test convert(Infinite, Inf) == ∞
        @test convert(Infinite, 1//0) == ∞

        @test convert(Infinite, ∞) == ∞

        @test Float64(∞) == Inf

        @test one(Infinite) == Infinity.UnknownReal
        @test zero(Infinite) == Infinity.UnknownReal
        @test float(Infinite) == float(Int)
        @test float(Infinity.UnknownReal) == float(Int)
    end

    @testset "Parse" begin
        @test tryparse(Infinite, "∞") === ∞
        @test tryparse(Infinite, "-∞") === -∞
        @test tryparse(Infinite, "1") === nothing

        @test parse(Infinite, "∞") === ∞
        @test parse(Infinite, "-∞") === -∞
        @test_throws ArgumentError parse(Infinite, "1")
    end

    @testset "Comparison" begin
        x = ∞
        @test !isfinite(x)
        @test !isfinite(-x)
        @test isinf(x)
        @test isinf(-x)
        @test !iszero(x)
        @test !iszero(-x)
        @test x == x
        @test x == Inf
        @test Inf == x
        @test x != -x
        @test x != -Inf
        @test Inf != -x
        @test hash(x) != hash(-x)
        @test -x < x
        @test x > -x
        @test x <= x
        @test -x >= -x
        @test signbit(-x)
        @test !signbit(x)
        @test sign(x) == 1
        @test sign(-x) == -1
        @test isapprox(x, x)
        @test !isapprox(x, -x)
    end

    @testset "Arithmetic" begin
        x = ∞
        @test typemin(Infinite) == -x
        @test typemax(Infinite) == x

        @test +x == x
        @test -x == -x
        @test -(-x) == x

        @test_throws InfMinusInfError x - x
        @test x + x == x
        @test -x + -x == -x
        @test -x - x == -x

        @test x * x == x
        @test x * -x == -x
        @test -x * x == -x
        @test -x * -x == x

        @test_throws DivideError x / x

        @test abs(x) == x
        @test abs(-x) == x

        @test_throws DivideError x // x
        @test x // 2 == 1//0
        @test 2 // x == 0//1
    end

    @testset "Rand" begin
        @test typeof(rand(Infinite)) === Infinite

        Random.seed!(1)
        @test rand(Infinite) == ∞
        @test rand(Infinite) == ∞
        @test rand(Infinite) == -∞
    end
end


@system LeafInitiation(Stage, Planting, FloralInitiation) begin
    SD: storage_days => 100 ~ preserve(u"d", parameter)
    ST: storage_temperature => 5 ~ preserve(u"°C", parameter)
    #FIXME: ThermalTime only accepts override of track, not preserve
    STP(ST): storage_tempreature_proxy ~ track(u"°C")
    SBF(context, T=STP, To=T_opt', Tx=T_ceil'): storage_beta_function ~ ::BetaFunction
    ILN: initial_leaves_at_harvest => 4 ~ preserve::int(parameter)
    ILS(LIR_max, β=SBF.ΔT, SD): initial_leaves_during_storage => begin
        LIR_max * β * SD
    end ~ preserve::int(round=:floor)
    initial_leaves(ILN, ILS) => ILN + ILS ~ preserve::int

    LIR_max: maximum_leaf_initiation_rate => 0.20 ~ preserve(u"d^-1", parameter)

    leaf_initiation(r=LIR_max, β=BF.ΔT) => r*β ~ accumulate(when=leaf_initiating)

    #HACK original Radish model assumed leaves are being initiated when the seeds are sown
    #HACK maize model model assumed leaf initiation begins when germination is over
    leaf_initiateable(planted) ~ flag

    # for maize
    #leaf_initiated(pheno.tassel_initiation.over) ~ flag
    # for Radish
    leaf_initiated(floral_initiated) ~ flag

    leaf_initiating(leaf_initiateable & !leaf_initiated) ~ flag

    # no MAX_LEAF_NO implied unlike original model
    leaves_initiated(initial_leaves, leaf_initiation) => begin
        initial_leaves + leaf_initiation
    end ~ track::int(round=:floor)
end


"""
    ESGPtrain(esn::AbstractLeakyESN, mean::GaussianProcesses.Mean, kernel::GaussianProcesses.Kernel
    [, lognoise::Float64, optimize::Bool, optimizer::Optim.AbstractOptimizer, y_target::AbstractArray{Float64})

Train the ESN using Gaussian Processes, as described in [1]

[1] Chatzis, Sotirios P., and Yiannis Demiris. "Echo state Gaussian process." IEEE Transactions on Neural Networks 22.9 (2011): 1435-1445.
"""
function ESGPtrain(esn::AbstractLeakyESN,
    mean::GaussianProcesses.Mean,
    kernel::GaussianProcesses.Kernel;
    lognoise::Float64 = -2.0,
    optimize::Bool = false,
    optimizer::Optim.AbstractOptimizer = Optim.LBFGS(),
    y_target::AbstractArray{Float64} = esn.train_data)

    states_new = nla(esn.nla_type, esn.states)
    if size(y_target, 1) == 1
        gp = GP(states_new, vec(y_target), mean, kernel, lognoise)
        if optimize == true
            optimize!(gp; method=optimizer)
        end
    else
        gp = []
        for i=1:size(y_target, 1)
            push!(gp, GP(states_new, y_target[i,:], mean, kernel, lognoise))
            if optimize == true
                optimize!(gp[i]; method=optimizer)
            end
        end
    end
    return gp
end

#one variable timestep prediction
function ESGPpredict(esn::AbstractLeakyESN,
    predict_len::Int,
    gp::GaussianProcesses.GPE)

    output = zeros(Float64, esn.in_size, predict_len)
    sigmas = zeros(Float64, esn.in_size, predict_len)
    x = esn.states[:, end]

    if esn.extended_states == false
        for i=1:predict_len
            x_new = hcat(nla(esn.nla_type, x)...)'
            out, sigma = GaussianProcesses.predict_y(gp, x_new)
            output[:, i] = out
            sigmas[:,i] = sigma
            x = leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x, out)
        end
    else
        for i=1:predict_len
            x_new = hcat(nla(esn.nla_type, x)...)'
            out, sigma = GaussianProcesses.predict_y(gp, x_new)
            output[:, i] = out
            sigmas[:,i] = sigma
            x = vcat(leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x[1:esn.res_size], out), out)
        end
    end
    return output, sigmas
end

#multidimensional timestep prediction

"""
    ESGPpredict(esn::AbstractLeakyESN, predict_len::Int, gp::AbstractArray{Any})

Return the prediction for a given lenght of the constructed ESN struct using GPs.

"""
function ESGPpredict(esn::AbstractLeakyESN,
    predict_len::Int,
    gp::AbstractArray{Any})

    output = zeros(Float64, esn.in_size, predict_len)
    sigmas = zeros(Float64, esn.in_size, predict_len)
    x = esn.states[:, end]

    if esn.extended_states == false
        for i=1:predict_len
            x_new = hcat(nla(esn.nla_type, x)...)'
            out = Array{Float64}(undef, esn.out_size)
            sigma = Array{Float64}(undef, esn.out_size)
            for i=1:size(gp, 1)
                out[i] = GaussianProcesses.predict_y(gp[i], x_new)[1][1]
                sigma[i] = GaussianProcesses.predict_y(gp[i], x_new)[2][1]
            end
            output[:, i] = out
            sigmas[:,i] = sigma
            x = leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x, out)
        end
    else
        for i=1:predict_len
            x_new = hcat(nla(esn.nla_type, x)...)'
            out = Array{Float64}(undef, esn.out_size)
            sigma = Array{Float64}(undef, esn.out_size)
            for i=1:size(gp, 1)
                out[i] = GaussianProcesses.predict_y(gp[i], x_new)[1][1]
                sigma[i] = GaussianProcesses.predict_y(gp[i], x_new)[2][1]
            end
            output[:, i] = out
            sigmas[:,i] = sigma
            x = vcat(leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x[1:esn.res_size], out), out)
        end
    end
    return output, sigmas
end

"""
    ESGPpredict_h_steps(esn::AbstractLeakyESN, predict_len::Int, h_steps::Int, test_data::AbstractArray{Float64}, gp::GaussianProcesses.GPE)

Return the prediction for h steps ahead of the constructed ESN struct using GPs.
"""
function ESGPpredict_h_steps(esn::AbstractLeakyESN,
    predict_len::Int,
    h_steps::Int,
    test_data::AbstractArray{Float64},
    gp::GaussianProcesses.GPE)

    output = zeros(Float64, esn.in_size, predict_len)
    sigmas = zeros(Float64, esn.in_size, predict_len)
    x = esn.states[:, end]

    if esn.extended_states == false
        for i=1:predict_len
            x_new = hcat(nla(esn.nla_type, x)...)'
            out, sigma = GaussianProcesses.predict_y(gp, x_new)
            output[:, i] = out
            sigmas[:,i] = sigma
            if mod(i, h_steps) == 0
                x = leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x, test_data[:,i])
            else
                x = leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x, out)
            end
        end
    else
        for i=1:predict_len
            x_new = hcat(nla(esn.nla_type, x)...)'
            out, sigma = GaussianProcesses.predict_y(gp, x_new)
            output[:, i] = out
            sigmas[:,i] = sigma
            if mod(i, h_steps) == 0
                x = vcat(leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x[1:esn.res_size], test_data[:,i]), test_data[:,i])
            else
                x = vcat(leaky_fixed_rnn(esn.activation, esn.alpha, esn.W, esn.W_in, x[1:esn.res_size], out), out)
            end
        end
    end
    return output, sigmas
end


using Plots
using DifferentialEquations

@userplot threebodyplot
@recipe function f(tbp::threebodyplot)
    x, y, l, xlim, ylim = tbp.args
    xaxis --> ("x", xlim)
    yaxis --> ("y", ylim)
    seriestype --> :scatter
    markertype --> :o
    markercolor --> "black"
    label --> l
    dpi --> 300
    [x], [y]
end

function anim()

    function inertial(ddu, du, u, p, t)

        μ1 = p[1,1]
        μ2 = p[2,1]
        x1 = p[:,2]
        x2 = p[:,3]

        r1 = sqrt((x1[1] - u[1])^2 + (x1[2] - u[2])^2 )
        r2 = sqrt((x2[1] - u[1])^2 + (x2[2] - u[2])^2 )

        ddu[1] = μ1*(x1[1] - u[1])/r1^3 + μ2*(x2[1] - u[1])/r2^3
        ddu[2] = μ1*(x1[2] - u[2])/r1^3 + μ2*(x2[2] - u[2])/r2^3

    end

    function rotating(ddu, du, u, p, t)

        μ1 = p[1]
        μ2 = p[2]
        n = p[3]

        r1 = sqrt((u[1] + μ2)^2 + u[2]^2)
        r2 = sqrt((u[1] - μ1)^2 + u[2]^2)

        ddu[1] = -(μ1*(u[1] + μ2)/r1^3 + μ2*(u[1] - μ1)/r2^3) + 2n*du[2] + n^2*u[1]
        ddu[2] = -u[2]*(μ1/r1^3 + μ2/r2^3) - 2n*du[1] + n^2*u[2]

    end

    function hill(ddu, du, u, p, t)

        f = -3/(u[1]^2 + u[2]^2)^1.5

        ddu[1] = u[1]*(f + 3) + 2du[2]
        ddu[2] = u[2]*f - 2du[1]

    end

    t0 = -100.
    t1 = 150.
    tspan = (t0, t1)

    mu1 = .2
    mu2 = .8
    n = 1
    x1 = [1, 1]
    x2 = [-1, -1]

    p = zeros(2,3)
    p[1,1] = mu1
    p[2,1] = mu2
    p[:,2] = x1
    p[:,3] = x2

    du0 = zeros(2)
    u0 = zeros(2)
    u0[1] = 0.5

    probi = SecondOrderODEProblem(inertial, du0, u0, tspan, p, abstol=1e-8, reltol=1e-8)
    soli = solve(probi, Tsit5())

    p = zeros(3)
    p[1] = mu1
    p[2] = mu2
    p[3] = n

    dv0 = zeros(2)
    v0 = zeros(2)
    v0[1] = u0[1]*cos(n*t0) + u0[2]*sin(n*t0)
    v0[2] = -u0[1]*sin(n*t0) + u0[2]*cos(n*t0)

    probr = SecondOrderODEProblem(rotating, dv0, v0, tspan, p, abstol=1e-8, reltol=1e-8)
    solr = solve(probr, Tsit5())

    dw0 = zeros(2)
    w0 = zeros(2)
    w0[1] = v0[1] + 1
    w0[2] = v0[2]

    probh = SecondOrderODEProblem(hill, dw0, w0, tspan, abstol=1e-8, reltol=1e-8)
    solh = solve(probr, Tsit5())

    n = min(length(solh.t), length(solr.t), length(soli.t))

    anim = @animate for i in 1:10:n

        p1 = plot((x1[1], x1[2]), markershape=:o, markercolor="blue", label="planet 1",
                  legend=:bottomright, framestyle=:zerolines)
        p1 = plot!((x2[1], x2[2]), markershape=:o, markercolor="green", label="planet 2")
        p1 = threebodyplot!(soli[3,i], soli[4,i], "intertial", (-1.5,1.5), (-1.5,1.5))

        p2 = plot((-mu2, 0), markershape=:o, markercolor="blue",  label="planet 1",
                  legend=:bottomright, framestyle=:zerolines)
        p2 = plot!((mu1, 0), markershape=:o, markercolor="green", label="planet 2")
        p2 = threebodyplot!(solr[3,i], solr[4,i], "rotating", (-1, 1), (-1, 1))

        p3 = plot((0, 0), markershape=:o, markercolor="green", label="planet 2",
                  legend=:bottomright, framestyle=:zerolines)
        p3 = threebodyplot!(solh[3,i], solh[4,i], "hill", (-1.5,1.5), (-1.5,1.5))

        plot(p1, p2, p3, layout=(3,1), size=(800,1600))
    end

    gif(anim, "de_anim_15.gif", fps=15)

end

anim()


module test_julia_math

using Test

@test Base.Math.FMA_NATIVE isa Bool

@test 0.99999999 ≈ 1

# Hodge star operator
@test Symbol(Char(0x22c6)) === :⋆

⋆(n) = n
@test ⋆(1) == 1

@test sqrt(2) == hypot(1, 1)
@test sqrt(5) == hypot(2, 1) == hypot(1, 2)
@test sqrt(8) == hypot(2, 2)
@test 3.0 == hypot(2, 1, 2)

@test ℯ^(im)π ≈ -1

end # module test_julia_math

module test_julia_math_hypot

using Test

@test hypot(3, 4) == 5

end # module test_julia_math_hypot


"""
    module Mesh

Mesh data structures and the interface expected from an `AbstractMesh`.

Exports: $(EXPORTS)
"""

module Mesh

using DocStringExtensions
using StaticArrays
using LinearAlgebra
using ForwardDiff # for computing derivatives of parametric elements
using OrderedCollections

using WaveProp.Utils
using WaveProp.Geometry
using WaveProp.Integration

import WaveProp.Geometry: ambient_dimension, geometric_dimension, domain, range, boundary, parametrization
import WaveProp.Integration: qnodes, qweights, qnormals, integrate, normal, jacobian, singular_quadrature

export GenericMesh, ElementIterator, nodes, etypes, elements, ent2tags, el2qnodes, elements, near_interaction_list, nodes,
compute_quadrature!,SubMesh, AbstractMesh, CartesianMesh, meshgen, NystromMesh, dof, AbstractElement, LagrangeLine, LagrangeTriangle, LagrangeRectangle, LagrangeTetrahedron, LagrangeElement, ParametricElement, derivative, derivative2, measure, convert_to_2d, decompose

include("element.jl")
include("meshes.jl")
include("submesh.jl")
include("cartesianmesh.jl")
include("meshgen.jl")
include("decompose.jl")
include("elementiterator.jl")

end


using Sobol
using NLopt
using PyPlot
using SpecialFunctions
using SparseArrays
using LinearAlgebra
using BenchmarkTools
using PyPlot
using Statistics
using MultistartOptimization

### ALL FUNCTIONS NEEDED FOR SOBOL PT GENERATION AND MLSL

# Generate an N-dimentional Sobol sequence of points
function gen_sobol(Npts, Dims,lb,ub)
    sobol_seq = SobolSeq(lb, ub)
    int_arr = [reshape(next!(sobol_seq),1,Dims) for i = 1:Npts]
    sobol_array = permutedims(reshape(hcat(int_arr...),(length(int_arr[1]), length(int_arr))))

    return sobol_array
end

# Generate 9-Dimentional schwefel test function:
# Global min is: f(x) = 0 at x = (420.9687,...,420.9687)
function schwefel_funcND(x)
    sum = 0
    Dims = length(x)
    for ii = 1:Dims
        xi = x[ii]
        sum = sum + xi*sin(sqrt(abs(xi)))
    end
    y = 418.982887*Dims - sum
    return y
end

# derivative of 9-Dimentional schwefel test function:
function schwefel_funcND_der(x)
    hold = similar(x)
    for ii = 1:size(hold)[1]
        hold[ii] = -sin(sqrt(abs(x[ii]))) - (x[ii]*cos(sqrt(abs(x[ii])))*sign(x[ii])/(2*sqrt(abs(x[ii]))))
    end
    return hold
end

#  test function for 9d schwefel
function testfunc_Nd_shwef(x::Vector, grad::Vector)
    if length(grad) > 0
        for i = 1:length(grad)
            grad[i] = schwefel_funcND_der(x)[i]
        end
    end
    return schwefel_funcND(x)
end

# camel function for testing
function six_hump_camel(x)
    y = 4*x[1]^2 - 2.1*x[1]^4 + (1/3)x[1]^6 + x[1]*x[2] - 4*x[2]^2+ 4*x[2]^4
    return y
end

# 2D shubert function
function shubert_func(x)

    x1 = x[1]
    x2 = x[2]
    sum1 = 0
    sum2 = 0
    for ii = 1:5
        new1 = ii * cos((ii+1)*x1+ii)
        new2 = ii * cos((ii+1)*x2+ii)
        sum1 = sum1 + new1
        sum2 = sum2 + new2
    end
    return sum1 * sum2

end

# derivative of 2D shubert function
function shubert_func_deriv(x)
    x1 = x[1]
    x2 = x[2]
    sum1 = 0
    sum2 = 0
    for ii = 1:5
        new1 = ii * cos((ii+1)*x1+ii)
        sum1 = sum1 + new1
    end
    for jj = 1:5
        new2 = -(jj * (jj+1)* sin((jj+1)*x2+jj))
        sum2 = sum2 + new2
    end
    der2 = sum1*sum2
    sum1 = 0
    sum2 = 0
    for ii = 1:5
        new1 = ii * cos((ii+1)*x2+ii)
        sum1 = sum1 + new1
    end
    for jj = 1:5
        new2 = -(jj * (jj+1)* sin((jj+1)*x1+jj))
        sum2 = sum2 + new2
    end

    der1 = sum1*sum2

    return [der1 der2]
end

#  test function for 2d shubert
function testfunc_shub(x::Vector, grad::Vector)
    if length(grad) > 0
        # gradients for six hump camel
        grad[1] = shubert_func_deriv(x)[1]
        grad[2] = shubert_func_deriv(x)[1]
    end
    return shubert_func(x)
end

# tes function for 2d six hump camel
function testfunc_6hc(x::Vector, grad::Vector)
    if length(grad) > 0
        # gradients for six hump camel
        grad[1] = 8*x[1] - 8.4*x[1]^3 + 2*x[1]^5 + x[2]
        grad[2] = x[1] - 8*x[2] + 16*x[2]^3
    end
    return six_hump_camel(x)
end

## optimizers for local phase of MLSL
function local_search(x_init,lb,ub,dims,loc_alg,tol,testfunc)

    opt = Opt(loc_alg, dims)
    opt.lower_bounds = lb
    opt.upper_bounds = ub
    opt.xtol_rel = tol
    opt.maxeval = 1000

    opt.min_objective = testfunc

    (minf,minx,ret) = optimize(opt, x_init)
    numevals = opt.numevals # the number of function evaluations

    return minf,minx,numevals
    #println("got $minf at $minx after $numevals iterations (returned $ret)")

end

## test global optimizers from Nlopt
function glob_test(lb,ub,dims,loc_alg,tol,obj_func,glob_alg,maxvl)

    optloc = Opt(loc_alg, dims)
    optloc.lower_bounds = lb
    optloc.upper_bounds = ub
    optloc.xtol_rel = tol
    optloc.maxeval = maxvl

    optloc.min_objective = obj_func

    opt = Opt(glob_alg, dims)
    opt.lower_bounds = lb
    opt.upper_bounds = ub
    opt.xtol_rel = tol
    opt.maxeval = maxvl

    opt.min_objective = obj_func
    opt.population = 64*dims^2 # stay consistent with my version of MLSL
    opt.local_optimizer = optloc

    (minf,minx,ret) = optimize(opt,ones(dims))
    numevals = opt.numevals # the number of function evaluations

    return minf,minx,numevals

end

# sort the N Sobol points by objective function value and slice alpha*N points
function sort_and_reduce(pts,Npts,func,alpha)
    func_val_pts = Array{Float64, 1}(undef,Npts)
    for iter in 1:Npts
        func_val_pts[iter] = func(pts[iter,:])
    end
    perm = sortperm(func_val_pts)

    permed_pts = copy(pts)
    for iter in 1:Npts
        permed_pts[iter,:] = pts[perm[iter],:]
    end

    return permed_pts[1:round(Int64,alpha*Npts),:], func_val_pts[perm][1:round(Int64,alpha*Npts),:], round(Int64,alpha*Npts)

end

# return the critical distance
function distance_metric(k,n_dims,Nr,sigma,omega_n,lb,ub)
    return (((abs(prod(ub - lb)))/omega_n)*(sigma*log(k*Nr))/(k*Nr))^(1/n_dims)
end

# return TRUE if the euclidean distance between two points is smaller
# return FALSE if greater
function is_close_euclidean(pt1,pt2,crit_dist)
    if norm(pt1 - pt2) >= crit_dist
        return false
    else
        return true
    end
end

# check if this local min has already been found and is it the set arr
function check_if_same_min(arr, f_cand)
    return f_cand in arr
end

# Basically just test a pure random search
function PRS(Npts,Dims,objfunc,lb,ub,loc_alg,tol,testfunc)
    func_evals = 0 # return total number of objective function evaluations
    holder = Array{Float64, 2}(undef,Npts,Dims+1)
    sobol_vect = gen_sobol(Npts, Dims,lb,ub)
    for iter in 1:Npts
        x_init = sobol_vect[iter,:]

        # perform local search on Sobol pt.
        holder[iter,1],holder[iter,2:end],loc_func_evals = local_search(x_init,lb,ub,Dims,loc_alg,tol,testfunc)

        func_evals += loc_func_evals
    end

    glob_min = argmin(holder[:,1])

    return holder[glob_min,1], holder[glob_min,2:end], func_evals
end

function MLSL(N_dims,num_objective_points,bb_lb,bb_ub,alpha_factor,ls_func,iter_MAX,sigma,loc_alg,tol,testfuncc)

    #### Set some important parameters ###
    omega_n = (pi^(N_dims/2))/(gamma(1+(N_dims/2))) # precompute omega factor for crit dist
    ###  ###  ###  ###  ###  ###  ###  ###

    W = 0
    N_evals = 0
    W_exp = 0
    K = 0

    ### generate Sobol objective points
    points_array = gen_sobol(num_objective_points, N_dims,bb_lb,bb_ub)
    reduced_pts,reduced_fvals,reduced_num_pts = sort_and_reduce(points_array,num_objective_points,ls_func,alpha_factor)

    ### generate a (1,Nr) array of cluster assignments and keep updating
    ### an index val of 0 means that no cluster has yet to be assigned
    clusters = zeros(Int8, reduced_num_pts)

    ### initialize a dynamic array of current local minima
    local_mins = Array{Float64}(undef,1)
    local_min_locs = Array{Float64,2}(undef,1,N_dims)

    ### initialize a dynamic array of errors and function evaluation numbers
    # return total number of objective function evaluations:
    # NOTE that 'N_sobol_pts' func vals have already been evaluated during set reduction
    func_evals = num_objective_points

    while (K < iter_MAX) && (W_exp < W + 0.5)

        K += 1
        crit_dist = distance_metric(K,N_dims,reduced_num_pts,sigma,omega_n,bb_lb,bb_ub) # calculate critical dist for iteration

        for i = 1:reduced_num_pts
            if clusters[i] == 0
                if W == 0 ## BASE CASE
                    lm,loc,fevals = local_search(reduced_pts[i,:],bb_lb,bb_ub,N_dims,loc_alg,tol,testfuncc)
                    func_evals += fevals
                    N_evals += 1
                    local_mins[1] = lm
                    local_min_locs[1,:] = loc
                    clusters[i] = 1   # assign first cluster
                    W += 1
                else
                    ### sort through all cluster-assigned points in Nr and see if
                    ### canditate falls in cluster via r_crit.
                    for clus_num = 1:W
                        for (ind, val) in enumerate(findall(x -> x==clus_num,clusters))
                            if (is_close_euclidean(reduced_pts[i,:],reduced_pts[val,:],crit_dist) == true) && (reduced_fvals[i] >= reduced_fvals[val])
                                clusters[i] = clus_num
                                break
                            end
                        end
                    end

                end

                # if no cluster assignment found
                if clusters[i] == 0
                    lm,loc,fevals = local_search(reduced_pts[i,:],bb_lb,bb_ub,N_dims,loc_alg,tol,testfuncc)
                    func_evals += fevals
                    N_evals += 1
                    local_mins = [local_mins;lm]
                    local_min_locs = [local_min_locs;loc']
                    clusters[i] = 1   # assign next cluster
                    if !check_if_same_min(local_mins, lm) # if minimum already in set, so not increment stopval
                        W += 1
                    end
                end

            else
                continue
            end

        end
        W_exp = (W*(N_evals-1))/(N_evals-W-2)
    end

    glob_min = argmin(local_mins)
    return local_mins[glob_min], local_min_locs[glob_min,:], func_evals
end


module PDiscRefFEsTests

using Test
using Gridap.TensorValues
using Gridap.ReferenceFEs

T = VectorValue{2,Float64}
reffe = LagrangianRefFE(T,QUAD,2,space=:P)
@test Conformity(reffe) == L2Conformity()
test_lagrangian_reference_fe(reffe)
@test is_n_cube(get_polytope(reffe))

T = VectorValue{2,Float64}
reffe = LagrangianRefFE(T,HEX,3,space=:P)
@test Conformity(reffe) == L2Conformity()
test_lagrangian_reference_fe(reffe)
@test is_n_cube(get_polytope(reffe))

#using Gridap.Visualization
#writevtk(reffe,"reffe")

end # module


test_orthogonality_orthonormality(dict::Dictionary, measure::BasisFunctions.Measure=measure(dict)) =
    test_orthogonality_orthonormality(dict, isorthogonal(dict, measure), isorthonormal(dict, measure), measure)

function test_orthogonality_orthonormality(dict::Dictionary, isorthog::Bool, isorthonorm::Bool, measure::BasisFunctions.Measure=measure(dict); atol=1e-6,rtol=1e-6, opts...)
    isorthonormal(dict, measure) && (@test isorthogonal(dict, measure))
    @test isorthogonal(dict, measure) == isorthog
    @test isorthonormal(dict, measure) == isorthonorm

    G = gram(dict, measure)
    @test eltype(G) == coefficienttype(dict)
    M = Matrix(BasisFunctions.default_gram(dict, measure; warnslow=false, atol=atol,rtol=rtol,opts...))

    if isorthog
        @test isdiag(G)
    else
        @test !isdiag(G)
        @test norm(M - Diagonal(diag(M))) > 1e-3 || any(abs.(diag(M)).< 1e-5)
    end
    if isorthonorm
        @test BasisFunctions.isidentity(G)
    else
        @test !BasisFunctions.isidentity(G)
        @test !(diag(M) ≈ Ones(length(dict)))
    end

    @test norm(M - Matrix(G)) < 1e-5
end


using PtFEM

data = Dict(
  # Beam(ndim, nst, nxe, nip, direction, finite_element(nod, nodof), axisymmet
  :struc_el => Beam(3, 1, 4, 1, :x, Line(2, 3), false),
  :properties => [
    4.0e6 1.0e6 0.3e6 0.3e6],
  :x_coords => [0.0, 1.0, 2.0, 3.0, 4.0],
  :y_coords => zeros(5),
  :z_coords => zeros(5),
  :gamma => [0.0, 0.0, 90.0],
  :g_num => [
    1 2 3 4;
    2 3 4 5],
  :support => [
    (1, [0 0 0])
    ],
  :loaded_nodes => [
    (5, [0.0 -100.0 0.0])]
)

data |> display
println()

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

display(dis_df)
println()
display(fm_df)


using CMAES, Optim, LinearAlgebra
using Test

f(x) = sum(abs2.(x))
D = 5

@testset "FullCovMatrix" begin

    T = CMAES.FullCovMatrix
    mfit = optimize(
       f,10*rand(D).-5,CMAES.CMA(T,5;dimension=D),
       Optim.Options(f_calls_limit=5000,store_trace=true,extended_trace=true),
    )
    @test Optim.minimum(mfit) < 1e-12
end

@testset "BD_CovMatrix" begin

    T = CMAES.BD_CovMatrix
    mfit = optimize(
       f,10*rand(D).-5,CMAES.CMA(T,5;dimension=D),
       Optim.Options(f_calls_limit=5000,store_trace=true,extended_trace=true),
    )
    @test Optim.minimum(mfit) < 1e-12
end

@testset "BD_CovMatrix+Diagonal" begin

    mfit = optimize(
       f,10*rand(D).-5,CMAES.CMA{Diagonal,CMAES.BD_CovMatrix}(9,5),
       Optim.Options(f_calls_limit=5000,store_trace=true,extended_trace=true),
    )
    @test Optim.minimum(mfit) < 1e-12
end