# To do: pass the storages f, ∆X, ∆F, Q, β.  Also, think about ways to reduce this memory.

# To use andersonaccel!, implement anderson_SALT! that accepts SALTSol as an initial guess
# and g! that takes SALTSol and returns SALTSol.  anderson_SALT! should create a version of g!
# that takes and returns vectors using CatViews.

# I will need to implement `reinterpret` for PETSc vectors to view complex PETSc vectors as
# a real PETSc vector.
function lsol2rvec(lsol::LasingSol)
    m_active = lsol.m_active
    ψr = reinterpret.(Ref(Float), @view(lsol.ψ[lsol.m_active]))
    # ψr = lsol.ψ[lsol.m_active]  # complex version

    # # For the Anderson in the complex domain
    # ωa² = lsol.ω[lsol.m_active] .+ im .* lsol.a²[lsol.m_active]
    # return CatView(ωa², lsol.ψ[lsol.m_active]...)

    # For the Anderson in the real domain
    return CatView(@view(lsol.ω[lsol.m_active]), @view(lsol.a²[lsol.m_active]), ψr...)

    # return CatView(ψr...)
    # return CatView(lsol.ω[lsol.m_active], lsol.a²[lsol.m_active])
end

# Create the fixed-point function to use in the Anderson acceleration.
function create_fpfun(lsol::LasingSol, lvar::LasingVar, gp::GainProfile)
    # When x is created from lsol by lsol2rvec, x points to the memory of lsol, so the update in
    # lsol is automatically reflected in x.
    function fpfun!(y, x)
        update_lsol!(lsol, lvar, gp)  # x is updated to g(xₖ) (but this x is not xₖ₊₁)

        # Uncomment the following two lines for the Anderson in the complex domain.
        # @info "This is complex Anderson."
        # y[1:length(lsol.m_active)] .= lsol.ω[lsol.m_active] .+ im .* lsol.a²[lsol.m_active]

        return nothing
    end

    return fpfun!
end

function create_normfun(lsol::LasingSol, lvar::LasingVar, gp::GainProfile, εc::AbsVecComplexF;
                        verbose::Bool=true, msgprefix::String="    ")
    function normfun!(x::AbsVec{<:Number}, k::Integer=0, lleq₀::Real=Inf)
        init_lvar!(lvar, lsol, gp, εc)
        lleq = norm_leq(lsol, lvar, gp)
        if verbose
            k==0 ? println(msgprefix * "Initial residual norm: ‖leq₀‖ = $lleq") :
                   println(msgprefix * "k = $k: ‖leq‖/‖leq₀‖ = $(lleq/lleq₀)")
        end

        return lleq
    end

    return normfun!
end

# Solve the fixed-point equation g(x) = x to given relative and absolute tolerances,
# returning the solution x, via Anderson acceleration of a fixed-point iteration starting at
# a given initial x (which should be of the correct type to hold the result).
#
# x should be set x = lsol2rvec(lsol) outside.
#
# This implementation operates in-place as much as possible.
# This function will be removed if an external implementation of the Anderson acceleration is used.
function anderson!(g!::Any,  # called by g!(y,x); overwrites a given output vector y with g(x)
                   x::AbsVec{T};  # initial guess; overwritten at each iteration step (note g! itself does not overwrite x)
                   norm=(x::AbsVec{T}, k::Integer, l₀::Real)->norm(x), # stopping criterion: norm(Δx) ≤ atol + rtol*norm(x)
                   m::Integer=2, # number of additional x's kept in algorithm; m = 0 means unaccelerated iteration
                   rtol::Real=τᵣ₀,  # relative tolerance; consider using τᵣ₀
                   atol::Real=τₐ₀,  # absolute tolerance
                   maxit::Int=typemax(Int)) where {T<:Number}  # number of maximum number of iteration steps
    m ≥ 0 || @error "m = $m should be ≥ 0."
    rtol ≥ 0 || @error "rtol = $rtol should be ≥ 0."
    atol ≥ 0 || @error "atol = $atol should be ≥ 0."
    maxit ≥ 0 || @error "maxit = $maxit should be ≥ 0."

    k = 0
    l₀ = norm(x)
    l₀ ≤ atol && return k, l₀, l₀  # lsol.m_active = [] falls to this as well

    τ = max(rtol*l₀, atol)

    n = length(x)
    m ≤ n || @error "m = $m should be > length(x) = $n."

    # Storing xold is needed only for the m ≠ 0 case, so executing the following block only
    # for m ≠ 0 would have saved 1 allocation.  However, putting the following block here
    # increase code readability, because doing update_lsol! right after norm_leq is an idiom.
    # m ≠ 0 is rarely used anyway.
    xold = Vector{T}(undef, n)
    xold .= x  # xold = x₀
    g!(x, xold)  # x is updated to x₁ = g(x₀)
    # Note we need at least two x's to perform the m ≠ 0 Anderson acceleration.

    if m == 0 # simple fixed-point iteration (no memory)
        while (k+=1) ≤ maxit
            l = norm(x, k, l₀)
            l ≤ τ && break

            # Evaluate g(xₖ).
            xold .= x  # xold = xₖ
            g!(x, xold)
        end
    else  # m ≠ 0
        # Pre-allocate all of the arrays we will need.  The goal is to allocate once and re-use
        # the storage during the iteration by operating in-place.
        f = Vector{T}(undef, n)
        ∆X = Matrix{T}(undef, n, m)  # columns: ∆x's
        ∆F = Matrix{T}(undef, n, m)  # columns: ∆f's
        Q = Matrix{T}(undef, n, m)  # space for QR factorization
        β = Vector{T}(undef, max(n,m))  # not m, in order to store RHS vector (f: length-n) and overwrite in-place via ldiv! (max length m)

        # Find x₁ = g(x₀): we need at least two x's to perform the Anderson acceleration.
        col = 1
        for i = 1:n
            f[i] = x[i] - xold[i]  # f(x₀) = g(x₀) - x₀
            ∆X[i,col] = f[i]  # ∆x₀ = x₁ - x₀
        end
        # Note that in the subsequent iteration, xₖ₊₁ is not necessarily g(xₖ), so
        # ∆xₖ = xₖ₊₁ - xₖ is not necessarily f(xₖ) = g(xₖ) - xₖ.

        # Perform subsequent iterations.
        #
        # (Assumption) The following quantities are known at the beginning of every iteration:
        # - xₖ
        # - f(xₖ₋₁)
        # - ∆xₖ₋₁
        # These quantities should be updated at the end of the for loop to satisfy the
        # assumption at every iteration.
        #
        # Using these quantities, in each iteration
        # - g(xₖ) is calculated,
        # - with which f(xₖ) = g(xₖ) - xₖ is calculated,
        # - with which ∆fₖ₋₁ = f(xₖ) - f(xₖ₋₁) is calculated.
        #
        # Then we know ∆xₖ₋₁ and ∆fₖ₋₁, which are needed for the Anderson acceleration.
        while (k+=1) ≤ maxit
            l = norm(x, k, l₀)
            l ≤ τ && break

            # Evaluate g(xₖ).
            xold .= x  # xold = xₖ
            g!(x, xold)

            # Prepare the least squares problem.
            for i = 1:n
                β[i] = x[i] - xold[i]  # temporary storage for f(xₖ) = g(xₖ) - xₖ
                ∆F[i,col] = β[i] - f[i]  # ∆fₖ₋₁ = f(xₖ) - f(xₖ₋₁)
                f[i] = β[i]  # f(xₖ)
            end

            # Solve the least squares problem.
            #
            # The code overwrites β with ∆F′ \ f.  (Note that β stores f at this point.)
            # Only first m′ entries of the solution β are meaningful, because they are the
            # entries of the least squares solution.
            #
            # In the following, QR′ and QR have different types, so we introduce separate
            # variables for type stability.
            if k < m
                # Construct subarrays to work in-place on a subset of the columns.
                Q′, ∆F′ = @views Q[:,1:k], ∆F[:,1:k]
                QR′ = qr!(copyto!(Q′, ∆F′), ColumnNorm())
                ldiv!(QR′, β)
            else
                QR = qr!(copyto!(Q, ∆F), ColumnNorm())
                ldiv!(QR, β)
            end

            # Replace the columns of ∆X (and ∆F in the next iteration) with the new data
            # in-place.  Rather than always appending the new data in the last column, cycle
            # through the m columns periodically.
            col = mod1(col+1, m)  # next column of ∆X and ∆F to update
            for i = 1:n
                # Update x with xₖ₊₁ = xₖ + fₖ - (∆X′ + ∆F′)*β′.
                # Note that x is already g(xₖ) = xₖ + f(xₖ)
                for j = 1:min(m,k)
                    x[i] -= (∆X[i,j] + ∆F[i,j]) * β[j]
                end

                ∆X[i,col] = x[i] - xold[i]  # ∆xₖ = xₖ₊₁ - xₖ
            end
        end  # for k = 1:maxit-1
    end  # if m == 0

    if k == maxit  # iteration terminated by consuming maxit steps
        l = norm(x, k, l₀)
        @warn "Anderson reached maxit = $maxit and didn't converge."
    end

    return k, l, l₀
end


@testset "GGP read" begin
	# Simple data loading
	ggp1 = readggp(joinpath(dirname(@__DIR__),"test","input","readggp_data.ggp"),nanval=9999.999)
	@test ggp1[:datetime][1] == DateTime(2017,08,26)
	@test ggp1[:datetime][end] == DateTime(2017,09,03,4,0,0)
	for i in [9,10,12]
		@test isnan(ggp1[:column2][i])
	end
	@test sum(ggp1[:column1]) ≈ 5.
	@test sum(filter(!isnan,ggp1[:column2])) ≈ 20.

	# Apply offset while loading data
	ggp2 = readggp(joinpath(dirname(@__DIR__),"test","input","readggp_data.ggp"),
		nanval=9999.999,offset=true)
	@test ggp2[:datetime][1] == DateTime(2017,08,26)
	@test ggp2[:datetime][end] == DateTime(2017,09,03,4,0,0)
	for i in [9,10,12]
		@test isnan(ggp2[:column2][i])
	end
	@test sum(ggp2[:column1]) ≈ 55.
	@test sum(filter(!isnan,ggp2[:column2])) ≈ 100.

	# Read header only
	ggphead = readggp(joinpath(dirname(@__DIR__),"test","input","readggp_data.ggp"),
		what="header")
	@test size(ggphead) == (2,)

	# Read EOSTloading (Pre)ETERNA file
	ggp3 = readggp(joinpath(dirname(@__DIR__),"test","input","eost_data.rot"))
	@test names(ggp3) == [:datetime,:column1,:column2,:column3,:column4,:column5]
	ggp3m = readdlm(joinpath(dirname(@__DIR__),"test","input","eost_data.rot"),skipstart=14)
	ggp3m = ggp3m[1:end-1,:];
	@test ggp3[:datetime] == DateTime.(string.(ggp3m[:,1]),"yyyymmdd")
	for i in 3:size(ggp3m,2)
		@test sum(ggp3[Symbol("column",i-2)]) ≈ sum(ggp3m[:,i])
	end

	# Read blockinfor
	blockinfo = readggp(joinpath(dirname(@__DIR__),"test","input","eost_data.rot"),what="blocks")
	@test blockinfo[:datetime][1] == DateTime(string(ggp3m[1,1]),"yyyymmdd")
	@test blockinfo[:startline][1] == 15
	@test blockinfo[:stopline][1] == size(ggp3m,1)+15-1 # header - 999999 line
	@test size(blockinfo) == (1,size(ggp3m,2)+1)
end

@testset "GGP write" begin
	dataout = DataFrame(pres=collect(1000.12345:1:1011.123456),
	       				datetime=collect(DateTime(2010,1,1):Dates.Hour(1):DateTime(2010,1,1,11)));
	# try writtin without optional parameters
	writeggp(dataout,joinpath(dirname(@__DIR__),"test","output","ggp_data_one.dat"));
	# read and test
	d = readdlm(joinpath(dirname(@__DIR__),"test","output","ggp_data_one.dat"),skipstart=3)
	for i = 1:12
		@test d[i,1] == 20100101
		@test d[i,3] ≈ round(dataout[:pres][i]*100)/100
		@test d[i,2] ≈ (i-1)*1e+4
	end
	@test d[end,1] == 99999999

	# Try full output
	dataout[:grav] =collect(900.123456:-3:(900.123456-11*3));
	# set units using the same keywords as in the dataframe
	units = Dict(:grav=>"V",:pres=>"hPa")
	# Set header. All entries with the excetpion of "freetext" will be formatted
	header = Dict("Filename"=>"file.data",
	              "Station"=>"Wettzell",
	              "Instrument"=>"iGrav",
	              "N. Latitude (deg)"=>49.14354,
	              "E. Longitude (deg)"=>12.87866,
	              "Elevation MSL (m)"=>613.7+1.05,
	              "Author"=>"Name (name@gfz-potsdam.de)",
				  "freetext"=>"Line without formatting\nMaximum 80 characters per line");
	block = Dict("start"=>[DateTime(2010,1,1,09,0,0)],
				"offset"=>[10.1234 20.1234], # one row per block. 2 values per row = number channels
	            "header"=>["iGrav006" 1.0 1.0 0.0 3])
	fileout = joinpath(dirname(@__DIR__),"test","output","ggp_data_two.dat");
	writeggp(dataout,fileout,
				units=units,header=header,decimal=[1,3],
				blockinfo=block,channels=[:grav,:pres])
	# test
	open(fileout,"r") do fid
		row = ""
		for i in 1:10
			row = readline(fid);
		end
		@test row == "yyyymmdd hhmmss grav(V) pres(hPa)";
	end
	b = readdlm(fileout,skipstart=12)
	@test b[1,1] == 77777777
	@test b[2,1] == 20100101
	@test b[end-1,1] == 20100101
	@test b[end-1,2] == 110000
	@test b[3,2] == 010000
	@test b[3,3] ≈ round(dataout[:grav][2]*10)/10
	@test b[end-2,4] ≈ round(dataout[:pres][end-1]*1000)/1000
	@test b[end-4,2] ≈ round(block["offset"][1]*10)/10
	@test b[end-4,3] ≈ round(block["offset"][2]*1000)/1000
	@test b[end-5,1] == block["header"][1]
	@test b[end-5,2:end] == block["header"][2:end]
end

@testset "GGP data2blocks" begin
	datawrite = DataFrame(pres=collect(1000.:1:1011.),
					grav=collect(900.:-3:(900. -11*3)),
					datetime=collect(DateTime(2010,1,1):Dates.Hour(1):DateTime(2010,1,1,11)));
	datawrite[:grav][[3,6]] .= NaN;
	datawrite[:pres][7] = NaN;
	dataout,block = ggpdata2blocks(datawrite;channels=[])
	@test filter(!isnan,datawrite[:grav]+datawrite[:pres]) ≈ dataout[:grav]+dataout[:pres]
	@test block["start"] == datawrite[:datetime][[3,6]]
	@test block["offset"] == zeros(Float64,2,2)

	#
	dataout2,block2 = ggpdata2blocks(dataout;channels=[:pres])
	@test dataout2[:datetime] == dataout[:datetime]
	@test dataout2[:pres] == dataout[:pres]
	@test !haskey(dataout2,:grav)
	@test isempty(block2)
end


#############################################################################################
#                             COPY TO BLOCK MATRICES                                       #
#############################################################################################

"Copy vectors from a vector of vectors to a concatenated vector"
function copy_inds(dest, src, inds)
    for i in eachindex(inds)
        dest[inds[i]] = src[i]
    end
end

function copyback_inds(dest, src, inds)
    for i in eachindex(inds)
        src[i] = dest[inds[i]]
    end
end

"Copy constraints to a single concatenated vector"
function copy_constraints!(d, solver::ConstrainedSolver)
    conSet = get_constraints(solver)
    for (i,con) in enumerate(conSet.errvals)
        copy_inds(d, con.vals, solver.con_inds[i])
    end
    return nothing
end

function copy_multipliers!(λ, solver::ConstrainedSolver)
    conSet = get_constraints(solver)
    for (i,con) in enumerate(conSet.errvals)
        copy_inds(λ, con.λ, solver.con_inds[i])
    end
    return nothing
end

function copyback_multipliers!(λ, solver::ConstrainedSolver)
    conSet = get_constraints(solver)
    for (i,con) in enumerate(conSet.errvals)
        copyback_inds(λ, con.λ, solver.con_inds[i])
    end
    return nothing
end

"Copy active set to a single concatenated vector"
function copy_active_set!(a, solver::ConstrainedSolver)
    conSet = get_constraints(solver)
    for i = 1:length(conSet.errvals)
        copy_inds(solver.active_set, conSet.convals[i].active, solver.con_inds[i])
    end
end

"""Copy constraint Jacobians to given indices in a sparse array
Dispatches on bandedness of the constraint"""
function copy_jacobian!(D, con::ALConVal{<:TO.StageConstraint}, cinds, xinds, uinds) where T
    for (i,k) in enumerate(con.inds)
        zind = [xinds[k]; uinds[k]]
        D[cinds[i], zind] .= con.jac[i]
    end
end

function copy_jacobian!(D, con::ALConVal{<:TO.StateConstraint}, cinds, xinds, uinds) where T
    for (i,k) in enumerate(con.inds)
        D[cinds[i], xinds[k]] .= con.jac[i]
    end
end

function copy_jacobian!(D, con::ALConVal{<:TO.ControlConstraint}, cinds, xinds, uinds) where T
    for (i,k) in enumerate(con.inds)
        D[cinds[i], uinds[k]] .= con.jac[i]
    end
end

# function copy_jacobian!(D, con::ConstraintVals{T,Coupled}, cinds, xinds, uinds) where T
#     for (i,k) in enumerate(con.inds)
#         zind = [xinds[k]; uinds[k]; xinds[k+1]; uinds[k+1]]
#         D[cinds[i], zind] .= con.∇c[i]
#     end
# end

function copy_jacobian!(D, con::TO.AbstractConstraintValues{<:DynamicsConstraint{<:Explicit}},
		cinds, xinds, uinds)
	N = length(xinds)
    for (i,k) in enumerate(con.inds)
        zind = [xinds[k]; uinds[k]; xinds[k+1]]
		zind1 = [xinds[k]; uinds[k]]
		zind2 = [xinds[k+1]; uinds[k+1]]
        D[cinds[i], zind1] .= con.jac[i,1]
		D[cinds[i], xinds[k+1]] .= con.jac[i,2][:,xinds[1]]
		# D[cinds[i], zind2] .= con.jac[i,2]
    end
end

"Copy all constraint Jacobians to a sparse matrix"
function copy_jacobians!(D, solver::ConstrainedSolver)
    conSet = get_constraints(solver)
    xinds, uinds = primal_partition(solver)
    cinds = solver.con_inds

    for i = 1:length(conSet.errvals)
        copy_jacobian!(D, conSet.convals[i], cinds[i], xinds, uinds) # TODO: switch to errvals
    end
    return nothing
end

"Copy constraint Jacobians to linear indices of a vector"
function copy_jacobian!(d::AbstractVector{<:Real}, con::TO.AbstractConstraintValues, linds)
	for (j,k) in enumerate(con.inds)
		inds = linds[j]
		d[inds] = con.jac[j]
	end
end

"Copy all constraint Jacobians to linear indices of a vector"
function copy_jacobians!(jac::AbstractVector{<:Real}, solver::ConstrainedSolver)
    conSet = get_constraints(solver)
    xinds, uinds = primal_partition(solver)
    cinds = solver.con_inds
    linds = jacobian_linear_inds(solver)

    for (i,con) in enumerate(conSet.constraints)
		copy_jacobian!(jac, con, linds[i])
    end
    return nothing
end

# function copy_gradient!(grad_f, E::Vector{<:CostExpansion}, xinds, uinds)
#     for k = 1:length(uinds)
#         grad_f[xinds[k]] = E[k].x
#         grad_f[uinds[k]] = E[k].u
#     end
#     if length(xinds) != length(uinds)
#         grad_f[xinds[end]] = E[end].x
#     end
# end


module SeisMain
    using Printf
    using Statistics
    using Distributed
    include("ReadWrite/ReadWrite.jl")
    include("Utils/Utils.jl")
end


"""
AbstractSystem

An abstract type representing a system
"""
abstract type AbstractSystem end

"""
state_dim(s::AbstractSystem)

Return the state dimension of a system
"""
state_dim(s::AbstractSystem) = s.state_dim

"""
state_set(s::AbstractSystem)

Return the state dimension of a system
"""
state_set(s::AbstractSystem) = s.X

"""
has_input(s::AbstractSystem)

Return the input condition of a system
"""
has_input(s::AbstractSystem) = false

"""
has_uncertainty(s::AbstractSystem)

Return the uncertainty condition of a system
"""
has_uncertainty(s::AbstractSystem) = false

"""
AbstractUncertainSystem

An abstract type representing an uncertain system
"""
abstract type AbstractUncertainSystem <:AbstractSystem end

"""
uncertainty_dim(s::AbstractUncertainSystem)

Return the uncertainty dimension of a system
"""
uncertainty_dim(s::AbstractUncertainSystem) = s.uncertainty_dim

"""
has_uncertainty(s::AbstractUncertainSystem)

Return the uncertainty condition of a system
"""
has_uncertainty(s::AbstractUncertainSystem) = true

"""
uncertainty_set(s::AbstractUncertainSystem)

Return the uncertainty set of a system
"""
uncertainty_set(s::AbstractUncertainSystem) = s.W

"""
AbstractControlSystem

An abstract type representing a control system
"""
abstract type AbstractControlSystem <:AbstractSystem end

"""
input_dim(s::AbstractControlSystem)

Return the input dimension of a control system
"""
input_dim(s::AbstractControlSystem) = s.input_dim

"""
has_input(s::AbstractControlSystem)

Return the input condition of a system
"""
has_input(s::AbstractControlSystem) = true

"""
input_set(s::AbstractControlSystem)

Return the input constraint of a control system
"""
input_set(s::AbstractControlSystem) = s.U

"""
AbstractUncertainControlSystem

An abstract type representing a control system with uncertainties
"""
abstract type AbstractUncertainControlSystem <:AbstractUncertainSystem end

"""
input_dim(s::AbstractUncertainControlSystem)

Return the input dimension of a control system
"""
input_dim(s::AbstractUncertainControlSystem) = s.input_dim

"""
has_input(s::AbstractUncertainControlSystem)

Return the input condition of a system
"""
has_input(s::AbstractUncertainControlSystem) = true

"""
input_set(s::AbstractUncertainControlSystem)

Return the input constraint of a control system
"""
input_set(s::AbstractUncertainControlSystem) = s.U

"""
AbstractSolver

An abstract solver object
"""
abstract type AbstractSolver end

"""
AbstractOptions

An abstract options object
"""
abstract type AbstractOptions end

"""
AbstractSolution

An abstract solution object
"""
abstract type AbstractSolution end


include("init-namedarrays.jl")

print("hcat/vcat, ")

letters = [string(Char(96+i)) for i=1:26]

## vectors
## same names
m = @inferred NamedArray(rand(10), (letters[1:10],))
m2 = @inferred NamedArray(rand(10), (letters[1:10],))
mm = hcat(m, m2)
@test mm.array == hcat(m.array, m2.array)
@test namesanddim(mm,1) == namesanddim(m,1)

mm = @inferred vcat(m, m2)
@test mm.array == vcat(m.array, m2.array)
@test names(mm, 1) == [string(i) for i in 1:length(mm)]

## different names
m2 = @inferred NamedArray(rand(10), (letters[11:20],))
mm = @inferred hcat(m, m2)
@test mm.array == hcat(m.array, m2.array)
@test names(mm, 1) != names(m2, 1)

mm = @inferred vcat(m, m2)
@test mm.array == vcat(m.array, m2.array)
@test names(mm, 1) == vcat(names(m, 1), names(m2, 1))

## matrix
## same names
m = @inferred NamedArray(rand(10, 10), (letters[1:10], letters[11:20]))
m2 = @inferred NamedArray(rand(10, 10), (letters[1:10], letters[11:20]))
mm = @inferred hcat(m, m2)
@test mm.array == hcat(m.array, m2.array)
@test namesanddim(mm,1) == namesanddim(m,1)

mm = @inferred vcat(m, m2)
@test mm.array == vcat(m.array, m2.array)
@test names(mm, 1) == [string(i) for i in 1:size(mm, 1)]
@test names(mm, 2) == names(m, 2)

## different names
m2 = @inferred NamedArray(rand(10, 10), (letters[11:20], letters[1:10]))
mm = @inferred hcat(m, m2)
@test mm.array == hcat(m.array, m2.array)
@test names(mm, 1) != names(m2, 1)

mm = @inferred vcat(m, m2)
@test mm.array == vcat(m.array, m2.array)
## @test names(mm, 1) == vcat(names(m, 1), names(m2, 1))
@test names(mm, 2) == [string(i) for i in 1:size(mm, 2)]


## ===================================================================================================================
##  Consolidate by first changing to common functions of csmc
## ===================================================================================================================
"""
    $SIGNATURES

Calculate the full upward Chapman-Kolmogorov transit integral solution approximation (i.e. upsolve).

Notes
- State machine function nr. 8g
- Assumes LIKELIHOODMESSAGE factors are in csmc.cliqSubFg but does not remove them.
- TODO: Make multi-core

DevNotes
- NEEDS DFG v0.8.1, see IIF #760
- temperory consolidation function
"""
function __doCliqUpSolveInitialized!(csmc::CliqStateMachineContainer)

  # check if all cliq vars have been initialized so that full inference can occur on clique
  status = getCliqueStatus(csmc.cliq)
  infocsm(csmc, "8g, doCliqUpSolveInitialized_StateMachine -- clique status = $(status)")
  logCSM(csmc, "8g, doCliqUpSolveInitialized_StateMachine -- clique status = $(status)")

  setCliqueDrawColor!(csmc.cliq, "red")

  opt = getSolverParams(csmc.cliqSubFg)
  # get Dict{Symbol, TreeBelief} of all updated variables in csmc.cliqSubFg
  retdict = approxCliqMarginalUp!(csmc, iters=opt.gibbsIters, logger=csmc.logger)
  # retdict = approxCliqMarginalUp!(csmc, LikelihoodMessage[]; iters=4, logger=csmc.logger)
  logCSM(csmc, "aproxCliqMarginalUp!"; retdict=retdict)

  updateFGBT!(csmc.cliqSubFg, csmc.cliq, retdict, dbg=getSolverParams(csmc.cliqSubFg).dbg, logger=csmc.logger) # urt

  # set clique color accordingly, using local memory
  # setCliqueDrawColor!(csmc.cliq, isCliqFullDim(csmc.cliqSubFg, csmc.cliq) ? "pink" : "tomato1")

  # notify of results (part of #459 consolidation effort)
  getCliqueData(csmc.cliq).upsolved = true

  return nothing

end

## ===================================================================================================================
##  CSM logging functions
## ===================================================================================================================
# using Serialization

"""
    $SIGNATURES

Internal helper function to save a dfg object to LogPath during clique state machine operations.

Notes
- will only save dfg object if `opts.dbg=true`

Related

saveDFG, loadDFG!, loadDFG
"""
function _dbgCSMSaveSubFG(csmc::CliqStateMachineContainer, filename::String)

  opt = getSolverParams(csmc.cliqSubFg)

  if opt.dbg
    folder::String=joinpath(opt.logpath,"logs","cliq$(getId(csmc.cliq))")
    if !ispath(folder)
      mkpath(folder)
    end
    # NOTE there was a bug using saveDFG, so used serialize, left for future use
    # serialize(joinpath(folder, filename), csmc.cliqSubFg)
    DFG.saveDFG(csmc.cliqSubFg, joinpath(folder, filename))
    drawGraph(csmc.cliqSubFg, show=false, filepath=joinpath(folder, "$(filename).pdf"))
  end

  opt.dbg

end

"""
    $SIGNATURES

Specialized info logger print function to show clique state machine information
in a standardized form.
"""
function infocsm(csmc::CliqStateMachineContainer, str::A) where {A <: AbstractString}

  tm = string(Dates.now())
  tmt = split(tm, 'T')[end]

  lbl = getLabel(csmc.cliq)
  lbl1 = split(lbl,',')[1]
  cliqst = getCliqueStatus(csmc.cliq)

  with_logger(csmc.logger) do
    @info "$tmt | $(getId(csmc.cliq))---$lbl1 @ $(cliqst) | "*str
  end
  flush(csmc.logger.stream)
  nothing
end

"""
    $SIGNATURES
Helper function to log a message at a specific level to a clique identified by `csm_i` where i = cliq.id
Notes:
- Related to infocsm.
- Different approach to logging that uses the build in logging functionality to provide more flexibility.
- Can be used with LoggingExtras.jl
"""
function logCSM(csmc, msg::String; loglevel::Logging.LogLevel=Logging.Debug, maxlog=nothing, kwargs...)
  csmc.enableLogging ? nothing : (return nothing)
  #Debug = -1000
  #Info = 0
  #Warn = 1000
  #Error = 2000
  @logmsg(loglevel,
          msg,
          _module=begin
            bt = backtrace()
            funcsym=(:logCSM, Symbol("logCSM##kw")) #always use the calling function of logCSM
            frame, caller = Base.firstcaller(bt, funcsym)
            # TODO: Is it reasonable to attribute callers without linfo to Core?
            caller.linfo isa Core.MethodInstance ? caller.linfo.def.module : Core
          end,
          _file=String(caller.file),
          _line=caller.line,
          _id=(frame,funcsym),
          # caller=caller,
          # st4 = stacktrace()[4],
          _group = Symbol("csm_$(csmc.cliq.id)"),
          maxlog=maxlog,
          kwargs...)

  return nothing
end

## ===================================================================================================================
## CSM Error functions
## ===================================================================================================================

function putErrorDown(csmc::CliqStateMachineContainer)
  setCliqueDrawColor!(csmc.cliq, "red")
  @sync for e in getEdgesChildren(csmc.tree, csmc.cliq)
    logCSM(csmc, "CSM clique $(csmc.cliq.id): propagate down error on edge $(e)")
    @async putBeliefMessageDown!(csmc.tree, e, LikelihoodMessage(status=ERROR_STATUS))
  end
  logCSM(csmc, "CSM clique $(csmc.cliq.id): Exit with error state", loglevel=Logging.Error)
  return nothing
end

function putErrorUp(csmc::CliqStateMachineContainer)
  setCliqueDrawColor!(csmc.cliq, "red")
  for e in getEdgesParent(csmc.tree, csmc.cliq)
    logCSM(csmc, "CSM clique, $(csmc.cliq.id): propagate up error on edge $(e)")
    putBeliefMessageUp!(csmc.tree, e, LikelihoodMessage(status=ERROR_STATUS))
  end
  return nothing
end

## ===================================================================================================================
## CSM Monitor functions
## ===================================================================================================================

"""
    $SIGNATURES
Monitor CSM tasks for failures and propagate error to the other CSMs to cleanly exit.
"""
function monitorCSMs(tree, alltasks; forceIntExc::Bool=false)
  task = @async begin
    while true
      all(istaskdone.(alltasks)) && (@info "monitorCSMs: all tasks done"; break)
      for (i,t) in enumerate(alltasks)
        if istaskfailed(t)
          if forceIntExc
            @error "Task $i failed, sending InterruptExceptions to all running CSM tasks"
            throwIntExcToAllTasks(alltasks)
            @debug "done with throwIntExcToAllTasks"
          else
            @error "Task $i failed, sending error to all cliques"
            bruteForcePushErrorCSM(tree)
            # for tree.messageChannels
            @info "All cliques should have exited"
          end
        end
      end
      sleep(1)
    end
  end
  return task
end

function throwIntExcToAllTasks(alltasks)
  for (i,t) in enumerate(alltasks)
    if !istaskdone(alltasks[i])
      @debug "Sending InterruptExceptions to CSM task $i"
      schedule(alltasks[i], InterruptException(), error=true)
      @debug "InterruptExceptions CSM task $i"
    end
  end
  return nothing
end

function bruteForcePushErrorCSM(tree::AbstractBayesTree)
    errMsg = LikelihoodMessage(status=ERROR_STATUS)
    for (i, ch) in getMessageChannels(tree)
        if isready(ch.upMsg)
            take!(ch.upMsg)
        else
            @debug("Up edge $i", ch.upMsg)
            @async put!(ch.upMsg, errMsg)
        end
        if isready(ch.downMsg)
            take!(ch.downMsg)
        else
            @debug("Down edge $i", ch.downMsg)
            @async put!(ch.downMsg, errMsg)
        end

    end

    for (i, ch) in getMessageChannels(tree)

        while isready(ch.upMsg)
            @debug "cleanup take on $i up"
            take!(ch.upMsg)
        end
        while isready(ch.downMsg)
            @debug "cleanup take on $i down"
            take!(ch.downMsg)
        end

    end

end

## ===================================================================================================================
## CSM Clique Functions
## ===================================================================================================================

"""
    $SIGNATURES

Set all up `upsolved` and `downsolved` cliq data flags `to::Bool=false`.
"""
function setAllSolveFlags!(treel::AbstractBayesTree, to::Bool=false)::Nothing
  for (id, cliq) in getCliques(treel)
    cliqdata = getCliqueData(cliq)
    setCliqueStatus!(cliqdata, NULL)
    cliqdata.upsolved = to
    cliqdata.downsolved = to
  end
  nothing
end

"""
    $SIGNATURES

Return true or false depending on whether the tree has been fully initialized/solved/marginalized.
"""
function isTreeSolved(treel::AbstractBayesTree; skipinitialized::Bool=false)
  acclist = CliqStatus[UPSOLVED, DOWNSOLVED, MARGINALIZED]
  skipinitialized ? nothing : push!(acclist, INITIALIZED)
  for (clid, cliq) in getCliques(treel)
    if !(getCliqueStatus(cliq) in acclist)
      return false
    end
  end
  return true
end

function isTreeSolvedUp(treel::AbstractBayesTree)
  for (clid, cliq) in getCliques(treel)
    if getCliqueStatus(cliq) != UPSOLVED
      return false
    end
  end
  return true
end

"""
    $SIGNATURES

Reset the Bayes (Junction) tree so that a new upsolve can be performed.

Notes
- Will change previous clique status from `DOWNSOLVED` to `INITIALIZED` only.
- Sets the color of tree clique to `lightgreen`.
"""
function resetTreeCliquesForUpSolve!(treel::AbstractBayesTree)::Nothing
  acclist = CliqStatus[DOWNSOLVED]
  for (clid, cliq) in getCliques(treel)
    if getCliqueStatus(cliq) in acclist
      setCliqueStatus!(cliq, INITIALIZED)
      setCliqueDrawColor!(cliq, "sienna")
    end
  end
  nothing
end

"""
    $SIGNATURES

Return true there is no other sibling that will make progress.

Notes
- Relies on sibling priority order with only one "currently best" option that will force progress in global upward inference.
- Return false if one of the siblings is still busy
"""
function areSiblingsRemaingNeedDownOnly(tree::AbstractBayesTree,
                                        cliq::TreeClique  )::Bool
  #
  stillbusylist = [NULL, INITIALIZED]
  prnt = getParent(tree, cliq)
  if length(prnt) > 0
    for si in getChildren(tree, prnt[1])
      # are any of the other siblings still busy?
      if si.id != cliq.id && getCliqueStatus(si) in stillbusylist
        return false
      end
    end
  end

  # nope, everybody is waiting for something to change -- proceed with forcing a cliq solve
  return true
end

"""
    $SIGNATURES

Approximate Chapman-Kolmogorov transit integral and return separator marginals as messages to pass up the Bayes (Junction) tree, along with additional clique operation values for debugging.

Notes
- `onduplicate=true` by default internally uses deepcopy of factor graph and Bayes tree, and does **not** update the given objects.  Set false to update `fgl` and `treel` during compute.

Future
- TODO: internal function chain is too long and needs to be refactored for maintainability.
"""
function approxCliqMarginalUp!( csmc::CliqStateMachineContainer,
                                childmsgs=LikelihoodMessage[];#fetchMsgsUpChildren(csmc, TreeBelief);
                                N::Int=getSolverParams(csmc.cliqSubFg).N,
                                dbg::Bool=getSolverParams(csmc.cliqSubFg).dbg,
                                multiproc::Bool=getSolverParams(csmc.cliqSubFg).multiproc,
                                logger=ConsoleLogger(),
                                iters::Int=3,
                                drawpdf::Bool=false  )
  #
  # use subgraph copy of factor graph for operations and transfer variables results later only
  fg_ = csmc.cliqSubFg
  tree_ = csmc.tree
  cliq = csmc.cliq

  with_logger(logger) do
    @info "======== Clique $(getLabel(cliq)) ========"
  end

  if multiproc
    cliqc = deepcopy(cliq)
    btnd = getCliqueData(cliqc)
    # ett.cliq = cliqc
    # TODO create new dedicated file for separate process to log with
    try
      retdict = remotecall_fetch(upGibbsCliqueDensity, getWorkerPool(), fg_, cliqc, csmc.solveKey, childmsgs, N, dbg, iters)
    catch ex
      with_logger(logger) do
        @info ex
        @error ex
        flush(logger.stream)
        msg = sprint(showerror, ex)
        @error msg
      end
      flush(logger.stream)
      error(ex)
    end
  else
    with_logger(logger) do
      @info "Single process upsolve clique=$(cliq.id)"
    end
    retdict = upGibbsCliqueDensity(fg_, cliq, csmc.solveKey, childmsgs, N, dbg, iters, logger)
  end

  with_logger(logger) do
    @info "=== end Clique $(getLabel(cliq)) ========================"
  end

  return retdict
end

"""
    $SIGNATURES

Determine which variables to iterate or compute directly for downward tree pass of inference.

DevNotes
- # TODO see #925

Related

directPriorMsgIDs, directFrtlMsgIDs, directAssignmentIDs, mcmcIterationIDs
"""
function determineCliqVariableDownSequence( subfg::AbstractDFG,
                                            cliq::TreeClique;
                                            solvable::Int=1,
                                            logger=ConsoleLogger())
  #
  frtl = getCliqFrontalVarIds(cliq)

  adj, varLabels, FactorLabels = DFG.getBiadjacencyMatrix(subfg, solvable=solvable)
  mask = (x-> x in frtl).(varLabels)
  newFrtlOrder = varLabels[mask]
  subAdj = adj[:,mask]
    #TODO don't use this getAdjacencyMatrixSymbols, #604
    # adj = DFG.getAdjacencyMatrixSymbols(subfg, solvable=solvable)
    # mask = map(x->(x in frtl), adj[1,:])
    # subAdj = adj[2:end,mask] .!= nothing
    # newFrtlOrder = Symbol.(adj[1,mask])

  crossCheck = 1 .< sum(Int.(subAdj), dims=2)
  iterVars = Symbol[]
  for i in 1:length(crossCheck)
    # must add associated variables to iterVars
    if crossCheck[i]
          # # DEBUG loggin
          # with_logger(logger) do
          #   @info "newFrtlOrder=$newFrtlOrder"
          #   @info "(subAdj[i,:]).nzind=$((subAdj[i,:]).nzind)"
          # end
          # flush(logger.stream)
      # find which variables are associated
      varSym = newFrtlOrder[(subAdj[i,:]).nzind]
      union!(iterVars, varSym)
    end
  end

  # return iteration list ordered by frtl
  return intersect(frtl, iterVars)
end

"""
    $SIGNATURES

Perform downward direction solves on a sub graph fragment.
Calculates belief on each of the frontal variables and iterate if required.

Notes
- uses all factors connected to the frontal variables.
- assumes `subfg` was properly prepared before calling.
- has multi-process option.

Dev Notes
- TODO incorporate variation possible due to cross frontal factors.
- cleanup and updates required, and @spawn jl 1.3
"""
function solveCliqDownFrontalProducts!( subfg::AbstractDFG,
                                        cliq::TreeClique,
                                        opts::SolverParams,
                                        logger=ConsoleLogger();
                                        solveKey::Symbol=:default,
                                        MCIters::Int=3 )
  #
  # get frontal variables for this clique
  frsyms = getCliqFrontalVarIds(cliq)

  # determine if cliq has cross frontal factors
  # iterdwn, directdwns, passmsgs?
  iterFrtls = determineCliqVariableDownSequence(subfg,cliq, logger=logger)

  # direct frontals
  directs = setdiff(frsyms, iterFrtls)

  # ignore limited fixed lag variables
  fixd = map(x->opts.limitfixeddown && isMarginalized(subfg,x), frsyms)
  skip = frsyms[fixd]
  iterFrtls = setdiff(iterFrtls, skip)
  directs = setdiff(directs, skip)
  with_logger(logger) do
    @info "cliq $(cliq.id), solveCliqDownFrontalProducts!, skipping marginalized keys=$(skip)"
  end

  # use new localproduct approach
  if opts.multiproc
    downresult = Dict{Symbol, Tuple{ManifoldKernelDensity, Vector{Float64}, Vector{Symbol}}}()
    @sync for i in 1:length(directs)
      @async begin
        downresult[directs[i]] = remotecall_fetch(localProductAndUpdate!, getWorkerPool(), subfg, directs[i], false, solveKey=solveKey)
        # downresult[directs[i]] = remotecall_fetch(localProductAndUpdate!, upp2(), subfg, directs[i], false)
      end
    end
    with_logger(logger) do
      @info "cliq $(cliq.id), solveCliqDownFrontalProducts!, multiproc keys=$(keys(downresult))"
    end
    for fr in directs
        with_logger(logger) do
            @info "cliq $(cliq.id), solveCliqDownFrontalProducts!, key=$(fr), infdim=$(downresult[fr][2]), lbls=$(downresult[fr][3])"
        end
      setValKDE!(subfg, fr, downresult[fr][1], false, downresult[fr][2])
    end
    for mc in 1:MCIters, fr in iterFrtls
      try
        result = remotecall_fetch(localProductAndUpdate!, getWorkerPool(), subfg, fr, false, solveKey=solveKey)
        # result = remotecall_fetch(localProductAndUpdate!, upp2(), subfg, fr, false)
        setValKDE!(subfg, fr, result[1], false, result[2])
        with_logger(logger) do
          @info "cliq $(cliq.id), solveCliqDownFrontalProducts!, iter key=$(fr), infdim=$(result[2]), lbls=$(result[3])"
        end
      catch ex
        # what if results contains an error?
        with_logger(logger) do
          @error ex
          flush(logger.stream)
          msg = sprint(showerror, ex)
          @error msg
        end
        error(ex)
      end
    end
  else
    # do directs first
    for fr in directs
      localProductAndUpdate!(subfg, fr, true, logger, solveKey=solveKey)
    end
    #do iters next
    for mc in 1:MCIters, fr in iterFrtls
      localProductAndUpdate!(subfg, fr, true, logger, solveKey=solveKey)
    end
  end

  return nothing
end

#


function checkWaveSpec!(inSpec::waveSpec)
if isdefined(inSpec,:f)
  nf=length(inSpec.f)
  if any(isnan(inSpec.f))
    #println("checkWaveSpec: NaNs found in f")
  end
  if !isdefined(inSpec,:df)
    inSpec.df=makeWaveSpecFreq(inSpec.f)
  end
else
  #println("checkWaveSpec: f not defined")
end

if isdefined(inSpec,:th)
  nth=length(inSpec.th)
  if any(isnan(inSpec.th))
    #println("checkWaveSpec: NaNs found in th")
  end
  if !isdefined(inSpec,:dth)
    inSpec.dth=makeWaveSpecDirn(inSpec.th)
  end
else
  #println("checkWaveSpec: th not defined")
end

if isdefined(inSpec,:Sf)
  inSpec.Sf=vec(inSpec.Sf)
  if isdefined(inSpec,:f)
    if length(inSpec.Sf)!=nf
       #println("checkWaveSpec: Sf is different lengths to f")
       return
    end
  end
end

if isdefined(inSpec,:Sth)
  inSpec.th=vec(inSpec.th)
  if isdefined(inSpec,:th)
    if length(inSpec.th)!=nth
       #println("checkWaveSpec: Sth is different lengths to th")
       return
    end
  end
end

if isdefined(inSpec,:f) & isdefined(inSpec,:th)
   fm=repmat(inSpec.f[:],1,nth)
   dfm=repmat(inSpec.df[:],1,nth)
   thm=repmat(pi/180*inSpec.th[:]',nf,1)
   dthm=repmat(pi/180*inSpec.dth[:]',nf,1)
end

if isdefined(inSpec,:S)
  nS=collect(size(inSpec.S))
  if isdefined(inSpec,:f) & isdefined(inSpec,:th)
    if any(nS.==nf) & any(nS.==nth)
      if nS[1]==nth
        inSpec.S=inSpec.S'
      end
    else
       #println("checkWaveSpec: S is different size to f and/or th")
       return
    end
  end
  if !isdefined(inSpec,:Sf)
    if isdefined(inSpec,:th)
      # Loop over freq
      inSpec.Sf=zeros(size(inSpec.S,1))
      for i=1:size(inSpec.S,1)
        sumS=0.
	# Sum all directions
        for j=1:size(inSpec.S,2)
          sumS+=dthm[j]*inSpec.S[i,j]
        end
	inSpec.Sf[i]=sumS
      end
      inSpec.Sf=vec(sum(dthm.*inSpec.S,2))
    end
  end
  if !isdefined(inSpec,:Sth)
    if isdefined(inSpec,:f)
      inSpec.Sth=zeros(size(inSpec.S,2))
      # Loop over dirns
      for i=1:size(inSpec.S,2)
        sumS=0.
	# Sum all frequencies
        for j=1:size(inSpec.S,1)
          sumS+=dfm[j]*inSpec.S[j,i]
        end
	inSpec.Sth[i]=sumS
      end
      inSpec.Sth=vec(sum(dfm.*inSpec.S,1))
    end
  end
end
if !isdefined(inSpec,:S) & isdefined(inSpec,:Sf)
  inSpec.S=inSpec.Sf[:,:]
end

return inSpec

end

"""
  Extends Base copy to copy MetoceanTools.waveSpec
"""
function Base.copy(inSpec::waveSpec)::waveSpec
  oSpec=waveSpec()
  isdefined(inSpec,:f) ? oSpec.f=copy(inSpec.f) : 0
  isdefined(inSpec,:df) ? oSpec.df=copy(inSpec.df) : 0
  isdefined(inSpec,:th) ? oSpec.th=copy(inSpec.th) : 0
  isdefined(inSpec,:dth) ? oSpec.dth=copy(inSpec.dth) : 0
  isdefined(inSpec,:Sf) ? oSpec.Sf=copy(inSpec.Sf) : 0
  isdefined(inSpec,:S) ? oSpec.S=copy(inSpec.S) : 0
  return oSpec
end

"""
  Function to split a full direction spectrum into two direction spectra
    by cutting along direction space specified by direction boundaries

  INPUT:
    inSpec - type waveSpec containing fields at least th and S both
               defining vector of directions (th) and direction spectra
         either full directon/frequency or direction spectrum
         Will copy over f,df and will split dth if is defined
    dSplit - defines the directions at which to split the spectrm [stDir,edDir]
  OUTPUT:
    o1,o2  - two partitions of type waveSpec

"""
function wvSpec_dirSplit(inSpec::waveSpec,dSplit)
    th=rem(inSpec.th+720,360)
    dSplitr=rem(dSplit+720,360)
    c1=indmin(abs(th-dSplitr[1]))
    c2=indmin(abs(th-dSplitr[2]))
    if c1==c2
       error("No split required as split directions found are identical at indices $(c1) and $(c2)")
    end
    o1=waveSpec()
    o2=waveSpec()
    o1.f=copy(inSpec.f)
    o2.f=copy(inSpec.f)
    o1.df=copy(inSpec.df)
    o2.df=copy(inSpec.df)
    nth=length(th)
    if c1>c2
      t=c2
      c2=c1
      c1=t
    end
    o1.th=inSpec.th[c1:(c2-1)]
    o2.th=[inSpec.th[c2:end];inSpec.th[1:(c1-1)]]
    o1.th=vec(o1.th)
    o2.th=vec(o2.th)
    if isdefined(inSpec,:df)
      o1.dth=inSpec.dth[c1:(c2-1)]
      o2.dth=[inSpec.dth[c2:end];inSpec.dth[1:(c1-1)]]
      o1.dth=vec(o1.dth)
      o2.dth=vec(o2.dth)
    end
    o1.S=inSpec.S[:,c1:(c2-1)]
    o2.S=[inSpec.S[:,c2:end] inSpec.S[:,1:(c1-1)]]
    checkWaveSpec!(o1)
    checkWaveSpec!(o2)
    return o1,o2
end
function wvSpec_dirSplit(inSpec::Array{waveSpec,1},dSplit)
  nSpec=length(inSpec)
  o1=Array{waveSpec,1}(nSpec)
  o2=Array{waveSpec,1}(nSpec)
  for i=1:nSpec
    o1[i],o2[i]=wvSpec_dirSplit(inSpec[i],dSplit)
  end
  return o1,o2
end

function makeWaveSpecDelDirn(th::Array{Float64,1})
  dth=similar(th)
  nth=length(th)
  for i=2:nth-1
    th1=rem(th[i-1]+720,360)
    th2=rem(th[i]+720,360)
    th3=rem(th[i+1]+720,360)
    dth1=abs(gmAngDiff(th2,th1))
    dth2=abs(gmAngDiff(th3,th2))
    dth[i]=0.5*(dth1+dth2)
  end
  dth[1]=dth[2]
  dth[nth]=dth[nth-1]
  return dth
end

function makeWaveSpecFreq()
  f=[1/x for x=30:-0.05:2]
  return f
end
function makeWaveSpecDelFreq(f::Array{Float64,1})
  df=copy(f)
  nf=length(f)
  df[1]=f[2]-f[1]
  df[2:(nf-1)]=( f[3:nf]-f[2:(nf-1)]     )/2 +
  ( f[2:(nf-1)]-f[1:(nf-2)] )/2
  df[nf]=f[nf]-f[nf-1]
  return df
end

#######################################################################
# Make a new wave spectrum
#######################################################################
#function makeSpecJONSWAP(Hs,Tp,Gamma,SigmaA,SigmaB)::waveSpec
function makeSpecJONSWAP(inParm::Array{Float64,1})::waveSpec
  # 0. Map input parameters
  (Hs,Tp,Gamma,SigmaA,SigmaB)=inParm
  # A. Make a new spectrum object
  s=waveSpec()
  # B. Create JONSWAP
  g::Float64=9.81
  fp::Float64=1./Tp
  nf=length(s.f)
  s.Sf=zeros(nf)
  for i=1:nf
    if s.f[i]<fp
      sigma=SigmaA
    else
      sigma=SigmaB
    end
    s.Sf[i]= g^2*(2*pi)^-4* s.f[i]^-5* exp(-5/4*(s.f[i]/fp)^-4)*
          Gamma^(exp(-1 * (((s.f[i]-fp)^2) /
	  (2*sigma^2*fp^2)) ))
  end
  sumS=0.
  for i=1:nf
    sumS+=s.Sf[i]*s.df[i]
  end
  alpha=Hs.^2./(16.*sumS)
  for i=1:nf
    s.Sf[i]=alpha.*s.Sf[i]
  end
  return s
end

#######################################################################
# Calculate wave spectrum integrated parameters
#######################################################################
function calcSpecPar(mySpec::waveSpec)

  # 0. Remove the zero frequency if present
  fZero=find(mySpec.f.==0)
  if !isempty(fZero)
    shift!(mySpec.f)
    shift!(mySpec.df)
    mySpec.S=mySpec.S[2:end,:]
    mySpec.S=mySpec.S[:,:]
  end

  # A. Detemine frequency cut-off
  fcut::Float64=mySpec.f[end]+mySpec.df[end]/2
  ecut::Float64=mySpec.S[end]

  # B. Calculate moment contributions from the tail
  m0t::Float64=1/4*fcut.*ecut
  m1t::Float64=1/3*fcut^2.*ecut
  m2t::Float64=1/2*fcut^3.*ecut

  # C. Calculate moments of the spectrum
  preCalc0::Array{Float64,1}=mySpec.df
  preCalc1::Array{Float64,1}=preCalc0.*mySpec.S[:,1]
  m0::Float64  = sum(sum(         preCalc1) + m0t)
  m1::Float64  = sum(sum(mySpec.f    .* preCalc1) + m1t)
  m2::Float64  = sum(sum(mySpec.f.^2 .* preCalc1) + m2t)
  mm1::Float64 = sum(sum(1./mySpec.f .* preCalc1))
  fe4::Float64 = sum(sum(mySpec.f .* preCalc0.*mySpec.S.^4))
  e4::Float64  = sum(sum(      preCalc0.*mySpec.S.^4))

  # D. Calculate integrated parameters
  Data=zeros(6)
  Name=["Hs","Tp","T01","T02","Tm01","T4"]
  Data[1]=4.*sqrt(m0);
  iTp::Int64=indmax(mySpec.S)
  Data[2]=1./(mySpec.f[iTp])
  Data[3]=m0./m1        #[s] Mean Period
  Data[4]=sqrt(m0./m2)  #[s] Zero-crossing Period
  Data[5]=mm1./m0      #[s] Energy Period
  # Calculate T4
  #  Weighted Peak by recommend by Young, Ocean Eng. Vol22 No.7 pp 669 to 686.
  #  The Determination of confidence limits associated with estimates of the
  #    spectral peak frequency
  Data[6]=1./(fe4./e4)

  return Data,Name
end

"""
  Part of:  fitSpecJONSWAPsingle

  Returns the RMS error of fit between single JONSWAP and input discrete spectrum

  Written by: Jason McConochie
  Revision 1, 30Dec2016, Jason McConochie
"""
function errorfitSpecJONSWAPsingle(wParm::Array{Float64,1},Sf::Array{Float64,1},fParm,fMask)
  maskReplace!(wParm,fParm,fMask)
  if any(wParm.<0.0)
    return 1000.
  end
  if wParm[3]<0.8
    return 100./(wParm[3])
  end
  solverSpec=makeSpecJONSWAP(wParm)
  sumS=0.
  for i=1:length(Sf)
    sumS+=(Sf[i]-solverSpec.Sf[i])^2
  end
  errRMS=sqrt(sumS)
  #errRMS=sqrt( sum((Sf.-solverSpec.Sf).^2) )
  return errRMS
end
"""
  fitSpecJONSWAPsingle

  Makes a single fit of the JONSWAP single spectrum to an input discrete spectrum
    interpolating the input spectrum to frequencies defined by waveSpec() initialisation

  Input:
     f,S - input spectrum
     fParm - array of [Hs,Tp,Gamma,SigmaA,SigmaB]
     fMask - Boolean array of mask indicating which of the fParm should be fixed in the fitting process
  Output:
     An array of parameters like fParm containing the best fit parameters

  Written by: Jason McConochie
  Revision 1, 30Dec2016, Jason McConochie
"""
function fitSpecJONSWAPsingle(f::Array{Float64,1},Sf::Array{Float64,1},fParm::Array{Float64,1},fMask::Array{Bool,1})
  # A. Interpolate to the standard waveSpec frequencies
  nS=waveSpec()
  fi=nS.f
  Si=interp1(f,Sf,fi)
  Si[Si.<0.]=0.
  r = optimize(b -> errorfitSpecJONSWAPsingle(b,Si,fParm,fMask), fParm)
  r1 = r.minimizer
  return r1
end
"""
  fitSpecJONSWAPsingle

  Makes a fit of the JONSWAP single spectrum to an input discrete spectrum
    interpolating the input spectrum to frequencies defined by waveSpec() initialisation

  This function takes an array of spectra defintions and returns a tsParm structure

  Input:
     f,S - input spectrum
     fParm - array of [Hs,Tp,Gamma,SigmaA,SigmaB]
     fMask - Boolean array of mask indicating which of the fParm should be fixed in the fitting process
  Output:
     A tsParm type of parameters
        sDate - dates starting from 1/1/2000 and then every hour
        Data - array containing Hs,Tp,Gamma,SigmaA,SigmaB on each row
        Name - Names of parameters

     This is suitable to be sent to tsQuickPlot

  Written by: Jason McConochie
  Revision 1, 30Dec2016, Jason McConochie
"""
function fitSpecJONSWAPsingle(inSpec::Array{waveSpec,1},fParm::Array{Float64,1},fMask::Array{Bool,1})
  o=tsParm()
  o.Data=zeros(Float64,(length(inSpec),5))
  o.Name=["Hs","Tp","Gamma","SigmaA","SigmaB"]
  o.sDate=Array{DateTime}(length(inSpec))
  for i=1:length(inSpec)
    o.Data[i,:]=fitSpecJONSWAPsingle(inSpec[i].f,inSpec[i].Sf,fParm,fMask)
    o.sDate[i]=DateTime(2000,1,1,0,0,0)+Dates.Day(i)
    solvedSpec=makeSpecJONSWAP(o.Data[i,:])
    #PyPlot.clf()
    #plot(inSpec[i].f,vec(inSpec[i].Sf),marker="o")
    #plot(solvedSpec.f,solvedSpec.Sf)
    #savefig(*("Jason",@sprintf("%015d",i)))
  end
  return o
end


abstract type AbstractSimulation end
"""
    Simulation

Contains all data needed by mesher and em-solver to run a simulation.

The simulation object shouldn't include any information about space or time discretization
or any information related to the solver or numerical algorithms used.
Its only purpose is to hold all the data needed by the solver and mesher to run a simulation.

The workflow is as follows:
- Construct all simulation componenets
- Call the mesh function to get a mesh object
- Discretize the simulation data
- Initalize fields for storing the solution
- Call the solve function to get the solution

"""
@with_kw struct Simulation <: AbstractSimulation
    background
    structures
    sources
    boundaryconditions
    # symmetries = NoSymmetry()
    # monitors = []
end


@testset "CUDA: BOCcos" begin
    system = BOCcos(GPSL1(use_gpu = Val(true)), 1, 1)
    @test @inferred(Tracking.is_upcoming_integration_new_bit(system, 0xfffff00000, 50)) == true
    @test @inferred(Tracking.is_upcoming_integration_new_bit(system, 0xfffff, 10)) == false
    @test @inferred(Tracking.is_upcoming_integration_new_bit(system, 0xfffff, 40)) == true

    @test @inferred(Tracking.get_default_correlator(system, NumAnts(1))) == EarlyPromptLateCorrelator(NumAnts(1))
    @test @inferred(Tracking.get_default_correlator(system, NumAnts(3))) == EarlyPromptLateCorrelator(NumAnts(3))

    system = BOCcos(GPSL5(use_gpu = Val(true)), 1, 1)
    @test @inferred(Tracking.is_upcoming_integration_new_bit(system, 0x35, 50)) == true
    @test @inferred(Tracking.is_upcoming_integration_new_bit(system, 0x35, 5)) == false
    @test @inferred(Tracking.is_upcoming_integration_new_bit(system, 0x3ca, 10)) == true # 0x3ca == 1111001010

    @test @inferred(Tracking.get_default_correlator(system, NumAnts(1))) == EarlyPromptLateCorrelator(NumAnts(1))
    @test @inferred(Tracking.get_default_correlator(system, NumAnts(3))) == EarlyPromptLateCorrelator(NumAnts(3))

end


using TermWin

trun( ()->begin
    n = 2000
    for i=1:n
        TermWin.progressUpdate( i/n )
        TermWin.progressMessage( ( @sprintf( "%d/%d ", i, n ) ) * strftime( "%H:%M:%S", time() ) )
        if mod(i,10)==0
            sleep(50.0/n)
        end
    end
    n
end )


module TestFold

include("../preamble.jl")

@testset "$fold" for fold in [foldl, reduce, dreduce]
    @testset "IdentityTransducer" begin
        @test fold(+, IdentityTransducer(), 1:10) == sum(1:10)
    end
end

end  # module


@doc """
    pairwise(dist::StringDistance, xs::AbstractVector; preprocess = nothing)
    pairwise(dist::StringDistance, xs::AbstractVector, ys::AbstractVector; preprocess = nothing)

Compute distances between all pairs of elements in `xs`  and `ys` according to the
`StringDistance` `dist`.

For QGramDistances preprocessing will be used either if `preprocess` is set
to true or if there are more than 5 elements in `xs`. Set `preprocess` to
false if no preprocessing should be used, regardless of length.

Both symmetric and asymmetric versions are available.

### Examples
```julia-repl
julia> using StringDistances
julia> iter = ["New York", "Princeton"]
julia> pairwise(Levenshtein(), iter) # symmetric
2×2 Array{Float64,2}:
 0.0  9.0
 9.0  0.0
julia> iter2 = ["San Francisco"]
julia> pairwise(Levenshtein(), iter, iter2) # asymmetric
2×1 Array{Float64,2}:
 12.0
 10.0
```
"""
Distances.pairwise

function Distances.pairwise(dist::StringDistance, xs::AbstractVector, ys::AbstractVector; preprocess = nothing)
    T = result_type(dist, eltype(xs), eltype(ys))
    if Missing <: Union{eltype(xs), eltype(ys)}
        T = Union{T, Missing}
    end
    R = Matrix{T}(undef, length(xs), length(ys))
    pairwise!(R, dist, xs, ys; preprocess = preprocess)
end

function Distances.pairwise(dist::StringDistance, xs::AbstractVector; preprocess = nothing)
    T = result_type(dist, eltype(xs), eltype(xs))
    if Missing <: eltype(xs)
        T = Union{T, Missing}
    end
    R = Matrix{T}(undef, length(xs), length(xs))
    pairwise!(R, dist, xs; preprocess = preprocess)
end

@doc """
    pairwise!(R::AbstractMatrix, dist::StringDistance, xs::AbstractVector; preprocess = nothing)
    pairwise!(R::AbstractMatrix, dist::StringDistance, xs::AbstractVector, ys::AbstractVector; preprocess = nothing)

Compute distances between all pairs of elements in `xs` and `ys` according to the
`StringDistance` `dist` and write the result in `R`.

For QGramDistances preprocessing will be used either if `preprocess` is set
to true or if there are more than 5 elements in `xs`. Set `preprocess` to
false if no preprocessing should be used, regardless of length.
"""
Distances.pairwise!

function Distances.pairwise!(R::AbstractMatrix, dist::StringDistance, xs::AbstractVector, ys::AbstractVector; preprocess = nothing)
    length(xs) == size(R, 1) || throw(DimensionMismatch("inconsistent length"))
    length(ys) == size(R, 2) || throw(DimensionMismatch("inconsistent length"))
    _asymmetric_pairwise!(R, dist, xs, ys; preprocess = preprocess)
end

function Distances.pairwise!(R::AbstractMatrix, dist::StringDistance, xs::AbstractVector; preprocess = nothing)
    length(xs) == size(R, 1) || throw(DimensionMismatch("inconsistent length"))
    length(xs) == size(R, 2) || throw(DimensionMismatch("inconsistent length"))
    (dist isa SemiMetric) ?
        _symmetric_pairwise!(R, dist, xs; preprocess = preprocess) :
        _asymmetric_pairwise!(R, dist, xs, xs; preprocess = preprocess)
end

function _preprocess(xs, dist::QGramDistance, preprocess)
    if (preprocess === true) || (isnothing(preprocess) && length(xs) >= 5)
        return map(x -> x === missing ? x : QGramSortedVector(x, dist.q), xs)
    else
        return xs
    end
end
_preprocess(xs, dist::StringDistance, preprocess) = xs

function _symmetric_pairwise!(R::AbstractMatrix, dist::StringDistance, xs::AbstractVector; preprocess = nothing)
    objs = _preprocess(xs, dist, preprocess)
    for i in 1:length(objs)
        # handle missing
        R[i, i] = objs[i] != objs[i]
        Threads.@threads for j in (i+1):length(objs)
            R[i, j] = R[j, i] = evaluate(dist, objs[i], objs[j])
        end
    end
    return R
end

function _asymmetric_pairwise!(R::AbstractMatrix, dist::StringDistance, xs::AbstractVector, ys::AbstractVector; preprocess = nothing)
    objsxs = _preprocess(xs, dist, preprocess)
    objsys = _preprocess(ys, dist, preprocess)
    for i in 1:length(objsxs)
        Threads.@threads for j in 1:length(objsys)
            R[i, j] = evaluate(dist, objsxs[i], objsys[j])
        end
    end
    return R
end


using Base: @deprecate, @deprecate_binding, depwarn

if !isdefined(Base, :stderr)
    @deprecate stderr(obj::StatisticalModel) stderror(obj)
else
    function (io::typeof(stderr))(obj::StatisticalModel)
        Base.depwarn("stderr(obj::StatisticalModel) is deprecated, use stderror(obj) instead", :stderr)
        io === stderr ? stderror(obj) : throw(MethodErrror(io, (obj,)))
    end
end

@deprecate model_response(obj::StatisticalModel) response(obj)

@deprecate norepeats(a::AbstractArray) allunique(a)

@deprecate(mad!(v::AbstractArray{<:Real}, center;
                constant::Real = BigFloat("1.482602218505601860547076529360423431326703202590312896536266275245674447622701")),
           mad!(v, center=center, constant=constant))


# __BEGIN_LICENSE__
#
# ThreeDeconv.jl
#
# Copyright (c) 2018, Stanford University
#
# All rights reserved.
#
# Redistribution and use in source and binary forms for academic and other
# non-commercial purposes with or without modification, are permitted provided
# that the following conditions are met:
#
# * Redistributions of source code, including modified source code, must retain
#   the above copyright notice, this list of conditions and the following
#   disclaimer.
#
# * Redistributions in binary form or a modified form of the source code must
#   reproduce the above copyright notice, this list of conditions and the
#   following disclaimer in the documentation and/or other materials provided with
#   the distribution.
#
# * Neither the name of The Leland Stanford Junior University, any of its
#   trademarks, the names of its employees, nor contributors to the source code
#   may be used to endorse or promote products derived from this software without
#   specific prior written permission.
#
# * Where a modified version of the source code is redistributed publicly in
#   source or binary forms, the modified source code must be published in a freely
#   accessible manner, or otherwise redistributed at no charge to anyone
#   requesting a copy of the modified source code, subject to the same terms as
#   this agreement.
#
# THIS SOFTWARE IS PROVIDED BY THE TRUSTEES OF THE LELAND STANFORD JUNIOR
# UNIVERSITY "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LELAND STANFORD JUNIOR
# UNIVERSITY OR ITS TRUSTEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
# OF SUCH DAMAGE.
#
# __END_LICENSE__

function optimize(optimizer::Optimizer, options::DeconvolutionOptions)
    tic = time()

    state = optimizer.initial_state

    metric = optimizer.check_state(state, 0, tic)
    trace = OptimizationTrace{typeof(metric)}()
    push!(trace, metric)

    if options.show_trace
        print_header(optimizer.method)
        show(metric)
    end

    if options.x0 != []
        state.x .= typeof(state.x)(options.x0)
    end

    converged = false
    for iter in 1:options.max_iters

        optimizer.update_state!(state)

        if iter % options.check_every == 0

            metric = optimizer.check_state(state, iter, tic)
            push!(trace, metric)

            if options.show_trace
                show(metric)
            end

            if optimizer.check_convergence(metric)
                @printf "Optimization converged.\n"
                converged = true
                break
            end

            if !isnan(options.time_limit) && metric.elapsed_time > options.time_limit
                @printf "Time out.\n"
                break
            end

        end

    end

    return OptimizationResult(state.x, trace, converged)
end


using JSCall, WebIO, JSExpr
using Test
using JSExpr: jsexpr
using JSCall
using WebIO
THREE, document = JSModule(
    :THREE,
    "https://cdnjs.cloudflare.com/ajax/libs/three.js/103/three.js",
)

scene = THREE.new.Scene()
width, height = 500, 500
# Create a basic perspective camera
camera = THREE.new.PerspectiveCamera(75, width / height, 0.1, 1000)
camera.position.z = 4
renderer = THREE.new.WebGLRenderer(Dict(:antialias => true))
renderer.setClearColor("#ffffff")
geometry = THREE.new.BoxGeometry(1, 1, 1)
material = THREE.new.MeshBasicMaterial(color = "#433F81")
cube = THREE.new.Mesh(geometry, material);
scene.add(cube)
container = document.querySelector("#container")
container.appendChild(renderer.domElement)
display((scope(THREE))(dom"div#container"()))

@jsfun function render(unused)
    @var cube = $cube
    @var renderer = $renderer
    @var scene = $scene
    @var camera = $camera
    cube.rotation.x += 0.01
    cube.rotation.y += 0.01
    renderer.render(scene, camera)
    requestAnimationFrame(render)
end

render(scene)


# License for this file: MIT (expat)
# Copyright 2017-2020, DLR Institute of System Dynamics and Control

"""
    module Modia3D.PathPlanning

Module for path planning and designing reference paths for Modia3D

# Main developers

Andrea Neumayr and Martin Otter, [DLR - Institute of System Dynamics and Control](https://www.dlr.de/sr/en)
"""
module PathPlanning

export PTP_path, pathEndTime, getPosition!, getPosition, getIndex, plotPath
export ReferencePath, scheduleReferenceMotion, calculateRobotMovement, getRefPathPosition, getRefPathInitPosition
export ptpJointSpace
export ptpJointSpace!

export getPathComponent

import Base
import Modia3D
import Modia3D.Basics
import Modia3D.Composition
import Modia3D.Shapes

using ModiaLang

using Unitful
using StaticArrays
using OrderedCollections

include("pathPlanning.jl")
include("referencePathInternal.jl")
include("referencePathUser.jl")

end


#
# Function to set bounds with optional user input
#

function setbounds(vars,l,u)
  # Throw error if the user set a bound to a variable which is not in list for this function
  for field in fieldnames(typeof(l))
    if (getfield(l,field) != nothing) || (getfield(u,field) !=nothing)
      found = false
      for var in vars
        if field == var.field
          found = true
          if var.type == Nothing
            error(" Bounds to intercept $(var.field) are not supported. For a constant use $(var.field) = const.")
          end
        end
      end
      if ! found
        error(" A bound was set to variable '$field', but '$field' is not a variable of the current fit function.")
      end
    end
  end
  # Total number variables
  n = 0
  for var in vars
    n += var.dim
  end
  lower = Vector{Float64}(undef,n)
  upper = Vector{Float64}(undef,n)
  idim = 1
  for var in vars
    ltmp = check_lower_bound_input(var,getfield(l,var.field))
    utmp = check_upper_bound_input(var,getfield(u,var.field))
    @. lower[idim:idim+var.dim-1] = ltmp
    @. upper[idim:idim+var.dim-1] = utmp
    idim += var.dim
  end
  i = 0
  for var in vars
    i += 1
    if lower[i] > upper[i]
      error(" Error in bounds. Lower bound of '$(var.field)' greater than upper bound. ")
    end
  end
  return lower, upper
end

function check_lower_bound_input(var,value)
  if value == nothing
    return [ -Inf for _ in 1:var.dim ]
  end
  if ! (value isa var.type )
    error("Lower bound of $(var.field) must be of type $(var.type), got $(typeof(value))")
  end
  if (value isa Vector) && (length(value) != var.dim)
    error("Lower bound of $(var.field) must be of dimension $(var.dim), got $(length(value))")
  end
  return value
end

function check_upper_bound_input(var,value)
  if value == nothing
    return [ +Inf for _ in 1:var.dim ]
  end
  if ! (value isa var.type)
    error("Upper bound of $(var.field) must be of type $(var.type), got $(typeof(value))")
  end
  if (value isa Vector) && (length(value) != var.dim)
    error("Upper bound of $(var.field) must be of dimension $(var.dim), got $(length(value))")
  end
  return value
end


using Colors, ColorSchemes
using CairoMakie, FileIO
using CSV, DataFrames
using Statistics

imgpath = joinpath("docs", "imgs", "ED-model")
resultspath = joinpath("docs", "results", "ED-model")

x = [results_ed["glc_ext"] for results_ed in res_ed]
y = [results_ed["mu"] for results_ed in res_ed]

fig = Figure()
ax = Axis(fig[1, 1])
scatter_glc = scatter!(ax, x, y)
line_glc = lines!(ax, x, y)

ax.xlabel = "External glucose concetration [mM]"
ax.ylabel = "Biomass [1/h]"

fig

FileIO.save(joinpath(imgpath, "Glu_ED_Growth.pdf"), fig)

## ##

x = [results_ed["mu"] for results_ed in res_ed]
a = [results_ed["g6p"] for results_ed in res_ed]
b = [results_ed["glu"] for results_ed in res_ed]

fig = Figure()
ax = Axis(fig[1, 1])
scatter1 = scatter!(ax, x, a)
line1 = lines!(ax, x, a)
scatter2 = scatter!(ax, x, b)
line2 = lines!(ax, x, b)

ax.xlabel = "Biomass [1/h]"
ax.ylabel = "Metabolite concentration"

fig[1, 1] = Legend(
    fig,
    [[scatter1, line1], [scatter2, line2]],
    ["Glucose-6-Phosphate", "Glutamate"],
    tellheight = false,
    tellwidth = false,
    halign = :left,
    valign = :bottom,
    labelsize = 14,
)
fig

FileIO.save(joinpath(imgpath, "Glu_ED_G6P+GLU.pdf"), fig)

## Enzyme concentration band plot ##

x = [results_ed["mu"] for results_ed in res_ed]
c = [results_ed["pts"] for results_ed in res_ed]
d = [results_ed["ed"] for results_ed in res_ed]
e = [results_ed["pyk"] for results_ed in res_ed]
f = [results_ed["ldh"] for results_ed in res_ed]
g = [results_ed["burn"] for results_ed in res_ed]
h = [results_ed["lp"] for results_ed in res_ed]

fracs = [c, d, e, f, g, h]
fraclabels = [
    "PTS",
    "ED",
    "Pyruvate kinase",
    "Lactate dehydrogenase",
    "ATP burning enzyme",
    "Lactate permease",
]
function plot_proteome(ax, x, fracs, fraclabels)
    d = cumsum(hcat(fracs...)'; dims = 1)
    d = [zeros(size(d, 2))'; d ./ d[end, :]']
    for i = 1:length(fraclabels)
        band!(ax, x, d[i, :], d[i+1, :], label = fraclabels[i])
    end
end

f = Figure();
ax = Axis(f[1, 1]);
plot_proteome(ax, 1:10, fracs, fraclabels)
ax.xlabel = "Biomass [1/h]"
ax.ylabel = "Proteome fraction"
f[1, 2] = Legend(f, ax, "Protein", unique = true, framevisible = false)
f

FileIO.save(joinpath(imgpath, "Glu_ED_Enzymeband.pdf"), f)

## Rates ##

x = [results_ed["mu"] for results_ed in res_ed]
i = [results_ed["v_pts"] for results_ed in res_ed]
j = [results_ed["v_pyk"] for results_ed in res_ed]
k = [results_ed["v_ldh"] for results_ed in res_ed]
l = [results_ed["v_burn"] for results_ed in res_ed]
m = [results_ed["v_lp"] for results_ed in res_ed]

fig = Figure()
ax = Axis(fig[1, 1])
scatter1 = scatter!(ax, x, i)
line1 = lines!(ax, x, i)
scatter2 = scatter!(ax, x, j)
line2 = lines!(ax, x, j)
scatter3 = scatter!(ax, x, k)
line3 = lines!(ax, x, k)
scatter4 = scatter!(ax, x, l)
line4 = lines!(ax, x, l)
scatter5 = scatter!(ax, x, m)
line5 = lines!(ax, x, m)

ax.xlabel = "Biomass [1/h]"
ax.ylabel = "Fluxes [mmol/gDW/h]"

fig[1, 1] = Legend(
    fig,
    [
        [scatter1, line1],
        [scatter2, line2],
        [scatter3, line3],
        [scatter4, line4],
        [scatter5, line5],
    ],
    ["v_pts", "v_pyk", "v_ldh", "v_burn", "v_lp"],
    tellheight = false,
    tellwidth = false,
    halign = :left,
    valign = :bottom,
    labelsize = 14,
)
fig

FileIO.save(joinpath(imgpath, "Glu_ED_Rates.pdf"), fig)


import Parameters: @with_kw

@with_kw struct Shape
    bbox::S.Box
    p::Array{Real}
    meshes::Array{Tuple}
end

export Shape


#################### Sampler ####################

const samplerfxargs = [(:model, Mamba.Model), (:block, Integer)]

#################### Types and Constructors ####################

type NullFunction end

type SamplingBlock
  model::Model
  index::Int
  transform::Bool

  SamplingBlock(model::Model, index::Integer=0, transform::Bool=false) =
    new(model, index, transform)
end

Sampler(param::Symbol, args...) = Sampler([param], args...)

function Sampler(params::Vector{Symbol}, f::Function, tune::Any=Dict())
  Sampler(params, modelfx(samplerfxargs, f), tune, Symbol[])
end

function SamplerVariate{T<:SamplerTune, U<:Real}(x::AbstractVector{U}, tune::T)
  SamplerVariate{T}(x, tune)
end

function SamplerVariate(block::SamplingBlock, pargs...; kargs...)
  m = block.model
  SamplerVariate(unlist(block), m.samplers[block.index], m.iter, pargs...;
                 kargs...)
end

function SamplerVariate{T<:SamplerTune, U<:Real}(x::AbstractVector{U},
                                                 s::Sampler{T}, iter::Integer,
                                                 pargs...; kargs...)
  if iter == 1
    v = SamplerVariate{T}(x, pargs...; kargs...)
    s.tune = v.tune
  else
    v = SamplerVariate(x, s.tune)
  end
  v
end

#################### Base Methods ####################

function Base.show(io::IO, s::Sampler)
  print(io, "An object of type \"$(summary(s))\"\n")
  print(io, "Sampling Block Nodes:\n")
  show(io, s.params)
  print(io, "\n\n")
  show(io, "text/plain", first(code_typed(s.eval)))
  println(io)
end

function Base.showall(io::IO, s::Sampler)
  show(io, s)
  print(io, "\nTuning Parameters:\n")
  show(io, s.tune)
  print(io, "\n\nTarget Nodes:\n")
  show(io, s.targets)
end

#################### Variate Validators ####################

validate(v::SamplerVariate) = v

function validatebinary(v::SamplerVariate)
  all(insupport(Bernoulli, v)) ||
    throw(ArgumentError("variate is not a binary vector"))
  v
end

function validatesimplex(v::SamplerVariate)
  isprobvec(v) || throw(ArgumentError("variate is not a probability vector"))
  v
end

#################### sample! Generics ####################

function sample!(v::SamplerVariate, density::Nullable; args...)
  isnull(density) && error("must specify a target density in $(typeof(v))",
                           " constructor or sample! method")
  sample!(v, get(density); args...)
end

#################### Simulation Methods ####################

function gradlogpdf!{T<:Real}(block::SamplingBlock, x::AbstractArray{T},
                              dtype::Symbol=:forward)
  gradlogpdf!(block.model, x, block.index, block.transform, dtype=dtype)
end

function logpdf!{T<:Real}(block::SamplingBlock, x::AbstractArray{T})
  logpdf!(block.model, x, block.index, block.transform)
end

function logpdfgrad!{T<:Real}(block::SamplingBlock, x::AbstractVector{T},
                              dtype::Symbol)
  grad = gradlogpdf!(block, x, dtype)
  logf = logpdf!(block, x)
  (logf, ifelse(isfinite(grad), grad, 0.0))
end

function unlist(block::SamplingBlock)
  unlist(block.model, block.index, block.transform)
end

function relist{T<:Real}(block::SamplingBlock, x::AbstractArray{T})
  relist(block.model, x, block.index, block.transform)
end

#################### Auxiliary Functions ####################

asvec(x::Union{Number, Symbol}) = [x]
asvec(x::Vector) = x


import SVMLightLoader: load_svmlight_file

url = "http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary/a1a"
filename = basename(url)

if !isfile(filename)
    import HTTPClient: get
    contents = get(url)
    f = open(filename, "w")
    write(f, contents.body.data)
    close(f)
end

# The code is from below:
# http://thirld.com/blog/2015/05/30/julia-profiling-cheat-sheet/
function benchmark(target, args...)
    # Any setup code goes here.

    # Run once, to force compilation.
    println("======================= First run:")
    @time target(args...)

    # Run a second time, with profiling.
    println("\n\n======================= Second run:")

    Profile.init()
    Profile.clear()
    Profile.clear_malloc_data()

    @profile @time target(args...)

    # Write profile results to profile.bin.
    f = open("trace.def", "w")
    Profile.print(f)
    close(f)
end

benchmark(load_svmlight_file, filename)


function show{P}(io::IO, x::ArbFloat{P})
    if isexact(x)
      s = string(midpoint(x))
    else
      s = string(x)
    end
    print(io, s)
end

show{P}(x::ArbFloat{P}) = show(Base.STDOUT, x)

function showcompact{P}(io::IO, x::ArbFloat{P})
    s = stringCompact(x)
    print(io, s)
end

function showall{P}(io::IO, x::ArbFloat{P})
    s = stringAll(x)
    print(io, s)
end

function showallcompact{P}(io::IO, x::ArbFloat{P})
    s = stringAllCompact(x)
    print(io, s)
end

function showsmart{P}(io::IO, x::ArbFloat{P})
    s = smartstring(x)
    print(io, s)
end
# showsmart is not a Base show function, it needs explict version without io parameter
showsmart{P}(x::ArbFloat{P}) = showsmart(Base.STDOUT, x)

function showmany{P,N}(io::IO, x::NTuple{N,ArbFloat{P}}, stringformer::Function)
    if N==0
       print(io,"()")
       return nothing
    elseif N==1
       print(io,string("( ",stringformer(x[1]),", )"))
       return nothing
    end

    ss = Vector{String}(N)
    for i in 1:N
      ss[i] = stringformer(x[i])
    end

    println(io,string("( ", ss[1], ","));
    for s in ss[2:end-1]
      println(io, string("  ", s, ","))
    end
    println(io,string("  ", ss[end], " )"))
end

showmany{P,N}(x::NTuple{N,ArbFloat{P}}, stringformer::Function) =
   showmany(Base.STDOUT,x,stringformer)

function showmany{P}(io::IO, x::Vector{ArbFloat{P}}, stringformer::Function)
    n = length(x)

    if n==0
       print(io,"[]")
       return nothing
    elseif n==1
       print(io,string("[ ",stringformer(x[1])," ]"))
       return nothing
    end

    ss = Vector{String}(n)
    for i in 1:n
      ss[i] = stringformer(x[i])
    end

    println(io,string("[ ", ss[1], ","));
    for s in ss[2:end-1]
      println(io, string("  ", s, ","))
    end
    println(io,string("  ", ss[end], " ]"))
end

showmany{P}(x::Vector{ArbFloat{P}}, stringformer::Function) =
    showmany(Base.STDOUT,x,stringformer)

show{P,N}(io::IO, x::NTuple{N,ArbFloat{P}}) = showmany(io, x, string)
showall{P,N}(io::IO, x::NTuple{N,ArbFloat{P}}) = showmany(io, x, stringAll)
showcompact{P,N}(io::IO, x::NTuple{N,ArbFloat{P}}) = showmany(io, x, stringCompact)
# showsmart is not a Base show function, it needs explict version without io parameter
showsmart{P,N}(io::IO, x::NTuple{N,ArbFloat{P}}) = showmany(io, x, smartstring)
showsmart{P,N}(x::NTuple{N,ArbFloat{P}}) = showmany(Base.STDOUT, x, smartstring)

show{P}(io::IO, x::Vector{ArbFloat{P}}) = showmany(io, x, string)
showall{P}(io::IO, x::Vector{ArbFloat{P}}) = showmany(io, x, stringAll)
showcompact{P}(io::IO, x::Vector{ArbFloat{P}}) = showmany(io, x, stringCompact)
# showsmart is not a Base show function, it needs explict version without io parameter
showsmart{P}(io::IO, x::Vector{ArbFloat{P}}) = showmany(io, x, smartstring)
showsmart{P}(x::Vector{ArbFloat{P}}) = showmany(Base.STDOUT, x, smartstring)

show{P,N}(io::IO, x::Vararg{ArbFloat{P},N}) = showmany(io, x, string)
showall{P,N}(io::IO, x::Vararg{ArbFloat{P},N}) = showmany(io, x, stringAll)
showcompact{P,N}(io::IO, x::Vararg{ArbFloat{P},N}) = showmany(io, x, stringCompact)
# showsmart is not a Base show function, it needs explict version without io parameter
showsmart{P,N}(io::IO, x::Vararg{ArbFloat{P},N}) = showmany(io, x, smartstring)
showsmart{P,N}(x::Vararg{ArbFloat{P},N}) = showmany(Base.STDOUT, x, smartstring)


println("First line of the script")
time_start = time()
using Test
using Logging
using SparseArrays
using ExSup
using Pio3d
using JLD2
@info "Before Distributed"
using Distributed
if nprocs() == 1
    addprocs(3)
end
# using Revise
# using ExSup
using LarSurf
# @everywhere using LarSurf

fn = "exp_surface_extraction_ircad_times.csv"

@info "before everywhere using"
@info "time from start: $(time()-time_start) [s]"
# using Plasm
# @everywhere using LarSurf
# @everywhere using Distributed

@info "after everywhere using, time from start: $(time()-time_start) [s]"

block_size = [64, 64, 64]

data_id = 1
show = false
taubin = true
taubin_n = 5
taubin_p1 = 0.4
taubin_p2 = -0.2

stepz = 1
stepxy = 1
do_crop = false
cropx = 200
cropy = 200
cropz = 400
# stepxy = 4
# block_size = [128, 128, 128]
# block_size = [128, 128, 128]
# block_size = [32, 32, 32]
# data_size1 = 128
# data_size1 = 256
# data_size1 = 512

# -------------------------------------------------------------------

data = LarSurf.Experiments.report_init_row(@__FILE__)
LarSurf.set_time_data(data)

# data["nprocs"] = nprocs()
# data["fcn"] = String(Symbol(fcni))
data["block size"] = block_size[1]

data["using done"] = time()-time_start
# segmentation = LarSurf.data234()
@info "Generate data..."
@info "time from start: $(time() - time_start) [s]"
mask_labels=["liver", "portalvein"]
# pth = Pio3d.datasets_join_path("medical/orig/3Dircadb1.$data_id/MASKS_DICOM/liver")

mask_label = "nrn"
threshold = 7000
# for mask_label in mask_labels

	pth = Pio3d.datasets_join_path("medical/orig/jatra_mikro_data/Nejlepsi_rozliseni_nevycistene")
	pth = Pio3d.datasets_join_path("medical/processed/corrosion_cast/nrn10.pklz")
	datap = Pio3d.read3d(pth)
	data3d_full = datap["data3d"]
	@info "raw data size=$(size(data3d_full))"
	data3d_full = data3d_full[1:stepz:end, 1:stepxy:end, 1:stepxy:end]
	if do_crop
		data3d_full = data3d_full[1:cropx, 1:cropy, 1:cropz]
	end
	    # round(size(data3d_full, 1) / target_size1)
	    # return data3d_full
	# segmentation = convert(Array{Int8, 3}, data3d_full .> threshold)
	segmentation = Int8.(data3d_full .> threshold)

	sz = [
		size(data3d_full, 1),
	 	size(data3d_full, 2),
	 	size(data3d_full, 3)
		]
	# @info "nvoxels=$(nvoxels)"
	voxelsize_mm = datap["voxelsize_mm"]
	voxelsize_mm[1] = voxelsize_mm[1] * stepz
	voxelsize_mm[2] = voxelsize_mm[2] * stepxy
	voxelsize_mm[3] = voxelsize_mm[3] * stepxy
	@info "voxelsize mm = $(voxelsize_mm), size = $(sz)"
	data = LarSurf.Experiments.report_add_data_info(data, segmentation, voxelsize_mm)
	# segmentation = LarSurf.generate_cube(data_size1; remove_one_pixel=true)
	@info "==== using done, data generated time from start: $(time() - time_start) [s]"
	data["data generated"] = time() - time_start

	@info "Setup..."
	setup_time = @elapsed LarSurf.lsp_setup(block_size;reference_time=time_start)
	println("setup time: $setup_time")
	@info "==== setup done, time from start: $(time() - time_start) [s]"
	data["setup done"] = time() - time_start
	# for wid in workers()
	#     # println("testing on $wid")
	#     ftr = @spawnat wid LarSurf._single_boundary3
	#     @test fetch(ftr) != nothing
	# end

	# @debug "Setup done"
	tmd = @timed larmodel = LarSurf.lsp_get_surface(segmentation; voxelsize=voxelsize_mm)
	val, tm, mem, gc = tmd
	println("Total time per $(mask_label): $tm")
	@info "==== time from start: $(time() - time_start) [s]"
	data["finished"] = time() - time_start
	ExSup.datetime_to_dict!(data)
	ExSup.add_to_csv(data, fn)

	V, FV = larmodel
	FVtri = LarSurf.triangulate_quads(FV)

	@JLD2.save "ircad_$(mask_label).jld2" V FV
	using ViewerGL
	println("=== ViewerGL readed")
	ViewerGL.VIEW([
	    ViewerGL.GLGrid(V, FVtri, ViewerGL.Point4d(1,1,1,0.1))
		ViewerGL.GLAxis(ViewerGL.Point3d(-1,-1,-1),ViewerGL.Point3d(1,1,1))
	])
	# FV = FVtri
	@JLD2.save "ircad_$(mask_label)_tri.jld2" V FVtri
	@info "smoothing"
	objlines = LarSurf.Lar.lar2obj(V, FVtri, "nrn_tri.obj")

	if taubin
		t = @elapsed Vs = LarSurf.Smoothing.smoothing_FV_taubin(V, FV, taubin_p1, taubin_p2, taubin_n)
		@info "smoothing taubin time", t
		# t = @elapsed FVtri = LarSurf.triangulate_quads(FV)

		if show
			ViewerGL.VIEW([
			    ViewerGL.GLGrid(Vs, FVtri, ViewerGL.Point4d(1, 0, 1, 0.1))
				ViewerGL.GLAxis(ViewerGL.Point3d(-1, -1, -1),ViewerGL.Point3d(1, 1, 1))
			])
		end
	else
		t = @elapsed Vs = LarSurf.Smoothing.smoothing_FV(V, FVtri, 0.6, 3)
		@info "smoothing time", t
	end
	@info "Smoothing numer of Vs: $(size(Vs))"
	@JLD2.save "ircad_$(mask_label)_sm.jld2" Vs FVtri
	# @JLD2.save "liver01tri.jld2" V FVtri
	objlines = LarSurf.Lar.lar2obj(Vs, FVtri, "nrn_tri_taubin.obj")
# end
# Plasm.view(val)


using Geotherm
using Test

@testset "Geotherm.jl" begin
    include("Geometry.jl")
    include("Integrate.jl")
    include("Geotherm.jl")
end


function save_sparse_matrix(fid, A::SparseMatrixCSC, name::String)

    n, m = size(A)
    Is, Js, Vs = findnz(A)

    fid["$(name)_Is"] = Int32[Is...]
    fid["$(name)_Js"] = Int32[Js...]
    fid["$(name)_Vs"] = Float64[Vs...]
    fid["$(name)_size"] = Int32[n, m]

    @info "Sparse Matrix '$(name)' saved to disk."
end

function load_sparse_matrix(fid, name::String)

    if isempty(fid["$(name)_Is"])
        Is = Int32[1]
        Js = Int32[1]
        Vs = Float64[0]
    else
        Is = fid["$(name)_Is"][:]
        Js = fid["$(name)_Js"][:]
        Vs = fid["$(name)_Vs"][:]
    end
    n, m = fid["$(name)_size"][:]

    @info "Loaded Sparse Matrix '$(name)'."

    return sparse(Is, Js, Vs, n, m)
end

function save_grid(fid, grd::Grid, name::String)

    # create group for grid
    create_group(fid, name)

    # save projection
    save_sparse_matrix(fid, grd.Proj, "$(name)/Proj")
    fid["$(name)/r0"] = grd.r0[:]
    fid["$(name)/r1"] = grd.r1[:]
    fid["$(name)/points"] = grd.points[:,:]
    fid["$(name)/spacing"] = Float64[grd.dx, grd.dy]
    fid["$(name)/_size"] = Int64[grd.Nk, grd.Nz, grd._Nx, grd._Ny, grd._Nbuffer]
    fid["$(name)/_NaNs"] = grd._nan_outside_boundaries[:,:]

    @info "Grid '$(name)' saved to disk."

end

function load_grid(fid, name::String)

    Proj = load_sparse_matrix(fid, "$(name)/Proj")
    r0 = fid["$(name)/r0"][:]
    r1 = fid["$(name)/r1"][:]
    points = fid["$(name)/points"][:,:]
    dx, dy = fid["$(name)/spacing"][:]
    Nk, Nz, Nx, Ny, Nbuffer = fid["$(name)/_size"][:]
    NaNs = fid["$(name)/_NaNs"][:,:]

    @info "Loaded Grid '$(name)'."

    return Grid(r0, r1, points, Proj, dx, dy, Nk, Nz, Nx, Ny, Nbuffer, NaNs)
end

function save_mirror(fid, M::Mirror, name::String)

    fid["$(name)/verts"] = M.verts[:,:]
    fid["$(name)/arms"] = M.arms[:,:,:]

end

function load_mirror(fid, name::String)

    verts = fid["$(name)/verts"][:,:]
    arms = fid["$(name)/arms"][:,:,:]

    return Mirror(verts, arms)
end

function save_ghost_data(fid, gd::GhostData, name::String)

    # create group
    create_group(fid, name)

    save_sparse_matrix(fid, gd.Proj, "$(name)/Proj")
    save_sparse_matrix(fid, gd.R, "$(name)/R")
    fid["$(name)/flip"] = gd.flip_factors[:,:]
end

function load_ghost_data(fid, name::String)

    Proj = load_sparse_matrix(fid, "$(name)/Proj")
    R = load_sparse_matrix(fid, "$(name)/R")
    flip_factors = fid["$(name)/flip"][:,:]

    return GhostData(Proj, R, flip_factors)
end

function boundary_setup(bdry_path::String, N1, N2, N3)

    count = 0

    # define the magnetic field
    psi, bx, by, bz = solovev_flux_function(0.1, 0.1, 0.3, 1.7, 10.0, downsep=[1, -0.561])
    Bp(r) = Float64[bx(r...), by(r...)]
    bp(r) = Bp(r) / norm(Bp(r))

    # values for flux surfaces
    psi_in = psi(1 + 0.28, 0)
    psi_out = psi(1 + 0.34, 0)
    psi_priv = psi(1, -(0.561 + 0.01))

    # find where the inner flux surface intersects y=0.1 on the outboard side
    bracket1 = [1.0, 0.1]
    bracket2 = [2.0, 0.1]
    r0 = rootfind_bisection(r->psi(r...), psi_in, bracket1, bracket2)

    # resolve inner boundary
    inner_chain_1, _ = trace_fieldline(r0, bp, r->psi(r...), 0.001) do r
        r[2] < 0
    end
    inner_chain_2, _ = trace_fieldline(inner_chain_1[:,end], bp, r->psi(r...), 0.001) do r
        r[2] > 0.1
    end
    skip = maximum([div(size(inner_chain_1)[2], N1), 1])
    inner_chain = hcat(inner_chain_1[:,1:skip:end-1], inner_chain_2[:,1:skip:end-1])
    Nin = size(inner_chain)[2]

    inner_range = 1:Nin
    count += Nin

    # find where outer flux surface intersects right diverter plate
    bracket1 = [1.0, -(0.561 + 0.04)]
    bracket2 = [2.0, -(0.561 + 0.04)]
    r0 = rootfind_bisection(r->psi(r...), psi_out, bracket1, bracket2)

    # resolve outer flux surface
    outer_chain, _ = trace_fieldline(r0, bp, r->psi(r...), 0.001) do r
        r[2] < -(0.561 + 0.04)
    end
    skip = maximum([div(size(outer_chain)[2], N2), 1])
    outer_chain = outer_chain[:,1:skip:end]
    Nout = size(outer_chain)[2]

    outer_range_1 = count+1:count+Nout
    count += Nout

    # find where outer flux surface intersects left diverter plate, add to outer_chain
    bracket1 = [0.0, -(0.561 + 0.04)]
    bracket2 = [1.0, -(0.561 + 0.04)]
    rl = rootfind_bisection(r->psi(r...), psi_out, bracket1, bracket2)
    outer_chain = hcat(outer_chain, rl)

    div_range_1 = count+1:count+1
    count += 1

    # find where private flux surface intersects left diverter plate
    bracket1 = [rl[1], -(0.561 + 0.04)]
    bracket2 = [1.0, -(0.561 + 0.04)]
    rl = rootfind_bisection(r->psi(r...), psi_priv, bracket1, bracket2)

    # trace private flux surface
    private_chain, _ = trace_fieldline(rl, r->-bp(r), r->psi(r...), 0.001) do r
        r[2] < -(0.561 + 0.04)
    end
    skip = maximum([div(size(private_chain)[2], N3), 1])
    private_chain = private_chain[:,1:skip:end-1]
    outer_chain = hcat(outer_chain, private_chain)
    Npriv = size(private_chain)[2]

    outer_range_2 = count+1:count+Npriv
    count += Npriv

    # find where private flux surface intersects right diverter plate
    bracket1 = [1.0, -(0.561 + 0.04)]
    bracket2 = [2.0, -(0.561 + 0.04)]
    rr = rootfind_bisection(r->psi(r...), psi_priv, bracket1, bracket2)
    outer_chain = hcat(outer_chain, rr)

    div_range_2 = count+1:count+1
    count += 1

    Nv = Nin + Nout + Npriv + 2

    inner_boundary = fill(false, Nv)
    inner_boundary[inner_range] .= true
    outer_boundary = fill(false, Nv)
    outer_boundary[outer_range_1] .= true
    outer_boundary[outer_range_2] .= true
    div_boundary = fill(false, Nv)
    div_boundary[div_range_1] .= true
    div_boundary[div_range_2] .= true

    M = Mirror([reverse(inner_chain, dims=2), outer_chain])

    fid = h5open(bdry_path, "w")
    save_mirror(fid, M, "Mirror")
    fid["inner_boundary"] = inner_boundary[:]
    fid["outer_boundary"] = outer_boundary[:]
    fid["div_boundary"] = div_boundary[:]
    close(fid)
end

function boundary_no_diverter(bdry_path::String, N1, N2, N3)

    count = 0

    # define the magnetic field
    psi, bx, by, bz = solovev_flux_function(0.1, 0.0, 0.3, 1.0, 10.0)
    Bp(r) = Float64[bx(r...), by(r...)]
    bp(r) = Bp(r) / norm(Bp(r))

    psi_in = psi(1 + 0.28, 0)
    psi_out = psi(1 + 0.34, 0)

    # find where the inner flux surface intersects y=0.1 on the outboard side
    bracket1 = [1.0, 0.1]
    bracket2 = [2.0, 0.1]
    r0 = rootfind_bisection(r->psi(r...), psi_in, bracket1, bracket2)

    # resolve inner boundary
    inner_chain_1, _ = trace_fieldline(r0, bp, r->psi(r...), 0.001) do r
        r[2] < 0
    end
    inner_chain_2, _ = trace_fieldline(inner_chain_1[:,end], bp, r->psi(r...), 0.001) do r
        r[2] > 0.1
    end
    skip = maximum([div(size(inner_chain_1)[2], N1), 1])
    inner_chain = hcat(inner_chain_1[:,1:skip:end-1], inner_chain_2[:,1:skip:end-1])
    Nin = size(inner_chain)[2]

    inner_range = 1:Nin
    count += Nin

    # find where the outer flux surface intersects y=0.1 on the outboard side
    bracket1 = [1.0, 0.1]
    bracket2 = [2.0, 0.1]
    r0 = rootfind_bisection(r->psi(r...), psi_out, bracket1, bracket2)

    # resolve outer boundary
    outer_chain_1, _ = trace_fieldline(r0, bp, r->psi(r...), 0.001) do r
        r[2] < 0
    end
    outer_chain_2, _ = trace_fieldline(outer_chain_1[:,end], bp, r->psi(r...), 0.001) do r
        r[2] > 0.1
    end
    skip = maximum([div(size(outer_chain_1)[2], N2), 1])
    outer_chain = hcat(outer_chain_1[:,1:skip:end-1], outer_chain_2[:,1:skip:end-1])
    Nout = size(outer_chain)[2]

    outer_range = count+1:count+Nout
    count += Nout

    Nv = Nin + Nout

    inner_boundary = fill(false, Nv)
    inner_boundary[inner_range] .= true
    outer_boundary = fill(false, Nv)
    outer_boundary[outer_range] .= true

    M = Mirror([reverse(inner_chain, dims=2), outer_chain])

    fid = h5open(bdry_path, "w")
    save_mirror(fid, M, "Mirror")
    fid["inner_boundary"] = inner_boundary[:]
    fid["outer_boundary"] = outer_boundary[:]
    close(fid)
end

function geometry_no_divertor(geo_path::String, bdry_path::String, Nx, Ny)

    fid = h5open(bdry_path, "r")
    M = load_mirror(fid, "Mirror")
    inner_boundary = fid["inner_boundary"][:]
    outer_boundary = fid["outer_boundary"][:]
    close(fid)

    grd = Grid(Nx, Ny, 1) do
        inside(x,y) = distance_to_mirror(x, y, M)[1] > -0.05
        r0 = Float64[0.5, -1]
        r1 = Float64[1.5, 1]
        inside, r0, r1
    end

    # compute penalization values as step functions
    pen = zeros(grd.Nk, 2)
    pen[:,1] = f_to_grid(grd) do x,y
        smoothstep(x, y, 0.02, 0.02, M[inner_boundary])
    end
    pen[:,2] = f_to_grid(grd) do x,y
        smoothstep(x, y, 0.02, 0.02, M[outer_boundary])
    end

    # K1 and K2 are operators for penalized forward-euler timesteps
    K1 = sparse(Diagonal(pen[:,1] .* pen[:,2]))
    K2 = 100 * (I - K1)

    gd_nmann = GhostData(M, grd)

    fid = h5open(geo_path, "w")
    save_grid(fid, grd, "Grid")
    save_sparse_matrix(fid, K1, "K1")
    save_sparse_matrix(fid, K2, "K2")
    save_ghost_data(fid, gd_nmann, "gd")
    close(fid)
end

function simple_geometry_setup(geo_path::String, bdry_path::String, Nx, Ny)

    fid = h5open(bdry_path, "r")
    M = load_mirror(fid, "Mirror")
    inner_boundary = fid["inner_boundary"][:]
    outer_boundary = fid["outer_boundary"][:]
    div_boundary = fid["div_boundary"][:]
    close(fid)

    grd = Grid(Nx, Ny, 1) do
        inside(x,y) = distance_to_mirror(x, y, M)[1] > -0.05
        r0 = Float64[0.5, -1]
        r1 = Float64[1.5, 1]
        inside, r0, r1
    end

    # compute penalization values as step functions
    pen = zeros(grd.Nk, 3)
    pen[:,1] = f_to_grid(grd) do x,y
        smoothstep(x, y, 0.02, 0.02, M[inner_boundary])
    end
    pen[:,2] = f_to_grid(grd) do x,y
        smoothstep(x, y, 0.02, 0.02, M[outer_boundary])
    end
    pen[:,3] = f_to_grid(grd) do x,y
        smoothstep(x, y, 0.02, 0.02, M[div_boundary])
    end

    # K1 and K2 are operators for penalized forward-euler timesteps
    K1 = sparse(Diagonal(pen[:,1] .* pen[:,2] .* pen[:,3]))
    K2 = 100 * (I - K1)

    gd_nmann = GhostData(M, grd)

    fid = h5open(geo_path, "w")
    save_grid(fid, grd, "Grid")
    save_sparse_matrix(fid, K1, "K1")
    save_sparse_matrix(fid, K2, "K2")
    save_ghost_data(fid, gd_nmann, "gd")
    close(fid)
end

function simple_physics_setup(w0, psi0, L_w, L_psi, init_path::String, geo_path::String)

    fid = h5open(geo_path, "r")
    grd = load_grid(fid, "Grid")
    gd = load_ghost_data(fid, "gd")
    close(fid)

    psi_func, bx, by, bz = solovev_flux_function(0.1, 0.1, 0.3, 1.7, 10.0, downsep=[1, -0.561])
    psi_mid = psi_func(0.685, 0)
    dp_w = abs(ForwardDiff.derivative(u -> psi_func(u,0), 0.65)) * L_w
    dp_psi = dp_w * L_psi / L_w

    w_vec = f_to_grid(grd) do x,y
        w0*tanh((psi_func(x,y) - psi_mid) / dp_w)
    end
    psi_vec = f_to_grid(grd) do x,y
        psi0*tanh((psi_func(x,y) - psi_mid) / dp_psi)
    end

    w = hcat(w_vec, w_vec, w_vec)
    psi = hcat(psi_vec, psi_vec, psi_vec)

    # compute derivatives
    Minv = stencil1d(3)
    Dx = derivative_matrix(1, 0, Minv, Minv, grd) * 0.3
    Dy = derivative_matrix(0, 1, Minv, Minv, grd) * 0.3
    Dxx = derivative_matrix(2, 0, Minv, Minv, grd) * (0.3)^2
    Dyy = derivative_matrix(0, 2, Minv, Minv, grd) * (0.3)^2

    # solve vorticity equation
    phi = solve_vorticity_eqn(zeros(grd.Nk), w[:,2], Dxx, Dyy, gd)

    fid = h5open(init_path, "w")
    save_sparse_matrix(fid, Dx, "Dx")
    save_sparse_matrix(fid, Dy, "Dy")
    save_sparse_matrix(fid, Dxx, "Dxx")
    save_sparse_matrix(fid, Dyy, "Dyy")
    fid["w"] = w[:,:]
    fid["phi"] = phi[:]
    fid["psi"] = psi[:,:]
    close(fid)
end

function simulate(Nt, k_w, k_psi, am, dt, output_path, init_path, geo_path)

    fid = h5open(geo_path, "r")
    grd = load_grid(fid, "Grid")
    K1 = load_sparse_matrix(fid, "K1")
    K2 = load_sparse_matrix(fid, "K2")
    gd = load_ghost_data(fid, "gd")
    close(fid)

    fid = h5open(init_path, "r")
    Dx = load_sparse_matrix(fid, "Dx")
    Dy = load_sparse_matrix(fid, "Dy")
    Dxx = load_sparse_matrix(fid, "Dxx")
    Dyy = load_sparse_matrix(fid, "Dyy")
    w = fid["w"][:,:]
    phi = fid["phi"][:]
    psi = fid["psi"][:,:]
    close(fid)

    fid = h5open(output_path, "w")
    save_grid(fid, grd, "Grid")
    create_dataset(fid, "w", datatype(Float64), dataspace(grd.Nk*grd.Nz,Nt), chunk=(grd.Nk*grd.Nz,1))
    create_dataset(fid, "phi", datatype(Float64), dataspace(grd.Nk*grd.Nz,Nt), chunk=(grd.Nk*grd.Nz,1))
    create_dataset(fid, "psi", datatype(Float64), dataspace(grd.Nk*grd.Nz,Nt), chunk=(grd.Nk*grd.Nz,1))
    fid["w"][:,1] = w[:,2]
    fid["phi"][:,1] = phi[:]
    fid["psi"][:,1] = psi[:,2]
    close(fid)

    Nskip = 1
    p = Progress(Nskip*(Nt-1), "Running simple model... ")
    for t=2:Nt
        for _=1:Nskip
            leapfrog!(w, psi, phi, Dx, Dxx, Dy, Dyy, K1, K2, gd, dt, am, k_psi, k_w)
            next!(p)
        end

        fid = h5open(output_path, "r+")
        fid["w"][:,t] = w[:,2]
        fid["phi"][:,t] = phi[:]
        fid["psi"][:,t] = psi[:,2]
        close(fid)
    end

end

#
#
#
# function simple_geometry_setup(path::String, Nx, Ny)
#
#     # define the magnetic field
#     psi, bx, by, bz = solovev_flux_function(0.1, 0.1, 0.3, 1.7, 10.0, downsep=[1, -0.561])
#
#     # values for flux surfaces
#     psi_in = psi(1 - 0.28, 0)
#     psi_out = psi(1 - 0.34, 0)
#     psi_priv = psi(1, -(0.561 + 0.01))
#
#     # define barriers for flux surfaces
#     inner_flux = Barrier() do
#         func(x,y) = (y > -0.561) ? psi(x,y) : abs(psi(x,y))
#         rmap(x,y) = trace_reflection(x, y, psi, psi_in, 0.001)
#         func, psi_in, rmap, 1
#     end
#
#     outer_flux = Barrier() do
#         func(x,y) = (norm([x-1,y+0.6]) > 0.05) ? psi(x,y) : psi_out + psi_priv - psi(x,y)
#         rmap(x,y) = trace_reflection(x, y, func, psi_out, 0.001)
#         func, psi_out, rmap, -1
#     end
#
#     # define barrier for divertor target plates
#     target = Barrier() do
#         func(x,y) = y
#         val = -(0.561 + 0.04)
#         rmap(x,y) = x, 2*val - y
#         func, val, rmap, 1
#     end
#
#     # compute step function widths
#     dr = 0.01
#     dp_in = abs(ForwardDiff.derivative(u -> psi(u,0), 0.7)) * dr
#     dp_out = abs(ForwardDiff.derivative(u -> psi(u,0), 0.6)) * dr
#     dp_priv = abs(ForwardDiff.derivative(u -> psi(1,u), -(0.561 + 0.05))) * dr
#
#     # create Grid
#     deltas = Float64[dp_in, 0.5*(dp_out + dp_priv), dr]
#     barrs = [inner_flux, outer_flux, target]
#     grd = Grid(Nx, Ny, 1) do
#         inside(x,y) = check_if_inside(x, y, 10*deltas, barrs)
#         r0 = Float64[0.5, -1]
#         r1 = Float64[1.5, 1]
#         inside, r0, r1
#     end
#
#     # compute penalization values as step functions
#     pen = zeros(grd.Nk, 3)
#     for i=1:3
#         pen[:,i] = f_to_grid(grd) do x,y
#             smoothstep(x, y, deltas[i], barrs[i])
#         end
#     end
#
#     # K1 and K2 are operators for penalized forward-euler timesteps
#     K1 = sparse(Diagonal(pen[:,1] .* pen[:,2] .* pen[:,3]))
#     K2 = 100 * (I - K1)
#
#     gc_nmann = GhostConditions(barrs, grd)
#     gc = swap_sign(gc_nmann, 1, 2, 3)
#
#     fid = h5open(path, "w")
#     save_grid(fid, grd, "Grid")
#     save_sparse_matrix(fid, K1, "K1")
#     save_sparse_matrix(fid, K2, "K2")
#     save_ghost_conditions(fid, gc_nmann, "gc")
#     close(fid)
# end
#
#
# function simple_physics_setup(w0, psi0, L_w, L_psi, init_path::String, geo_path::String)
#
#     fid = h5open(geo_path, "r")
#     grd = load_grid(fid, "Grid")
#     gc = load_ghost_conditions(fid, "gc")
#     close(fid)
#
#     psi_func, bx, by, bz = solovev_flux_function(0.1, 0.1, 0.3, 1.7, 10.0, downsep=[1, -0.561])
#     psi_mid = psi_func(0.65, 0)
#     dp_w = abs(ForwardDiff.derivative(u -> psi_func(u,0), 0.65)) * L_w
#     dp_psi = dp_w * L_psi / L_w
#
#     w_vec = f_to_grid(grd) do x,y
#         w0*tanh((psi_func(x,y) - psi_mid) / dp_w)
#     end
#     psi_vec = f_to_grid(grd) do x,y
#         psi0*tanh((psi_func(x,y) - psi_mid) / dp_psi)
#     end
#
#     w = hcat(w_vec, w_vec, w_vec)
#     psi = hcat(psi_vec, psi_vec, psi_vec)
#
#     # compute derivatives
#     Minv = stencil1d(3)
#     Dx = derivative_matrix(1, 0, Minv, Minv, grd) * 0.3
#     Dy = derivative_matrix(0, 1, Minv, Minv, grd) * 0.3
#     Dxx = derivative_matrix(2, 0, Minv, Minv, grd) * (0.3)^2
#     Dyy = derivative_matrix(0, 2, Minv, Minv, grd) * (0.3)^2
#
#     # solve vorticity equation
#     phi = solve_vorticity_eqn(zeros(grd.Nk), w[:,2], Dxx, Dyy, gc)
#
#     fid = h5open(init_path, "w")
#     save_sparse_matrix(fid, Dx, "Dx")
#     save_sparse_matrix(fid, Dy, "Dy")
#     save_sparse_matrix(fid, Dxx, "Dxx")
#     save_sparse_matrix(fid, Dyy, "Dyy")
#     fid["w"] = w[:,:]
#     fid["phi"] = phi[:]
#     fid["psi"] = psi[:,:]
#     close(fid)
# end


view_x(d::AbstractDataContainer, indices) = view(d.X, indices)
view_y(::AbstractLikelihood, y::AbstractVector, i::AbstractVector) = view(y, i)
function view_y(l::AbstractLikelihood, d::AbstractDataContainer, i::AbstractVector)
    return view_y(l, output(d), i)
end
function view_y(l::AbstractLikelihood, d::MODataContainer, i::AbstractVector)
    return view_y.(l, output(d), Ref(i))
end
function view_y(
    l::Tuple{Vararg{<:AbstractLikelihood}}, d::MODataContainer, i::AbstractVector
)
    return view_y.(l, output(d), Ref(i))
end

# Verify that the data is self-consistent and consistent with the likelihood ##
function check_data!(y::AbstractArray, likelihood::Union{Distribution,AbstractLikelihood})
    return treat_labels!(y, likelihood)
end

function wrap_data(X, y, likelihood::AbstractLikelihood)
    y = check_data!(y, likelihood)
    return wrap_data(X, y)
end

function wrap_data(X, y, likelihoods::Tuple{Vararg{<:AbstractLikelihood}})
    ys = map(check_data!, y, likelihoods)
    return wrap_modata(X, ys)
end


module FakeModule
    export f, h

    f(x) = 3x
    g(x) = x^2
    h(x) = -x
end