function layersGroundTruth1D(gt::DataGroundTruth)
	PL = []
	push!(PL, gt.plt_lyr_cluster[1])
	push!(PL, gt.plt_lyr_cluster[2])
	PL
end

function layersGroundTruth2D(id::Int, gt::DataGroundTruth)
	PL = Gadfly.Context[]
	PL = union(PL, gt.plt_lyr_cluster[id])
	PL
end
function layersGroundTruth2D(ids::Vector{Int}, gt::DataGroundTruth)
	PL = Gadfly.Context[]
	for id in ids
		PL = union(PL, gt.plt_lyr_cluster[id])
	end
	PL
end

function layersExpertBelief1D(cs::ClassificationSystem;
															margdim=1,
															colors::NothingUnion{Dict{Int, String}}=nothing )
	# use defaults
	colors = colors == nothing ? cs.colors : colors

  # prep data
	pr, cl = BallTreeDensity[], String[]
	for p in cs.expertBelief
		push!(pr, marginal(p[2],[margdim]))
		push!(cl, colors[p[1]]) # get accompanying color
	end

	# plot and return layers
	pl_init = plotKDE(pr,c=cl);
	pl_init.layers
end

function layersExpertBelief2D(id::Int, cs::ClassificationSystem;
															margdim=1,
															colors::NothingUnion{Dict{Int, String}}=nothing )
	# use defaults
	colors = colors == nothing ? cs.colors : colors

  # prep data
	pr, cl = BallTreeDensity[], String[]
	for p in cs.expertBelief
		push!(pr, marginal(p[2],[margdim]))
		push!(cl, colors[p[1]]) # get accompanying color
	end

	# plot and return layers
	pl_init = plotKDE(pr,c=cl);
	pl_init.layers
end

function layersCurrentBelief1D(cs::ClassificationSystem;
															margdim=1,
															colors::NothingUnion{Dict{Int, String}}=nothing )
	# use defaults
	colors = colors == nothing ? cs.colors : colors

  # prep data
	pr, cl = BallTreeDensity[], String[]
	for p in cs.currentBelief
		push!(pr, marginal(p[2],[margdim]))
		push!(cl, colors[p[1]]) # get accompanying color
	end

	# plot and return layers
	pl_init = plotKDE(pr,c=cl);
	pl_init.layers
end

function plotUtil1D(;
		sampledata::NothingUnion{SampleData}=nothing,
		groundtruth::NothingUnion{DataGroundTruth}=nothing,
		cs::NothingUnion{ClassificationSystem}=nothing,
		expertcolor::NothingUnion{Dict{Int,String}}=nothing,
		drawcurrent::Bool=false,
		currentcolor::NothingUnion{Dict{Int,String}}=nothing
		)

		PL = []
		PL = groundtruth == nothing ? PL : union(PL, layersGroundTruth1D(groundtruth))
		# push!(PL, Guide.title("True classification for intersecting clusters in 1 dimension"))

		PL = cs == nothing ? PL : union(PL, layersExpertBelief1D(cs, colors=expertcolor))

		PL = (cs == nothing || !drawcurrent) ? PL : union(PL, layersCurrentBelief1D(cs, colors=currentcolor))

		# layer(x=data.samples,y=zeros(size(data.samples)),Geom.point,Theme(default_color=colorant"gray")),

		plot(PL...)
end

function plotUtil2D(id::Union{Int, Vector{Int}};
		sampledata::NothingUnion{SampleData}=nothing,
		groundtruth::NothingUnion{DataGroundTruth}=nothing,
		cs::NothingUnion{ClassificationSystem}=nothing,
		expertcolor::NothingUnion{Dict{Int,String}}=nothing,
		drawcurrent::Bool=false,
		currentcolor::NothingUnion{Dict{Int,String}}=nothing
		)

		PL = []
		PL = groundtruth == nothing ? PL : union(PL, layersGroundTruth2D(id, groundtruth))
		# push!(PL, Guide.title("True classification for intersecting clusters in 1 dimension"))

		PL = cs == nothing ? PL : union(PL, layersExpertBelief2D(id, cs, colors=expertcolor))
		#
		# PL = (cs == nothing || !drawcurrent) ? PL : union(PL, layersCurrentBelief1D(cs, colors=currentcolor))

		plot(PL...)
end

function plotEMStatus(params, stats)
  plfrac = plot(
  layer(x=1:params.EMiters, y=stats.POPFRAC[1,:], Geom.line, Theme(default_color=colorant"blue" )),
  layer(x=1:params.EMiters, y=stats.POPFRAC[2,:], Geom.line, Theme(default_color=colorant"red"  )),
  # layer(x=1:params.EMiters, y=abs(dbg.INDV_MISASSIGN_C[1] - dbg.INDV_MISASSIGN_C[2]), Geom.line, Theme(default_color=colorant"magenta"  )),
  Guide.title("Population fraction estimates"),
  Guide.ylabel("%")
  )

	plkl = plot(
	layer(x=1:params.EMiters, y=abs.(stats.SEQKLDIVERG[1][:]), Geom.line, Theme(default_color=colorant"blue" )),
	layer(x=1:params.EMiters, y=abs.(stats.SEQKLDIVERG[2][:]), Geom.line, Theme(default_color=colorant"red"  )),
	# layer(x=1:params.EMiters, y=abs(dbg.INDV_MISASSIGN_C[1] - dbg.INDV_MISASSIGN_C[2]), Geom.line, Theme(default_color=colorant"magenta"  )),
	Guide.title("Abs sequential KL divergence, | D(p_{k+1} || p_{k}) |"),
	)

	vstack(plfrac, plkl)
end

function plotClassificationStats(params, dbg)
  pl_accur = plot(
  layer(x=1:params.EMiters, y=dbg.ACCUR_C[1], Geom.line, Theme(default_color=colorant"blue" )),
  layer(x=1:params.EMiters, y=dbg.ACCUR_C[2], Geom.line, Theme(default_color=colorant"red"  )),
  Guide.title("Absolute percentage error in sample count among classifications"),
  Guide.ylabel("%")
  );

  pl_rel_accur = plot(
  layer(x=1:params.EMiters, y=dbg.REL_ACCUR_C[1], Geom.line, Theme(default_color=colorant"blue" )),
  layer(x=1:params.EMiters, y=dbg.REL_ACCUR_C[2], Geom.line, Theme(default_color=colorant"red"  )),
  Guide.title("Relative percentage error in sample count among classifications"),
  Guide.ylabel("%")
  );

  # pl_indvmissassign = plot(
  # layer(x=1:params.EMiters, y=dbg.INDV_MISASSIGN_C[1], Geom.line, Theme(default_color=colorant"blue" )),
  # layer(x=1:params.EMiters, y=dbg.INDV_MISASSIGN_C[2], Geom.line, Theme(default_color=colorant"red"  )),
  # # layer(x=1:params.EMiters, y=abs(dbg.INDV_MISASSIGN_C[1] - dbg.INDV_MISASSIGN_C[2]), Geom.line, Theme(default_color=colorant"magenta"  )),
  # Guide.title("Common percentage error count among classifications (unweighted)"),
  # Guide.ylabel("%")
  # );

  vstack(pl_accur, pl_rel_accur)
end


module TheFix
    using REPL

    function cleanse(expr; safe::Bool=true)
        return expr
    end

    function cleanse(expr::Symbol; safe::Bool = true)
        try
            Main.eval(expr)
        catch ex
            expr = replace(expr, ex; safe = safe)
        end
        return expr
    end

    function cleanse(expr::Expr; safe::Bool=true)
        start = expr.head == :(=) && expr.args[1] isa Symbol ? 2 : 1
        for i in start:length(expr.args)
            expr.args[i] = cleanse(expr.args[i]; safe = safe)
        end
        try
            Main.eval(expr)
        catch ex
            expr = replace(expr, ex; safe = safe)
        end
        return expr
    end

    function replace(expr::Symbol, ex::UndefVarError; safe::Bool=true)
        if !(first(string(expr)) == '.' && isdefined(Main, Symbol(string(expr)[2:end])))
            possibilities = REPL.levsort(String(ex.var), REPL.accessible(Main))
            if safe
                i = 1
                while i ≤ min(length(possibilities), 3)
                    if i == 1
                        @info "Couldn't find $(ex.var). Did you mean $(possibilities[i])?"
                    else
                        @info "Did you mean $(possibilities[i])?"
                    end
                    answer = lowercase(strip(readline(stdin)))
                    if isempty(answer) || answer == "y" || answer == "yes"
                        expr = Meta.parse(possibilities[i])
                        break
                    elseif answer == "n" || answer == "no"
                        i += 1
                    else
                        println(stdout, "Unrecognized answer. Answer `y` or `n`.")
                    end
                end
                if i > min(length(possibilities), 3)
                    @info "Couldn't find a fix. What did you mean?"
                    expr = Meta.parse(strip(readline(stdin)))
                end
            else
                expr = Meta.parse(first(possibilities))
            end
            @info "Fixing $ex with $expr."
        end
        return expr
    end

    function replace(expr::Expr, ex::DomainError; safe::Bool=true)
        if occursin("complex", ex.msg) || occursin("NaN result for non-NaN input.", ex.msg)
            for (i, arg) in enumerate(expr.args)
                if Main.eval(arg) isa Number && Main.eval(arg) == ex.val
                    expr.args[i] = :(Complex($arg))
                elseif arg isa Expr
                    if arg.head == :tuple
                        for (j, argt) in enumerate(arg.args)
                            if any(x -> x == ex.val, Main.eval(argt))
                                arg.args[j] = :(Complex.($argt))
                            end
                        end
                    end
                end
            end
            @info "Fixing $ex with $expr."
        elseif occursin("negative power", ex.msg)
            for (i, arg) in enumerate(expr.args)
                if arg == :(.^) || i == 1
                    continue
                elseif Main.eval(arg) isa Number && Main.eval(arg) == ex.val
                    expr.args[i] = :(float($arg))
                elseif Main.eval(arg) isa AbstractArray && any(x -> x == ex.val, Main.eval(arg))
                    expr.args[i] = :(float.($arg))
                elseif Main.eval(arg) isa Tuple && any(x -> x == ex.val, Main.eval(arg))
                    expr.args[i] = :(float.($arg))
                end
            end
            @info "Fixing $ex with $expr."
        else
            @warn "Exception $ex not implemented."
        end
        return expr
    end

    function replace(expr::Expr, ex::OverflowError; safe::Bool=true)
        if occursin("factorial", ex.msg)
            for (i, arg) in enumerate(expr.args)
                if Main.eval(arg) isa Integer
                    expr.args[i] = :(big($arg))
                elseif arg isa Expr
                    if arg.head == :tuple
                        for (j, argt) in enumerate(arg.args)
                            arg.args[j] = :(big.($argt))
                        end
                    end
                end
            end
            @info "Fixing $ex with $expr."
        elseif occursin("gcd", ex.msg)
            for (i, arg) in enumerate(expr.args)
                if Main.eval(arg) isa Integer
                    expr.args[i] = :(widen($arg))
                elseif arg isa Expr
                    if arg.head == :tuple
                        for (j, argt) in enumerate(arg.args)
                            arg.args[j] = :(widen.($argt))
                        end
                    end
                end
            end
            @info "Fixing $ex with $expr."
        elseif occursin("checked", ex.msg) || occursin("overflow", ex.msg)
            for (i, arg) in enumerate(expr.args)
                if Main.eval(arg) isa Integer || Main.eval(arg) isa Rational
                    expr.args[i] = :(widen($arg))
                elseif Main.eval(arg) isa AbstractArray
                    expr.args[i] = :(widen.($arg))
                elseif arg isa Expr
                    if arg.head == :tuple
                        for (j, argt) in enumerate(arg.args)
                            arg.args[j] = :(widen.($argt))
                        end
                    end
                end
            end
            @info "Fixing $ex with $expr."
        else
            @warn "Exception $ex not implemented."
        end
        return expr
    end

    function replace(expr::Expr, ex::DivideError; safe::Bool=true)
        for (i, arg) in enumerate(expr.args)
            if arg == :div
                while length(expr.args) > 3
                    pop!(expr.args)
                end
                expr.args[i] = :(/)
            elseif arg == :(÷) || arg == :cld || arg == :fld
                expr.args[i] = :(/)
            elseif arg == :rem || arg == :mod
                if length(expr.args) > 3
                    x = expr.args[2]
                    y = expr.args[3]
                    r = expr.args[4]
                    expr = :($x - $y*round($x/$y, $r))
                    break
                else
                    x = expr.args[2]
                    y = expr.args[3]
                    expr = :($x - $y*($x/$y))
                    break
                end
            elseif arg == :(%)
                x = expr.args[2]
                y = expr.args[3]
                expr = :($x - $y*($x/$y))
                break
            elseif arg == :divrem
                if length(expr.args) > 3
                    x = expr.args[2]
                    y = expr.args[3]
                    r = expr.args[4]
                    expr = :(($x/$y, $x - $y*round($x/$y, $r)))
                    break
                else
                    x = expr.args[2]
                    y = expr.args[3]
                    expr = :(($x/$y, $x - $y*($x/$y)))
                    break
                end
            end
        end
        @info "Fixing $ex with $expr."
        return expr
    end

    function replace(expr::Expr, ex::BoundsError; safe::Bool=true)
        if length(expr.args) == 2
            arg1 = expr.args[1]
            arg2 = expr.args[2]
            if Main.eval(arg2) isa Integer
                expr.args[2] = :(clamp($arg2, extrema(eachindex($arg1))...))
            elseif Main.eval(arg2) isa AbstractUnitRange
                expr.args[2] = :(intersect($arg2, eachindex($arg1)))
            end
        else
            arg1 = expr.args[1]
            for i in 2:length(expr.args)
                argi = expr.args[i]
                l, r = Main.eval(:(extrema(axes($arg1, $(i-1)))))
                idx = Main.eval(argi)
                if idx isa Integer
                    if first(idx) < l || last(idx) > r
                        expr.args[i] = :(clamp($argi, extrema(axes($arg1, $(i-1)))...))
                    end
                elseif idx isa AbstractUnitRange
                    if first(idx) < l || last(idx) > r
                        expr.args[i] = :(intersect($argi, axes($arg1, $(i-1))))
                    end
                end
            end
        end
        @info "Fixing $ex with $expr."
        return expr
    end

    function replace(expr, ex::Exception; safe::Bool=true)
        @warn "Exception $ex not implemented."
        return expr
    end

    function display_error(io::IO, stack::Vector)
        printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
        bt = Any[ (x[1], Base.scrub_repl_backtrace(x[2])) for x in stack ]
        Base.show_exception_stack(IOContext(io, :limit => true), bt)
    end

    """
        TheFix.@safeword(fix, safe)

    Create a safe word `fix` that may be used to correct common errors in the REPL.
    This creates a struct `fix` with no fields and the fix occurs by showing it.
    The Boolean variable `safe` indicates whether or not the user requests confirmation.

    # Examples
    ```jldoctest
    julia> TheFix.@safeword fix true

    julia> sine(4)
    ERROR: UndefVarError: sine not defined
    Stacktrace:
     [1] top-level scope at REPL[3]:1

    julia> fix
    [ Info: Couldn't find sine. Did you mean sin?
    y
    [ Info: Fixing UndefVarError(:sine) with sin.

    julia> sin(4)
    -0.7568024953079282

    julia> TheFix.@safeword FIX false

    julia> logarithm(3)
    ERROR: UndefVarError: logarithm not defined
    Stacktrace:
     [1] top-level scope at REPL[7]:1

    julia> FIX
    [ Info: Fixing UndefVarError(:logarithm) with log.

    julia> log(3)
    1.0986122886681098
    ```
    """
    macro safeword(fix, safe)
        return esc(quote
            export $fix

            struct $fix end

            function Base.show(io::IO, f::Type{$fix})
                file = open(TheFix.REPL.find_hist_file(), read = true, write = true)
                str = read(file, String)
                itr = findlast("# mode: julia", str)
                itr = findprev("# mode: julia", str, first(itr))
                nxt = itr[end] + 3
                lst = first(findnext("# time:", str, nxt)) - 2
                expr = TheFix.cleanse(Meta.parse(str[nxt:lst]); safe = $safe)
                try
                    printstyled(io, "\n"*TheFix.REPL.JULIA_PROMPT, bold=true; color=202)
                    println(io, string(expr))
                    show(io, "text/plain", Main.eval(expr))
                    hist = Base.active_repl.mistate.current_mode.hist
                    !isempty(hist.history) && isequal(:julia, last(hist.modes)) && string(expr) == last(hist.history) && return
                    push!(hist.modes, :julia)
                    push!(hist.history, string(expr))
                    TheFix.REPL.history_reset_state(hist)
                    entry = """
                    # time: $(Libc.strftime("%Y-%m-%d %H:%M:%S %Z", time()))
                    # mode: julia
                    $(replace(string(expr), r"^"ms => "\t"))
                    """
                    seekend(file)
                    print(file, entry)
                    flush(file)
                catch ex
                    Base.invokelatest(TheFix.display_error, io, Base.catch_stack())
                end
            end
        end)
    end
end # module


@testset "Testing Pooling" begin
    kernel_sizes = [(2, 2), (3, 3), (7, 7)]
    paddings = [0, 1, 3, 5]
    strides = [1, 2, 3]

    # Occaisonally, OneDNN and Flux will disagree on which index is the "most popular"
    # among its filter window.
    #
    # There seem to be cases where either one or the other returns slightly incorrect
    # answers, based on two inputs that are pretty close.
    #
    # So maybe this has to do to some weird rouding issue? I'm not sure.
    #
    # For testing purposes, we just make sure to introduce enough delta between the
    # elements in the array so this confusion doesn't occus.
    Random.seed!(12345)
    x = zeros(Float32, 8, 8, 8, 4)
    nums = Set(eachindex(x))
    for i in eachindex(x)
        v = rand(nums)
        x[i] = v
        delete!(nums, v)
    end
    @test isempty(nums)

    X = OneDNN.Memory(x)

    iter = Iterators.product(kernel_sizes, paddings, strides)
    for _tup in iter
        kernel = _tup[1]
        padding = _tup[2]
        stride = _tup[3]

        @show (kernel, padding, stride)

        # Test on both MaxPool and MeanPool
        fn_pairs = [
            (OneDNN.MaxPool, Flux.MaxPool), (OneDNN.InclusiveMeanPool, Flux.MeanPool)
        ]
        for (fn_onednn, fn_flux) in fn_pairs
            pool_onednn = fn_onednn(kernel; padding = padding, strides = stride)
            pool_flux = fn_flux(kernel; pad = padding, stride = stride)

            # inference
            @test isapprox(OneDNN.materialize(pool_onednn(X)), pool_flux(x))

            # training
            out_onednn, back_onednn = Zygote._pullback(pool_onednn, X)
            out_flux, back_flux = Zygote._pullback(pool_flux, x)

            @test isapprox(OneDNN.materialize(out_onednn), out_flux)

            dx_onednn = back_onednn(out_onednn)
            dx_flux = back_flux(out_flux)

            @test dx_onednn[1] === dx_flux[1] === nothing
            @test isapprox(OneDNN.materialize(dx_onednn[2]), dx_flux[2])
        end
    end
end


include("base.jl")
include("blas.jl")
include("linalg.jl")
include("statistics.jl")
include("nnlib.jl")
include("sparse.jl")
include("mapreduce.jl")
include("bennett.jl")


################################
## definition of Timenum type ##
################################

## constraints:
## - DataArray, where individual entries are subtype of Date
## - idx and vals must have same number of observations
## - values must be numeric

type Timenum{T} <: AbstractTimenum
    vals::DataFrame
    idx::Array{T, 1}

    function Timenum(vals::DataFrame, idx::Array{T, 1})
        chkIdx(idx)
        chkNumDf(vals)
        if(size(vals, 1) != length(idx))
            if (length(idx) == 0) | (size(vals, 1) == 0)
                df = convert(DataFrame, rand(2, 2))
                return new(df[1:2, [false, false]], Array{T, 1}[])
            end
            error(length(idx), " idx entries, but ", size(vals, 1), " rows of data")
        end
        return new(vals, idx)
    end
end

function Timenum{T}(vals::DataFrame, idx::Array{T, 1})
    return Timenum{T}(vals, idx)
end


using AbstractGPs

using Distributions: Poisson, LogNormal
using SampleChainsDynamicHMC
using Soss: Soss, For
using Test
using Turing: Turing

@testset "PPLs" begin
    include("turing.jl")
    println(" ")
    @info "Ran Turing tests"

    include("soss.jl")
    println(" ")
    @info "Ran Soss tests"
end


# extract all sites
const LIQUID_STR = (:liquid,:LIQUID,:L,:l)

"""
    is_liquid(x::Union{Symbol,String})

Returns `true` if the symbol is in `(:liquid,:LIQUID,:L,:l)`.

If a string is passed, it is converted to symbol.
"""
is_liquid(sym::Symbol) = sym in LIQUID_STR
is_liquid(str::String) = is_liquid(Symbol(str))

const VAPOUR_STR = (:vapor,:VAPOR,:VAPOUR,:vapour,:g,:G,:v,:V,:gas,:GAS)

"""
    is_vapour(x::Union{Symbol,String})

Returns `true` if the symbol is in `(:vapor,:VAPOR,:VAPOUR,:vapour,:g,:G,:v,:V,:gas,:GAS)`.

If a string is passed, it is converted to symbol.
"""
is_vapour(sym::Symbol) = sym in VAPOUR_STR
is_vapour(str::String) = is_vapour(Symbol(str))

const SUPERCRITICAL_STR = (:sc,:SC,:supercritical,:SUPERCRITICAL)

"""
    is_supercritical(x::Union{Symbol,String})

Returns `true` if the symbol is in `(:sc,:SC,:supercritical,:SUPERCRITICAL)`.

If a string is passed, it is converted to symbol.
"""
is_supercritical(sym::Symbol) = sym in SUPERCRITICAL_STR
is_supercritical(str::String) = is_vapour(Symbol(str))

"""
    ∑(iterator)

equivalent to `sum(iterator,init=0.0)`.

"""
function ∑(iterator) #not collecting is faster
    #_0 = ifelse(length(iterator)>0,zero(first(iterator)),0.0)
    _0 = 0.0
    return reduce(Base.add_sum,iterator,init=_0)
end

"""
    ∑(fn,iterator)

equivalent to `sum(fniterator,init=0.0)`.

"""
function ∑(fn,iterator) #not collecting is faster
    #_0 = ifelse(length(iterator)>0,zero(first(iterator)),0.0)
    _0 = 0.0
    return mapreduce(fn,Base.add_sum,iterator,init=_0)
end

"""
    xlogx(x::Number)
Return `x * log(x)` for `x ≥ 0`, handling ``x = 0`` by taking the downward limit.

copied from LogExpFunctions.jl
"""
function xlogx(x::Number)
    result = x * log(x)
    ifelse(iszero(x), zero(result), result)
end

struct FractionVector{T,V} <: AbstractVector{T}
    vec::V
    val::T
end
#=

Fraction Vector
useful when expressing fractions in n-1 components.
the last component is calculated at build time.
it allocates less than creating a new vector or appending.
=#
##
function FractionVector(v::AbstractVector)
    a = one(eltype(v))
    any(x->x<0,v) && throw(DomainError(v,"all elements of a fraction vector should be positive."))
    a -=sum(v)
    a < 0 && throw(DomainError(a,"the values of the input vector add to more than one"))
    return FractionVector(v,a)
end

function FractionVector(v::Real)
    a = one(v)
    (v < zero(v)) && throw(DomainError(v,"all elements of a fraction vector should be positive."))
    a -= v
    a < 0 && throw(DomainError(a,"the values of the input vector add to more than one"))
    return FractionVector(v,a)
end

@inline Base.eltype(v::FractionVector{T}) where T = T
@inline Base.length(v::FractionVector)::Int = Base.length(v.vec) + 1

@inline function Base.length(v::FractionVector{T,<:Real})::Int where T
    return 2
end

@inline Base.size(v::FractionVector) = (length(v),)
@inline function Base.getindex(v::FractionVector,i::Int)
    @boundscheck checkbounds(v, i)
    return ifelse(length(v)==i,v.val,v.vec[min(length(v.vec),i)])
end

@inline function Base.getindex(v::FractionVector{T,<:Real},i::Int) where T
    @boundscheck checkbounds(v, i)
    return ifelse(i==1,v.vec,v.val)
end

Base.IndexStyle(::Type{<:FractionVector}) = IndexLinear()

unicode_subscript(a::Int) = join('₀'+d for d in reverse(digits(a)))


using Query
using Base.Test

# 3VL

@test Nullable(true) & Nullable(true) == Nullable(true)
@test Nullable(true) & Nullable(false) == Nullable(false)
@test Nullable(true) & Nullable{Bool}() == Nullable{Bool}()
@test Nullable(false) & Nullable(true) == Nullable(false)
@test Nullable(false) & Nullable(false) == Nullable(false)
@test Nullable(false) & Nullable{Bool}() == Nullable(false)
@test Nullable{Bool}() & Nullable(true) == Nullable{Bool}()
@test Nullable{Bool}() & Nullable(false) == Nullable(false)
@test Nullable{Bool}() & Nullable{Bool}() == Nullable{Bool}()

@test true & Nullable(true) == Nullable(true)
@test true & Nullable(false) == Nullable(false)
@test true & Nullable{Bool}() == Nullable{Bool}()
@test false & Nullable(true) == Nullable(false)
@test false & Nullable(false) == Nullable(false)
@test false & Nullable{Bool}() == Nullable(false)

@test Nullable(true) & true == Nullable(true)
@test Nullable(true) & false == Nullable(false)
@test Nullable(false) & true == Nullable(false)
@test Nullable(false) & false == Nullable(false)
@test Nullable{Bool}() & true == Nullable{Bool}()
@test Nullable{Bool}() & false == Nullable(false)

@test Nullable(true) | Nullable(true) == Nullable(true)
@test Nullable(true) | Nullable(false) == Nullable(true)
@test Nullable(true) | Nullable{Bool}() == Nullable(true)
@test Nullable(false) | Nullable(true) == Nullable(true)
@test Nullable(false) | Nullable(false) == Nullable(false)
@test Nullable(false) | Nullable{Bool}() == Nullable{Bool}()
@test Nullable{Bool}() | Nullable(true) == Nullable(true)
@test Nullable{Bool}() | Nullable(false) == Nullable{Bool}()
@test Nullable{Bool}() | Nullable{Bool}() == Nullable{Bool}()

@test true | Nullable(true) == Nullable(true)
@test true | Nullable(false) == Nullable(true)
@test true | Nullable{Bool}() == Nullable(true)
@test false | Nullable(true) == Nullable(true)
@test false | Nullable(false) == Nullable(false)
@test false | Nullable{Bool}() == Nullable{Bool}()

@test Nullable(true) | true == Nullable(true)
@test Nullable(true) | false == Nullable(true)
@test Nullable(false) | true == Nullable(true)
@test Nullable(false) | false == Nullable(false)
@test Nullable{Bool}() | true == Nullable(true)
@test Nullable{Bool}() | false == Nullable{Bool}()

@test !Nullable(true) == Nullable(false)
@test !Nullable(false) == Nullable(true)
@test !Nullable{Bool}() == Nullable{Bool}()

# null comparisons
@test (Nullable(5)==null) == false
@test (Nullable{Int}()==null) == true
@test (null==Nullable(5)) == false
@test (null==Nullable{Int}()) == true

@test (Nullable(5)!=null) == true
@test (Nullable{Int}()!=null) == false
@test (null!=Nullable(5)) == true
@test (null!=Nullable{Int}()) == false

:+, :-, :!, :~
@test +Nullable(1) == Nullable(+1)
@test +Nullable{Int}() == Nullable{Int}()
@test -Nullable(1) == Nullable(-1)
@test -Nullable{Int}() == Nullable{Int}()
@test ~Nullable(1) == Nullable(~1)
@test ~Nullable{Int}() == Nullable{Int}()

# TODO add ^, / back
for op in (:+, :-, :*, :%, :&, :|, :<<, :>>)
    @eval begin
        @test $op(Nullable(3), Nullable(5)) == Nullable($op(3, 5))
        @test $op(Nullable{Int}(), Nullable(5)) == Nullable{Int}()
        @test $op(Nullable(3), Nullable{Int}()) == Nullable{Int}()
        @test $op(Nullable{Int}(), Nullable{Int}()) == Nullable{Int}()

        @test $op(Nullable{Int}(3), 5) == Nullable($op(3, 5))
        @test $op(3, Nullable{Int}(5)) == Nullable($op(3, 5))
        @test $op(Nullable{Int}(), 5) == Nullable{Int}()
        @test $op(3, Nullable{Int}()) == Nullable{Int}()
    end
end

@test Nullable(3) == Nullable(3)
@test !(Nullable(3) == Nullable(4))
@test !(Nullable{Int}() == Nullable(3))
@test !(Nullable{Float64}() == Nullable(3))
@test !(Nullable(3) == Nullable{Int}())
@test !(Nullable(3) == Nullable{Float64}())
@test Nullable{Int}() == Nullable{Int}()
@test Nullable{Int}() == Nullable{Float64}()

@test Nullable(3) == 3
@test 3 == Nullable(3)
@test !(Nullable(3) == 4)
@test !(4 == Nullable(3))
@test !(Nullable{Int}() == 3)
@test !(3 == Nullable{Int}())

@test !(Nullable(3) != Nullable(3))
@test Nullable(3) != Nullable(4)
@test Nullable{Int}() != Nullable(3)
@test Nullable{Float64}() != Nullable(3)
@test Nullable(3) != Nullable{Int}()
@test Nullable(3) != Nullable{Float64}()
@test !(Nullable{Int}() != Nullable{Int}())
@test !(Nullable{Int}() != Nullable{Float64}())

@test !(Nullable(3) != 3)
@test !(3 != Nullable(3))
@test Nullable(3) != 4
@test 4 != Nullable(3)
@test Nullable{Int}() != 3
@test 3 != Nullable{Int}()

@test Nullable(4) > Nullable(3)
@test !(Nullable(3) > Nullable(4))
@test !(Nullable(4) > Nullable{Int}())
@test !(Nullable{Int}() > Nullable(3))
@test !(Nullable{Int}() > Nullable{Int}())

@test Nullable(4) > 3
@test !(Nullable(3) > 4)
@test !(Nullable{Int}() > 3)

@test 4 > Nullable(3)
@test !(3 > Nullable(4))
@test !(4 > Nullable{Int}())

@test lowercase(Nullable("TEST"))==Nullable("test")
@test lowercase(Nullable{String}())==Nullable{String}()

@test Nullable("TEST")[2:end]==Nullable("EST")
@test Nullable{String}()[2:end]==Nullable{String}()

@test length(Nullable("TEST"))==Nullable(4)
@test length(Nullable{String}())==Nullable{Int}()


abstract type AbstractILMProblem{DT,ST,DTP} end

function regenerate_problem() end

"""
$(TYPEDEF)

When defining a problem type with scalar data, make it a subtype of this.
"""
abstract type AbstractScalarILMProblem{DT,ST,DTP} <: AbstractILMProblem{DT,ST,DTP} end

"""
$(TYPEDEF)

When defining a problem type with vector data, make it a subtype of this.
"""
abstract type AbstractVectorILMProblem{DT,ST,DTP} <: AbstractILMProblem{DT,ST,DTP} end

"""
The `@ilmproblem` macro is used to automatically generate a type
particular to an immersed-layer problem, which can then be used for dispatch on
those types of problems. It takes two arguments: the name of the problem
(to which `Problem` will be automatically appended), and whether the problem
is of scalar or vector type. For example,

    @ilmproblem(StokesFlow,vector)

would generate a type `StokesFlowProblem` dealing with vector-valued data. The
resulting type then automatically has a constructor that allows one to pass
in the grid information and bodies, as well as optional choices for the
DDF function and the scaling type. For the example, this constructor would be

    StokesFlowProblem(grid,bodies[,ddftype=CartesianGrids.Yang3][,scaling=GridScaling])

Note that there is another constructor for problems with no surfaces that only
requires that the grid information be passed, e.g.,

    StokesFlowProblem(grid)

There are several keyword arguments for the problem constructor

- `ddftype = ` to set the DDF type. The default is `Yang3`.
- `scaling = ` to set the scaling type, `IndexScaling` (default) or `GridScaling`.
- `dtype = ` to set the element type to `Float64` (default) or `ComplexF64`.
- `phys_params = ` to pass in physical parameters
- `bc = ` to pass in boundary condition data or functions
- `forcing = ` to pass in forcing functions or data
- `motions = ` to provide function(s) that specify the velocity of the immersed surface(s). Note: if this keyword is used, it is assumed that surfaces will move.
- `timestep_func =` to pass in a function for time-dependent problems that provides the time-step size.
                  It is expected that this function takes in two arguments,
                  the `grid::PhysicalGrid` and `phys_params`, and returns the time step. It is up to the
                  user to decide how to determine this. It could also simply return a
                  constant value, regardless of the arguments.

"""
macro ilmproblem(name,vector_or_scalar)
    vs_string = string(vector_or_scalar)
    abtype = Symbol("Abstract"*uppercasefirst(vs_string)*"ILMProblem")
    typename = Symbol(string(name)*"Problem")
    return esc(quote

          @doc """
              $($typename)

          ILM problem type dealing with $($vs_string)-type data.
          """
          struct $typename{DT,ST,DTP,BLT,PHT,BCF,FF,DTF,MTF} <: $abtype{DT,ST,DTP}
             g :: PhysicalGrid
             bodies :: BLT
             phys_params :: PHT
             bc :: BCF
             forcing :: FF
             timestep_func :: DTF
             motions :: MTF
             $typename(g::PT,bodies::BodyList;ddftype=ImmersedLayers.DEFAULT_DDF,
                                              scaling=ImmersedLayers.DEFAULT_SCALING,
                                              dtype=ImmersedLayers.DEFAULT_DATA_TYPE,
                                              phys_params=nothing,
                                              bc=nothing,
                                              forcing=nothing,
                                              timestep_func=nothing,
                                              motions=nothing) where {PT <: PhysicalGrid} =
                    new{ddftype,scaling,dtype,typeof(bodies),typeof(phys_params),typeof(bc),typeof(forcing),typeof(timestep_func),
                                        typeof(ImmersedLayers._list(motions))}(
                                              g,bodies,phys_params,bc,forcing,timestep_func,ImmersedLayers._list(motions))
          end

          $typename(g::PhysicalGrid,body::Body;kwargs...) = $typename(g,BodyList([body]); kwargs...)
          $typename(g::PhysicalGrid;kwargs...) = $typename(g,BodyList(); kwargs...)

          ImmersedLayers.regenerate_problem(sys::ILMSystem{S,P},bl::BodyList) where {S,P<:$typename} =
                                      $typename(sys.base_cache.g,bl,
                                                ddftype=ImmersedLayers._ddf_type(P),
                                                scaling=ImmersedLayers._scaling_type(P),
                                                dtype=ImmersedLayers._element_type(P),
                                                phys_params=sys.phys_params,
                                                bc=sys.bc,
                                                forcing=sys.forcing,
                                                timestep_func=sys.timestep_func,
                                                motions=sys.motions)

           nothing
     end)

end

regenerate_problem(sys,body::Body) = regenerate_problem(sys,BodyList([body]))

_list(m::RigidBodyTools.AbstractMotion) = MotionList([m])
_list(m::MotionList) = m
_list(::Nothing) = nothing
_list(::Any) = nothing

_ddf_type(::AbstractILMProblem{DT}) where {DT} = DT
_ddf_type(::Type{P}) where P <: AbstractILMProblem{DT} where {DT} = DT
_scaling_type(::AbstractILMProblem{DT,ST}) where {DT,ST} = ST
_scaling_type(::Type{P}) where P <: AbstractILMProblem{DT,ST} where {DT,ST} = ST
_element_type(::AbstractILMProblem{DT,ST,DTP}) where {DT,ST,DTP} = DTP
_element_type(::Type{P}) where P <: AbstractILMProblem{DT,ST,DTP} where {DT,ST,DTP} = DTP

@ilmproblem BasicScalarILM scalar
@ilmproblem BasicVectorILM vector

# Extend this function for other problem types in order to create an extra cache
# of variables and operators for the problem
"""
    prob_cache(prob,base_cache::BasicILMCache)

This function is called by [`__init`](@ref) to generate a problem-specific extra
cache. Extend this function in order to generate an extra cache for a
user-defined problem type. The user must define the cache type itself.
""" prob_cache

prob_cache(prob::BasicScalarILMProblem,base_cache::BasicILMCache) = nothing
prob_cache(prob::BasicVectorILMProblem,base_cache::BasicILMCache) = nothing


bm_nonsymbolic_small = @benchmark sem_fit(model_ml_small)

BenchmarkTools.Trial: 7037 samples with 1 evaluation.
 Range (min … max):  536.094 μs …  13.142 ms  ┊ GC (min … max): 0.00% … 84.11%
 Time  (median):     623.008 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   703.678 μs ± 577.872 μs  ┊ GC (mean ± σ):  4.18% ±  4.91%

     ▃█▇▅▃▁
  ▃▄███████▇▆▆▄▄▄▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▁▂▂▁▁▂▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  536 μs           Histogram: frequency by time         1.24 ms <

 Memory estimate: 439.34 KiB, allocs estimate: 696.

bm_nonsymbolic_big = @benchmark sem_fit(model_ml_big)

BenchmarkTools.Trial: 10 samples with 1 evaluation.
 Range (min … max):  377.030 ms … 742.732 ms  ┊ GC (min … max): 0.00% … 0.62%
 Time  (median):     516.519 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   538.650 ms ± 122.110 ms  ┊ GC (mean ± σ):  0.36% ± 0.49%

  ▁ ▁           █ ▁            ▁         ▁   ▁  ▁             ▁
  █▁█▁▁▁▁▁▁▁▁▁▁▁█▁█▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█▁▁▁█▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
  377 ms           Histogram: frequency by time          743 ms <

 Memory estimate: 56.59 MiB, allocs estimate: 956.

# MKL

using MKL

bm_nonsymbolic_small_mkl = @benchmark sem_fit(model_ml_small)

BenchmarkTools.Trial: 4948 samples with 1 evaluation.
 Range (min … max):  712.489 μs …  12.225 ms  ┊ GC (min … max): 0.00% … 82.20%
 Time  (median):     899.797 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):     1.003 ms ± 549.112 μs  ┊ GC (mean ± σ):  2.60% ±  4.65%

        ▂▁█▄▁
  ▁▂▂▃▃▄██████▆▅▅▅▄▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  712 μs           Histogram: frequency by time         1.78 ms <

 Memory estimate: 439.34 KiB, allocs estimate: 696.

bm_nonsymbolic_big_mkl = @benchmark sem_fit(model_ml_big)

BenchmarkTools.Trial: 21 samples with 1 evaluation.
 Range (min … max):  149.162 ms … 401.825 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     243.611 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   249.148 ms ±  53.616 ms  ┊ GC (mean ± σ):  0.54% ± 0.63%

  ▁   ▁      ▁    ▁█▁█▁ ▁ ▁ ▁▁█▁    ▁▁     ▁                  ▁
  █▁▁▁█▁▁▁▁▁▁█▁▁▁▁█████▁█▁█▁████▁▁▁▁██▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
  149 ms           Histogram: frequency by time          402 ms <

 Memory estimate: 56.59 MiB, allocs estimate: 956.

# BFGS

algo = BFGS(;linesearch = BackTracking(order=3), alphaguess = InitialHagerZhang())

bm_algo_small = @benchmark sem_fit(model_ml_small)

BenchmarkTools.Trial: 3270 samples with 1 evaluation.
 Range (min … max):  937.452 μs … 21.758 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):       1.255 ms              ┊ GC (median):    0.00%
 Time  (mean ± σ):     1.510 ms ±  1.043 ms  ┊ GC (mean ± σ):  2.31% ± 4.83%

   ▆█▄▂
  ▆█████▇▆▆▅▄▄▅▄▄▄▄▄▄▄▄▄▄▄▃▃▃▃▂▂▂▂▂▂▂▂▂▁▂▂▂▂▁▁▂▁▁▁▂▂▁▁▁▁▂▂▁▁▁▂ ▃
  937 μs          Histogram: frequency by time          4.1 ms <

 Memory estimate: 439.34 KiB, allocs estimate: 696.

bm_algo_big = @benchmark sem_fit(model_ml_big)

BenchmarkTools.Trial: 14 samples with 1 evaluation.
 Range (min … max):  274.281 ms … 578.298 ms  ┊ GC (min … max): 0.00% … 0.58%
 Time  (median):     340.173 ms               ┊ GC (median):    0.47%
 Time  (mean ± σ):   358.096 ms ±  82.599 ms  ┊ GC (mean ± σ):  0.54% ± 0.57%

  ▁ ▁█▁    ▁ ▁  ▁  ▁▁  ▁         ▁   ▁                        ▁
  █▁███▁▁▁▁█▁█▁▁█▁▁██▁▁█▁▁▁▁▁▁▁▁▁█▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
  274 ms           Histogram: frequency by time          578 ms <

 Memory estimate: 56.59 MiB, allocs estimate: 956.

# profiling

ProfileView.@profview profile_test(model_ml_big, 10)

- small model: matrix inversion takes more time

big model takes more time
- SemML: 153

ML.jl, 221, uniformscaling bad performance for small model

- factorize upper triangular


module TestMacrosWhere

import Test: @testset, @test
import Volcanito: @where, materialize
import DataFrames: DataFrame

@testset "@where" begin
    df = DataFrame(
        a = [1, 2, 3, 4],
        b = [2, 3, 4, 5],
    )

    plan1 = @where(df, a < b)
    res1 = materialize(plan1)
    @test res1 == df

    plan2 = @where(df, a > 1)
    res2 = materialize(plan2)
    @test res2 == df[2:4, :]
end

end


export update!

function update! end

function (sim::Simulation)(::typeof(update!),opt ,m::AbstractArray,vals::AbstractArray,maskF = EVERYWHERE ,gridtype::GridType = p̂)
	mask = maskF.(Coordinates(sim.grid,gridtype))
    m[mask] .+= apply!(opt, m, vals)[mask]
end


if !@isdefined(TabLevel)
    TabLevel = ""
end
println(TabLevel*"Open OpMul.jl")
TabLevel=TabLevel*"    "

import Base.*
import Base.^

*(x::MajTypes,y::MajTypes) = OpMul(x,y)
*(x::Scalars,y::MajTypes) = OpScale(x,y)
*(x::MajTypes,y::Scalars) = OpScale(y,x)
^(x::MajTypes,y::Integer) = OpPower(x,y)

function OpMul(A) return OpCopy(A) end ##Of only one factor then the result is trivial

function OpMul(a::MajoranaSp,b::MajoranaSp)
    ###When both terms are superpositoins we add them term by term

    tot=OpScale(0)
    #print("tot=")
    #OpPrintln(tot)
    #println("tot=",tot)
    #print("b=")
    #OpPrintln(b)
    for aOP in a.SpList
        #print("aOP=")
        #OpPrintln(aOP)
        aOPb=aOP*b
        #print("aOPb=")
        #OpPrintln(aOPb)
        tot=aOPb+tot
        #print("tot=")
        #OpPrintln(tot)
    end
    return tot
end

function OpMul(a::MajoranaOp,b_in::MajoranaSp)
    if a == 0
        return 0
    end
    b = OpCopy(b_in)
    for indx in 1:length(b.SpList)
        b.SpList[indx]=a*b.SpList[indx]
    end
    return OpReduce(b)

end

function OpMul(a_in::MajoranaSp,b::MajoranaOp)
    if b == 0
        return 0
    end
    a = OpCopy(a_in)
    ###Loop over the term an multiply the on by one
    for indx in 1:length(a.SpList)
        a.SpList[indx]=a.SpList[indx]*b
    end
    return OpReduce(a)
end

function OpMul(a::MajoranaOp,b::MajoranaOp)
    ##Check that a is not a ket state
    if a.KetState
        error("The state "*OpToStr(a)*" may not be to the left of a matrix multiplication ")
    end
    OpReduce(MajoranaOp(a.scale*b.scale,vcat(a.OpList,b.OpList),b.KetState))
end

function OpMul(a::MajElem,b::MajElem)
    OpReduce(MajoranaOp(1,[a,b]))
end

OpMul(a::MajElem,b::MajoranaOp) = MajoranaOp(1,[a])*b
OpMul(b::MajoranaOp,a::MajElem) = b*MajoranaOp(1,[a])

###  Methods to Make majoranas equal
#### Scaling operators
function OpScale(scale::Scalars)
    return scale
end

function OpScale(scale::Scalars,a_in::MajoranaSp;verbose=false)
    verbose && println("Scale $a_in by  $scale")
    if a_in.Reduced ###Reduce the Majorana first
        a = OpCopy(a_in)
    else
        a = OpReduce(a_in,verbose=verbose)
    end
    for indx in 1:length(a.SpList) ###Updated the terms one by one
        a.SpList[indx]=OpScale(scale,a.SpList[indx],verbose=verbose)
    end
    return a
end

function OpScale(scale::Scalars,a_in::MajoranaOp;verbose=false)
    verbose && println("Scale $a_in by  $scale")
    if scale == 0
        return 0
    end
    a = OpReduce(a_in)
    a.scale*=scale
    return a
end

function OpScale(scale::Scalars,a::MajElem;verbose=false)
    verbose && println("Scale $a_in by  $scale")
    if scale == 0
        return 0
    else
        return MajoranaOp(scale,[a])
    end
end

function OpPower(x::Union{MajoranaOp,MajoranaSp,MajElem},y::Integer)
    if y < 0
        throw(DomainError("Majorana cannot be raised to a negative power $y"))
    elseif y==0
        return 1
    elseif y==1
        return 1*x ###To make sure it copied and reduced
    elseif y==2
        return x*x ###To make sure it copied and reduced
    else
        return x*(x^(y-1)) ###Recursively apply the product
    end
end

TabLevel=TabLevel[1:end-4]
println(TabLevel*"Close OpMul.jl")


using SimJulia

struct StoreObject
  i :: Int
end

@resumable function my_consumer(sim::Simulation, sto::Store)
  for j in 1:10
    @yield timeout(sim, rand())
    println("$(now(sim)), consumer is demanding object")
    obj = @yield get(sto)
    println("$(now(sim)), consumer is being served with object ", obj.i)
  end
end

@resumable function my_producer(sim::Simulation, sto::Store)
  for j in 1:10
    println("$(now(sim)), producer is offering object $j")
    @yield put(sto, StoreObject(j))
    println("$(now(sim)), producer is being served")
    @yield timeout(sim, 2*rand())
  end
end

sim = Simulation()
sto = Store{StoreObject}(sim)
@process my_consumer(sim, sto)
@process my_producer(sim, sto)
run(sim)


struct XVG
    Data::Array{Array{Float64,1},1}
    x_label::SubString{String}
    y_label::SubString{String}
    plot_labels
    title::SubString{String}
end

function read_xvg(path::AbstractString)
    a = readlines(path)
    plot_atributes = filter(x->startswith(x,"@"),a)
    if length(plot_atributes) >= 11
            if length(plot_atributes) > 11
                plot_label = Array{AbstractString,1}(undef,length(plot_atributes)-11 + 1)
                for i in 11:length(plot_atributes)
                    plot_label[i-10] = replace(replace(replace(strip(split(plot_atributes[i],"legend")[2]),"\\s"=>"_"),"\""=>""),"\\N"=>"")
                end
                String.(plot_label)
            else
                plot_label = split(plot_atributes[11],"legend")[2][3:end-1]
            end
    else
        plot_label = split(plot_atributes[1],"title")[2][3:end-1]
    end
    title = split(plot_atributes[1],"title")[2][3:end-1]
    x_label = split(plot_atributes[2],"label")[2][3:end-1]
    y_label = split(plot_atributes[3],"label")[2][3:end-1]
    dp = map(x->parse.(Float64,x),split.(filter(x->startswith(x," "),a)))

    return XVG(dp,x_label,y_label,plot_label,title)
end

using RecipesBase

@recipe function f(a::XVG)
    label --> false
    xguide --> a.x_label
    yguide --> a.y_label
    title --> a.title
    legend --> :outertopright

    if typeof(a.plot_labels) != SubString{String}
        labels --> reshape(a.plot_labels,(1,length(a.plot_labels)))
    else
        labels --> a.plot_labels
    end
    @series begin
        if a.Data[1] |> length == 2
            getindex.(a.Data,1),getindex.(a.Data,2)
        else
            getindex.(a.Data,1),[getindex.(a.Data,2), getindex.(a.Data,3),getindex.(a.Data,4),getindex.(a.Data,5)]
        end

    end
end


function reverse(mg::MetaDiGraph{T,U}) where T <: Integer where U <: Real
    rg = reverse(mg.graph)
    map = nv(mg):-1:1
    rvprops = Dict{T,PropDict}()
    reprops = Dict{SimpleEdge{T},PropDict}()
    rgprops = mg.gprops
    rweightfield = mg.weightfield
    rdefaultweight = mg.defaultweight
    rindices = mg.indices

    for v in keys(mg.vprops)
        rvprops[map[v]] = mg.vprops[v]
    end

    for e in keys(mg.eprops)
        reprops[reverse(e)] = mg.eprops[e]
    end

    rmg = MetaDiGraph(rg,
        rvprops,
        reprops,
        rgprops,
        rweightfield,
        rdefaultweight,
        MetaDict(),
        Set{Symbol}())
    for p in mg.indices
        set_indexing_prop!(rmg, p)
    end

    return rmg
end


using Test
using MLStyle, MacroTools

@testset "search_and_replace" begin

ex = @search_and_replace sin(x) sin(x) cos(x)
@test ex == :( cos(x) )

ex = @search_and_replace sin(y) sin($x) cos($x)
@test ex == :( cos(y) )

ex = @search_and_replace sin(sin(x)) sin($x) cos($x +1)
@test ex == :( cos(cos(x+1) +1) )

end


"""
    evaluate_results(;[simname=""])

Convert simulation results for data exchange with other frameworks.

# Arguments
- `simname`: Path to a specific .jld file to be converted

See also: [Config](@ref), [cfg_sim](@ref), [cfg_ot](@ref), [cfg_ag](@ref), [cfg_feed](@ref), [cfg_mech](@ref)
"""
function evaluate_results(
    ;
    simname::String=""
)

    if !in("dataexchange", readdir())
        mkdir("dataexchange")
    end

    evalqueue = Array{Simulation, 1}[]

    if simname != ""

        raw_data = load(joinpath("results", simname))
        data = raw_data[first(keys(raw_data))]

        push!(evalqueue, data)

    else
        for simname in readdir("results")
            raw_data = load(joinpath("results", simname))
            data = raw_data[first(keys(raw_data))]

            push!(evalqueue, data)
        end
    end

    results = DataFrame(
        RunName = String[],
        RepNr = Int64[],
        Densities = Float64[],
        OutdegreeSD = Float64[],
        IndegreeSD = Float64[],
        OutdegreeIndegreeRatioMean = Float64[],
        ClosenessCentralityMean = Float64[],
        BetweennessCentralityMean = Float64[],
        EigenCentralityMean = Float64[],
        ClustCoeff = Float64[],
        CommunityCount = Int64[],
        CommunityCountNontrivial = Int64[],
        ConnectedComponents = Int64[],
        OpinionSD = Float64[],
        OpChangeDeltaMean = Float64[],
        PublOwnOpinionDiff = Float64[],
        SupernodeOutdegree = Int64[],
        SupernodeCloseness = Float64[],
        SupernodeBetweenness = Float64[],
        SupernodeEigen = Float64[],
        SupernodeOpinion = Float64[],
        Supernode1st2ndOpdiff = Float64[],
        CommunityOpMeanSDs = Float64[]
    )

    for simarray in evalqueue

        repcount = length(simarray)
        nodes_ranked = [first.(sort([(i, outdegree(simarray[j].final_state[1], i)) for i in 1:nv(simarray[j].final_state[1])], by=last, rev=true)) for j in 1:repcount]

        opinionsd = [std([agent.opinion for agent in simarray[i].final_state[2]]) for i in 1:repcount]
        opchange_delta_mean = [mean([abs(simarray[j].init_state[2][i].opinion - simarray[j].final_state[2][i].opinion) for i in 1:simarray[j].config.network.agent_count]) for j in 1:repcount]
        densities = [density(simarray[i].final_state[1]) for i in 1:repcount]
        outdegree_sd = [std(outdegree(simarray[i].final_state[1])) for i in 1:repcount]
        outdegree_mean = [mean(outdegree(simarray[i].final_state[1])) for i in 1:repcount]
        indegree_sd = [std(indegree(simarray[i].final_state[1])) for i in 1:repcount]
        outinratio = [mean([outdegree(simarray[i].final_state[1], j) / degree(simarray[i].final_state[1], j) for j in 1:nv(simarray[i].final_state[1]) if degree(simarray[i].final_state[1], j) > 0]) for i in 1:repcount]
        publownopiniondiff = [mean([abs(agent.perceiv_publ_opinion - agent.opinion) for agent in simarray[i].final_state[2]]) for i in 1:repcount]
        closeness_centrality_mean = [mean(closeness_centrality(simarray[i].final_state[1])) for i in 1:repcount]
        betweenness_centrality_mean = [mean(betweenness_centrality(simarray[i].final_state[1])) for i in 1:repcount]
        eigen_centrality_mean = [mean(eigenvector_centrality(simarray[i].final_state[1])) for i in 1:repcount]
        supernode_outdegree = [outdegree(simarray[i].final_state[1], nodes_ranked[i][1]) for i in 1:repcount]
        supernode_closeness = [closeness_centrality(simarray[i].final_state[1])[nodes_ranked[i][1]] for i in 1:repcount]
        supernode_betweenness = [betweenness_centrality(simarray[i].final_state[1])[nodes_ranked[i][1]] for i in 1:repcount]
        supernode_eigen = [eigenvector_centrality(simarray[i].final_state[1])[nodes_ranked[i][1]] for i in 1:repcount]
        supernode_opinion = [simarray[i].final_state[2][nodes_ranked[i][1]].opinion for i in 1:repcount]
        supernode1st2nd_opdiff = [abs(simarray[i].final_state[2][nodes_ranked[i][1]].opinion - simarray[i].final_state[2][nodes_ranked[i][2]].opinion) for i in 1:repcount]
        clust_coeff = [global_clustering_coefficient(simarray[i].final_state[1]) for i in 1:repcount]
        conn_components = [length(connected_components(simarray[i].final_state[1])) for i in 1:repcount]

        # Calc the SD of the opinion means of the identified clusters
        n_communities = Int64[]
        community_opinion_mean_sds = Float64[]
        n_communities_nontrivial = Int64[]
        for i in 1:repcount
            label_prop = label_propagation(simarray[i].final_state[1])[1]
            community_sizes = last.(collect(countmap(label_prop)))
            push!(n_communities_nontrivial, length([i for i in community_sizes if i > 1]))
            push!(n_communities, maximum(label_prop))
            if maximum(label_prop) == 1
                push!(community_opinion_mean_sds, 0)
                continue
            end
            push!(community_opinion_mean_sds, std([mean([agent.opinion for agent in simarray[i].final_state[2] if agent.id in findall(x->x==j, label_prop)]) for j in 1:maximum(label_prop)]))
        end

        if simarray[1].batch_name != ""
            runname = ["Run$i" for i in 1:length(simarray)]
        else
            runname = simarray[1].name
        end

        append!(
            results,
            DataFrame(
                RunName = runname,
                RepNr = collect(1:repcount),
                Densities = densities,
                OutdegreeSD = outdegree_sd,
                IndegreeSD = indegree_sd,
                OutdegreeIndegreeRatioMean = outinratio,
                ClosenessCentralityMean = closeness_centrality_mean,
                BetweennessCentralityMean = betweenness_centrality_mean,
                EigenCentralityMean = eigen_centrality_mean,
                ClustCoeff = clust_coeff,
                CommunityCount = n_communities,
                CommunityCountNontrivial = n_communities_nontrivial,
                ConnectedComponents = conn_components,
                OpinionSD = opinionsd,
                OpChangeDeltaMean = opchange_delta_mean,
                PublOwnOpinionDiff = publownopiniondiff,
                SupernodeOutdegree = supernode_outdegree,
                SupernodeCloseness = supernode_closeness,
                SupernodeBetweenness = supernode_betweenness,
                SupernodeEigen = supernode_eigen,
                SupernodeOpinion = supernode_opinion,
                Supernode1st2ndOpdiff = supernode1st2nd_opdiff,
                CommunityOpMeanSDs = community_opinion_mean_sds
            )
        )

    end

    if simarray[1].batch_name == ""
        if !in(simarray[1].name, readdir("dataexchange"))
            mkdir(joinpath("dataexchange", simarray[1].name))
        end

        CSV.write(
        joinpath(
        "dataexchange",
        simarray[1].name,
        "results_" * simarray[1].name * ".csv"
        ),
        results
        )

    else
        if !in(simarray[1].batch_name, readdir("dataexchange"))
            mkdir(joinpath("dataexchange", simarray[1].batch_name))
        end

        CSV.write(
        joinpath(
        "dataexchange",
        simarray[1].batch_name,
        "results.csv"
        ),
        results
        )
    end
end


using ExprSearch
using DecisionTrees


using Mapper2
using Test

using Logging
disable_logging(Logging.BelowMinLevel)

# Add path to example architecture to the LOAD_PATH variable
for i in 1:2
    include(joinpath(Mapper2.PKGDIR, "example", "ex$i", "Example$i.jl"))
end
include(joinpath(Mapper2.PKGDIR, "example", "chessboard", "Chessboard.jl"))
using .Chessboard
using .Example1
using .Example2

# Taskgraph
include("Taskgraph.jl")

# Architecture
include("Architecture/Architecture.jl")

# Placement
include("Place/MoveGenerators.jl")
include("Place/InitialPlacement.jl")

# Maps
include("Map/IntegrationTest.jl")
include("Map/SaveLoad.jl")

# Long running placement
include("Place/Placement.jl")


@testset "driver" begin
  using LightGraphs
  result = driver(4,2)
  for g in keys(result)
      @test nv(g) == 16
  end

  import Graph500.Graph500Results
  @test Graph500Results().scale == 0
end


function writePhysicalNames(physicalNames::PhysicalNames)
    docCharArray::Array{Char, 1} = collect("\$PhysicalNames\n")
    docCharArray = vcat(docCharArray, collect(string(length(physicalNames.dimTags))), ['\n'])
    for i ∈ 1:length(physicalNames.dimTags)
        docCharArray = vcat(docCharArray, collect(string(physicalNames.dimTags[i][1])), [' '],
        collect(string(physicalNames.dimTags[i][2])), [' ','"'], collect(physicalNames.names[i]),
        ['"', '\n'])
    end
return vcat(docCharArray, collect("\$EndPhysicalNames\n"))
end

function writeNodes(nodes::Array{Float64, 2})
    @assert length(nodes[1, :]) == 3 "Nodes must have 3 columns or 3 dimensions."
    docCharArray::Array{Char, 1} = collect("\$Nodes\n")
    docCharArray = vcat(docCharArray, collect(string(length(nodes[:,1]))),['\n'])
    for nodeNo ∈ 1:length(nodes[:,1])
        docCharArray = vcat(docCharArray, [' '], collect(string(nodeNo)), [' '], collect(string(nodes[nodeNo,1])),
        [' '], collect(string(nodes[nodeNo,2])), [' '], collect(string(nodes[nodeNo,3])), ['\n'])
    end
    return vcat(docCharArray, collect("\$EndNodes\n"))
end

function writeElements(elementArray::Array{Element, 1})
    docCharArray::Array{Char, 1} = vcat(collect("\$Elements\n"), collect(string(length(elementArray))), ['\n'])
    for elementNo ∈ 1:length(elementArray)
        docCharArray = vcat(docCharArray, collect(string(elementNo)), [' '],
        collect(string(elementArray[elementNo].gmshElementType)), [' '],
        collect(string(length(elementArray[elementNo].attributes))), [' '])
        for attribNo ∈ 1:length(elementArray[elementNo].attributes)
            docCharArray = vcat(docCharArray, collect(string(elementArray[elementNo].attributes[attribNo])))
        end
        for nodeNo ∈ 1:length(elementArray[elementNo].nodeTags)
            docCharArray = vcat(docCharArray, [' '], collect(string(elementArray[elementNo].nodeTags[nodeNo])))
        end
        docCharArray = vcat(docCharArray, ['\n'])
    end
    return vcat(docCharArray, collect("\$EndElements\n"))
end
function writeGmshv2(fileName::String, physicalNames::PhysicalNames, nodes::Array{Float64, 2}, elementArray::Array{Element, 1})
    #Mesh File Header
    docCharArray::Array{Char, 1} = collect("\$MeshFormat\n2.2 0 8\n\$EndMeshFormat\n")
    #Mesh Physical Names
    docCharArray = vcat(docCharArray, writePhysicalNames(physicalNames),
    writeNodes(nodes), writeElements(elementArray))
    file = open(fileName, "w")
    write(file, String(docCharArray))
    close(file)
    return String(docCharArray)
end


#
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

# What is included in this file:
# - the `fmi3InstanceState`-enum which mirrors the internal FMU state (state-machine, not the system state)
# - the `FMU3Instance`-struct
# - the `FMU3`-struct
# - string/enum-converters for FMI-attribute-structs (e.g. `fmi3StatusToString`, ...)

# this is a custom type to catch the internal state of the instance

@enum fmi3InstanceState begin
    fmi3InstanceStateInstantiated       # after instantiation
    fmi3InstanceStateInitializationMode # after finishing initialization
    fmi3InstanceStateEventMode
    fmi3InstanceStateStepMode
    fmi3InstanceStateClockActivationMode
    fmi3InstanceStateContinuousTimeMode
    fmi3InstanceStateConfigurationMode
    fmi3InstanceStateReconfigurationMode
    fmi3InstanceStateTerminated
    fmi3InstanceStateError
    fmi3InstanceStateFatal
end

"""
ToDo.
"""
mutable struct FMU3InstanceEnvironment
    logStatusOK::Bool
    logStatusWarning::Bool
    logStatusDiscard::Bool
    logStatusError::Bool
    logStatusFatal::Bool
    logStatusPending::Bool

    function FMU3InstanceEnvironment()
        inst = new()
        inst.logStatusOK = true
        inst.logStatusWarning = true
        inst.logStatusDiscard = true
        inst.logStatusError = true
        inst.logStatusFatal = true
        inst.logStatusPending = true
        return inst
    end
end

"""
Source: FMISpec3.0, Version D5ef1c1:: 2.2.1. Header Files and Naming of Functions

The mutable struct represents a pointer to an FMU specific data structure that contains the information needed to process the model equations or to process the co-simulation of the model/subsystem represented by the FMU.
"""
mutable struct FMU3Instance
    compAddr::Ptr{Nothing}
    fmu # ::FMU3
    state::fmi3InstanceState
    instanceEnvironment::FMU3InstanceEnvironment

    loggingOn::Bool
    instanceName::String
    continuousStatesChanged::fmi3Boolean

    t::fmi3Float64             # the system time
    t_offset::fmi3Float64      # time offset between simulation environment and FMU
    x::Union{Array{fmi3Float64, 1}, Nothing}   # the system states (or sometimes u)
    ẋ::Union{Array{fmi3Float64, 1}, Nothing}   # the system state derivative (or sometimes u̇)
    ẍ::Union{Array{fmi3Float64, 1}, Nothing}   # the system state second derivative
    #u::Array{fmi3Float64, 1}   # the system inputs
    #y::Array{fmi3Float64, 1}   # the system outputs
    #p::Array{fmi3Float64, 1}   # the system parameters
    z::Array{fmi3Float64, 1}   # the system event indicators
    z_prev::Union{Nothing, Array{fmi3Float64, 1}}   # the last system event indicators

    realValues::Dict

    x_vrs::Array{fmi3ValueReference, 1}   # the system state value references
    ẋ_vrs::Array{fmi3ValueReference, 1}   # the system state derivative value references
    u_vrs::Array{fmi3ValueReference, 1}   # the system input value references
    y_vrs::Array{fmi3ValueReference, 1}   # the system output value references
    p_vrs::Array{fmi3ValueReference, 1}   # the system parameter value references

    # deprecated
    jac_ẋy_x::Matrix{fmi3Float64}
    jac_ẋy_u::Matrix{fmi3Float64}
    jac_x::Array{fmi3Float64}
    jac_u::Union{Array{fmi3Float64}, Nothing}
    jac_t::fmi3Float64

    # linearization jacobians
    A::Union{Matrix{fmi3Float64}, Nothing}
    B::Union{Matrix{fmi3Float64}, Nothing}
    C::Union{Matrix{fmi3Float64}, Nothing}
    D::Union{Matrix{fmi3Float64}, Nothing}

    jacobianUpdate!             # function for a custom jacobian constructor (optimization)
    skipNextDoStep::Bool    # allows skipping the next `fmi3DoStep` like it is not called
    senseFunc::Symbol       # :auto, :full, :sample, :directionalDerivatives, :adjointDerivatives

    # custom

    rootsFound::Array{fmi3Int32}
    stateEvent::fmi3Boolean
    timeEvent::fmi3Boolean
    stepEvent::fmi3Boolean

    # constructor

    function FMU3Instance()
        inst = new()
        inst.state = fmi3InstanceStateInstantiated
        inst.t = -Inf
        inst.t_offset = 0.0

        inst.senseFunc = :auto

        inst.x = nothing
        inst.ẋ = nothing
        inst.ẍ = nothing
        inst.z_prev = nothing

        inst.realValues = Dict()
        inst.x_vrs = Array{fmi3ValueReference, 1}()
        inst.ẋ_vrs = Array{fmi3ValueReference, 1}()
        inst.u_vrs = Array{fmi3ValueReference, 1}()
        inst.y_vrs = Array{fmi3ValueReference, 1}()
        inst.p_vrs = Array{fmi3ValueReference, 1}()

        # initialize further variables
        inst.jac_x = Array{fmi3Float64, 1}()
        inst.jac_u = nothing
        inst.jac_t = -1.0
        inst.jac_ẋy_x = zeros(fmi3Float64, 0, 0)
        inst.jac_ẋy_u = zeros(fmi3Float64, 0, 0)
        inst.skipNextDoStep = false

        inst.A = nothing
        inst.B = nothing
        inst.C = nothing
        inst.D = nothing
        return inst
    end

    function FMU3Instance(compAddr, fmu)
        inst = FMU3Instance()
        inst.compAddr = compAddr
        inst.fmu = fmu
        return inst
    end
end

"""
Overload the Base.show() function for custom printing of the FMU2Component.
"""
Base.show(io::IO, c::FMU3Instance) = print(io,
"FMU:            $(c.fmu.modelDescription.modelName)
InstanceName:   $(isdefined(c, :instanceName) ? c.instanceName : "[not defined]")
Address:        $(c.compAddr)
State:          $(c.state)
Logging:        $(c.loggingOn)
FMU time:       $(c.t)
FMU states:     $(c.x)"
)

"""
=======
A mutable struct representing the excution configuration of a FMU.
For FMUs that have issues with calls like `fmi3Reset` or `fmi3FreeInstance`, this is pretty useful.
"""
mutable struct FMU3ExecutionConfiguration <: FMUExecutionConfig
    terminate::Bool     # call fmi3Terminate before every training step / simulation
    reset::Bool         # call fmi3Reset before every training step / simulation
    setup::Bool         # call setup functions before every training step / simulation
    instantiate::Bool   # call fmi3Instantiate before every training step / simulation
    freeInstance::Bool  # call fmi3FreeInstance after every training step / simulation

    loggingOn::Bool
    externalCallbacks::Bool

    handleStateEvents::Bool                 # handle state events during simulation/training
    handleTimeEvents::Bool                  # handle time events during simulation/training

    assertOnError::Bool                     # wheter an exception is thrown if a fmi3XXX-command fails (>= fmi3StatusError)
    assertOnWarning::Bool                   # wheter an exception is thrown if a fmi3XXX-command warns (>= fmi3StatusWarning)

    autoTimeShift::Bool                     # wheter to shift all time-related functions for simulation intervals not starting at 0.0

    sensealg                                # algorithm for sensitivity estimation over solve call
    useComponentShadow::Bool                # whether FMU outputs/derivatives/jacobians should be cached for frule/rrule (useful for ForwardDiff)
    rootSearchInterpolationPoints::UInt     # number of root search interpolation points
    inPlace::Bool                           # whether faster in-place-fx should be used
    useVectorCallbacks::Bool                # whether to vector (faster) or scalar (slower) callbacks

    maxNewDiscreteStateCalls::UInt          # max calls for fmi3NewDiscreteStates before throwing an exception

    function FMU3ExecutionConfiguration()
        inst = new()

        inst.terminate = true
        inst.reset = true
        inst.setup = true
        inst.instantiate = false
        inst.freeInstance = false

        inst.loggingOn = false
        inst.externalCallbacks = false

        inst.handleStateEvents = true
        inst.handleTimeEvents = true

        inst.assertOnError = false
        inst.assertOnWarning = false

        inst.autoTimeShift = true

        inst.sensealg = nothing # auto
        inst.useComponentShadow = false
        inst.rootSearchInterpolationPoints = 100
        inst.inPlace = true
        inst.useVectorCallbacks = true

        inst.maxNewDiscreteStateCalls = 100

        return inst
    end
end

# default for a "healthy" FMU - this is the fastetst
FMU3_EXECUTION_CONFIGURATION_RESET = FMU3ExecutionConfiguration()
FMU3_EXECUTION_CONFIGURATION_RESET.terminate = true
FMU3_EXECUTION_CONFIGURATION_RESET.reset = true
FMU3_EXECUTION_CONFIGURATION_RESET.instantiate = false
FMU3_EXECUTION_CONFIGURATION_RESET.freeInstance = false

# if your FMU has a problem with "fmi3Reset" - this is default
FMU3_EXECUTION_CONFIGURATION_NO_RESET = FMU3ExecutionConfiguration()
FMU3_EXECUTION_CONFIGURATION_NO_RESET.terminate = false
FMU3_EXECUTION_CONFIGURATION_NO_RESET.reset = false
FMU3_EXECUTION_CONFIGURATION_NO_RESET.instantiate = true
FMU3_EXECUTION_CONFIGURATION_NO_RESET.freeInstance = true

# if your FMU has a problem with "fmi3Reset" and "fmi3FreeInstance" - this is for weak FMUs (but slower)
FMU3_EXECUTION_CONFIGURATION_NO_FREEING = FMU3ExecutionConfiguration()
FMU3_EXECUTION_CONFIGURATION_NO_FREEING.terminate = false
FMU3_EXECUTION_CONFIGURATION_NO_FREEING.reset = false
FMU3_EXECUTION_CONFIGURATION_NO_FREEING.instantiate = true
FMU3_EXECUTION_CONFIGURATION_NO_FREEING.freeInstance = false

"""
ToDo
"""
struct FMU3Event <: FMUEvent
    t::Union{Float32, Float64}
    indicator::UInt

    x_left::Union{Array{Float64, 1}, Array{Float32, 1}, Nothing}
    x_right::Union{Array{Float64, 1}, Array{Float32, 1}, Nothing}

    function FMU3Event(t::Union{Float32, Float64}, indicator::UInt = 0, x_left::Union{Array{Float64, 1}, Array{Float32, 1}, Nothing} = nothing, x_right::Union{Array{Float64, 1}, Array{Float32, 1}, Nothing} = nothing)
        inst = new(t, indicator, x_left, x_right)
        return inst
    end
end

"""
ToDo
"""
mutable struct FMU3Solution <: FMUSolution
    fmu                                             # FMU2
    success::Bool

    states                                          # ODESolution

    values
    valueReferences::Union{Array, Nothing}          # Array{fmi3ValueReference}

    events::Array{FMU2Event, 1}

    function FMU3Solution(fmu)
        inst = new()

        inst.fmu = fmu
        inst.success = false
        inst.states = nothing
        inst.values = nothing
        inst.valueReferences = nothing

        inst.events = []

        return inst
    end
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.1. Header Files and Naming of Functions

The mutable struct representing an FMU in the FMI 3.0 Standard.
Also contains the paths to the FMU and ZIP folder as well als all the FMI 3.0 function pointers
"""
mutable struct FMU3 <: FMU
    modelName::String
    instanceName::String
    fmuResourceLocation::String

    modelDescription::fmi3ModelDescription

    type::fmi3Type
    instanceEnvironment::fmi3InstanceEnvironment
    instances::Array # {fmi3Instance}

    # c-functions
    cInstantiateModelExchange::Ptr{Cvoid}
    cInstantiateCoSimulation::Ptr{Cvoid}
    cInstantiateScheduledExecution::Ptr{Cvoid}

    cGetVersion::Ptr{Cvoid}
    cFreeInstance::Ptr{Cvoid}
    cSetDebugLogging::Ptr{Cvoid}
    cEnterConfigurationMode::Ptr{Cvoid}
    cExitConfigurationMode::Ptr{Cvoid}
    cEnterInitializationMode::Ptr{Cvoid}
    cExitInitializationMode::Ptr{Cvoid}
    cTerminate::Ptr{Cvoid}
    cReset::Ptr{Cvoid}
    cGetFloat32::Ptr{Cvoid}
    cSetFloat32::Ptr{Cvoid}
    cGetFloat64::Ptr{Cvoid}
    cSetFloat64::Ptr{Cvoid}
    cGetInt8::Ptr{Cvoid}
    cSetInt8::Ptr{Cvoid}
    cGetUInt8::Ptr{Cvoid}
    cSetUInt8::Ptr{Cvoid}
    cGetInt16::Ptr{Cvoid}
    cSetInt16::Ptr{Cvoid}
    cGetUInt16::Ptr{Cvoid}
    cSetUInt16::Ptr{Cvoid}
    cGetInt32::Ptr{Cvoid}
    cSetInt32::Ptr{Cvoid}
    cGetUInt32::Ptr{Cvoid}
    cSetUInt32::Ptr{Cvoid}
    cGetInt64::Ptr{Cvoid}
    cSetInt64::Ptr{Cvoid}
    cGetUInt64::Ptr{Cvoid}
    cSetUInt64::Ptr{Cvoid}
    cGetBoolean::Ptr{Cvoid}
    cSetBoolean::Ptr{Cvoid}
    cGetString::Ptr{Cvoid}
    cSetString::Ptr{Cvoid}
    cGetBinary::Ptr{Cvoid}
    cSetBinary::Ptr{Cvoid}
    cGetFMUState::Ptr{Cvoid}
    cSetFMUState::Ptr{Cvoid}
    cFreeFMUState::Ptr{Cvoid}
    cSerializedFMUStateSize::Ptr{Cvoid}
    cSerializeFMUState::Ptr{Cvoid}
    cDeSerializeFMUState::Ptr{Cvoid}
    cGetDirectionalDerivative::Ptr{Cvoid}
    cGetAdjointDerivative::Ptr{Cvoid}
    cEvaluateDiscreteStates::Ptr{Cvoid}
    cGetNumberOfVariableDependencies::Ptr{Cvoid}
    cGetVariableDependencies::Ptr{Cvoid}

    # Co Simulation function calls
    cGetOutputDerivatives::Ptr{Cvoid}
    cEnterStepMode::Ptr{Cvoid}
    cDoStep::Ptr{Cvoid}

    # Model Exchange function calls
    cGetNumberOfContinuousStates::Ptr{Cvoid}
    cGetNumberOfEventIndicators::Ptr{Cvoid}
    cGetContinuousStates::Ptr{Cvoid}
    cGetNominalsOfContinuousStates::Ptr{Cvoid}
    cEnterContinuousTimeMode::Ptr{Cvoid}
    cSetTime::Ptr{Cvoid}
    cSetContinuousStates::Ptr{Cvoid}
    cGetContinuousStateDerivatives::Ptr{Cvoid}
    cGetEventIndicators::Ptr{Cvoid}
    cCompletedIntegratorStep::Ptr{Cvoid}
    cEnterEventMode::Ptr{Cvoid}
    cUpdateDiscreteStates::Ptr{Cvoid}

    # Scheduled Execution function calls
    cSetIntervalDecimal::Ptr{Cvoid}
    cSetIntervalFraction::Ptr{Cvoid}
    cGetIntervalDecimal::Ptr{Cvoid}
    cGetIntervalFraction::Ptr{Cvoid}
    cGetShiftDecimal::Ptr{Cvoid}
    cGetShiftFraction::Ptr{Cvoid}
    cActivateModelPartition::Ptr{Cvoid}

    # paths of ziped and unziped FMU folders
    path::String
    binaryPath::String
    zipPath::String

    # execution configuration
    executionConfig::FMU3ExecutionConfiguration
    hasStateEvents::Union{Bool, Nothing}
    hasTimeEvents::Union{Bool, Nothing}

    # c-libraries
    libHandle::Ptr{Nothing}

    # START: experimental section (to FMIFlux.jl)
    dependencies::Matrix
    # linearization jacobians
    A::Matrix{fmi3Float64}
    B::Matrix{fmi3Float64}
    C::Matrix{fmi3Float64}
    D::Matrix{fmi3Float64}

    # END: experimental section

    # Constructor
    function FMU3()
        inst = new()
        inst.instances = []

        inst.hasStateEvents = nothing
        inst.hasTimeEvents = nothing

        inst.executionConfig = FMU3_EXECUTION_CONFIGURATION_NO_RESET
        return inst
    end
end

""" Overload the Base.show() function for custom printing of the FMU3"""
Base.show(io::IO, fmu::FMU3) = print(io,
"Model name:       $(fmu.modelName)
Instance name:     $(fmu.instanceName)
Model description: $(fmu.modelDescription)
Type:              $(fmu.type)
Instances:        $(fmu.instances)"
)

"""
Formats the fmi3Status into a String.
"""
function fmi3StatusToString(status::fmi3Status)
    if status == fmi3StatusOK
        return "OK"
    elseif status == fmi3StatusWarning
        return "Warning"
    elseif status == fmi3StatusDiscard
        return "Discard"
    elseif status == fmi3StatusError
        return "Error"
    elseif status == fmi3StatusFatal
        return "Fatal"
    else
        return "Unknown"
    end
end

"""
Formats the fmi3Status into a Integer.
"""
function fmi3StatusToString(status::Integer)
    if status == fmi3StatusOK
        return "OK"
    elseif status == fmi3StatusWarning
        return "Warning"
    elseif status == fmi3StatusDiscard
        return "Discard"
    elseif status == fmi3StatusError
        return "Error"
    elseif status == fmi3StatusFatal
        return "Fatal"
    else
        return "Unknown"
    end
end

function fmi3CausalityToString(c::fmi3Causality)
    if c == fmi3CausalityParameter
        return "parameter"
    elseif c == fmi3CausalityCalculatedParameter
        return "calculatedParameter"
    elseif c == fmi3CausalityInput
        return "input"
    elseif c == fmi3CausalityOutput
        return "output"
    elseif c == fmi3CausalityLocal
        return "local"
    elseif c == fmi3CausalityIndependent
        return "independent"
    elseif c == fmi3CausalityStructuralParameter
        return "structuralParameter"
    else
        @assert false "fmi3CausalityToString(...): Unknown causality."
    end
end

function fmi3StringToCausality(s::String)
    if s == "parameter"
        return fmi3CausalityParameter
    elseif s == "calculatedParameter"
        return fmi3CausalityCalculatedParameter
    elseif s == "input"
        return fmi3CausalityInput
    elseif s == "output"
        return fmi3CausalityOutput
    elseif s == "local"
        return fmi3CausalityLocal
    elseif s == "independent"
        return fmi3CausalityIndependent
    elseif s == "structuralParameter"
        return fmi3CausalityStructuralParameter
    else
        @assert false "fmi3StringToCausality($(s)): Unknown causality."
    end
end

function fmi3VariabilityToString(c::fmi3Variability)
    if c == fmi3VariabilityConstant
        return "constant"
    elseif c == fmi3VariabilityFixed
        return "fixed"
    elseif c == fmi3VariabilityTunable
        return "tunable"
    elseif c == fmi3VariabilityDiscrete
        return "discrete"
    elseif c == fmi3VariabilityContinuous
        return "continuous"
    else
        @assert false "fmi3VariabilityToString(...): Unknown variability."
    end
end

function fmi3StringToVariability(s::String)
    if s == "constant"
        return fmi3VariabilityConstant
    elseif s == "fixed"
        return fmi3VariabilityFixed
    elseif s == "tunable"
        return fmi3VariabilityTunable
    elseif s == "discrete"
        return fmi3VariabilityDiscrete
    elseif s == "continuous"
        return fmi3VariabilityContinuous
    else
        @assert false "fmi3StringToVariability($(s)): Unknown variability."
    end
end

function fmi3InitialToString(c::fmi3Initial)
    if c == fmi3InitialApprox
        return "approx"
    elseif c == fmi3InitialExact
        return "exact"
    elseif c == fmi3InitialCalculated
        return "calculated"
    else
        @assert false "fmi3InitialToString(...): Unknown initial."
    end
end

function fmi3StringToInitial(s::String)
    if s == "approx"
        return fmi3InitialApprox
    elseif s == "exact"
        return fmi3InitialExact
    elseif s == "calculated"
        return fmi3InitialCalculated
    else
        @assert false "fmi3StringToInitial($(s)): Unknown initial."
    end
end

function fmi3TypeToString(c::fmi3Type)
    if c == fmi3TypeCoSimulation
        return "coSimulation"
    elseif c == fmi3TypeModelExchange
        return "modelExchange"
    elseif c == fmi3TypeScheduledExecution
        return "scheduledExecution"
    else
        @assert false "fmi3TypeToString(...): Unknown type."
    end
end

function fmi3StringToType(s::String)
    if s == "coSimulation"
        return fmi3TypeCoSimulation
    elseif s == "modelExchange"
        return fmi3TypeModelExchange
    elseif s == "scheduledExecution"
        return fmi3TypeScheduledExecution
    else
        @assert false "fmi3StringToInitial($(s)): Unknown type."
    end
end

function fmi3IntervalQualifierToString(c::fmi3IntervalQualifier)
    if c == fmi3IntervalQualifierIntervalNotYetKnown
        return "intervalNotYetKnown"
    elseif c == fmi3IntervalQualifierIntervalUnchanged
        return "intervalUnchanged"
    elseif c == fmi3IntervalQualifierIntervalChanged
        return "intervalChanged"
    else
        @assert false "fmi3IntervalQualifierToString(...): Unknown intervalQualifier."
    end
end

function fmi3StringToIntervalQualifier(s::String)
    if s == "intervalNotYetKnown"
        return fmi3IntervalQualifierIntervalNotYetKnown
    elseif s == "intervalUnchanged"
        return fmi3IntervalQualifierIntervalUnchanged
    elseif s == "intervalChanged"
        return fmi3IntervalQualifierIntervalChanged
    else
        @assert false "fmi3StringToIntervalQualifier($(s)): Unknown intervalQualifier."
    end
end

function fmi3DependencyKindToString(c::fmi3DependencyKind)
    if c == fmi3DependencyKindIndependent
        return "independent"
    elseif c == fmi3DependencyKindConstant
        return "constant"
    elseif c == fmi3DependencyKindFixed
        return "fixed"
    elseif c == fmi3DependencyKindTunable
        return "tunable"
    elseif c == fmi3DependencyKindDiscrete
        return "discrete"
    elseif c == fmi3DependencyKindDependent
        return "dependent"
    else
        @assert false "fmi3DependencyKindToString(...): Unknown dependencyKind."
    end
end

function fmi3StringToDependencyKind(s::AbstractString)
    if s == "independent"
        return fmi3DependencyKindIndependent
    elseif s == "constant"
        return fmi3DependencyKindConstant
    elseif s == "fixed"
        return fmi3DependencyKindFixed
    elseif s == "tunable"
        return fmi3DependencyKindTunable
    elseif s == "discrete"
        return fmi3DependencyKindDiscrete
    elseif s == "dependent"
        return fmi3DependencyKindDependent
    else
        @assert false "fmi3StringToDependencyKind($(s)): Unknown dependencyKind."
    end
end

function fmi3VariableNamingConventionToString(c::fmi3VariableNamingConvention)
    if c == fmi3VariableNamingConventionFlat
        return "flat"
    elseif c == fmi3VariableNamingConventionStructured
        return "structured"
    else
        @assert false "fmi3VariableNamingConventionToString(...): Unknown variableNamingConvention."
    end
end

function fmi3StringToVariableNamingConvention(s::String)
    if s == "flat"
        return fmi3VariableNamingConventionFlat
    elseif s == "structured"
        return fmi3VariableNamingConventionStructured
    else
        @assert false "fmi3StringToVariableNamingConvention($(s)): Unknown variableNamingConvention."
    end
end


module JuCUDA

import Compat: String, view

include("cuda/CUDA.jl")
include("CURAND.jl")
include("NVRTC.jl")

using .CUDA, .CURAND, .NVRTC

const array_h = open(readstring, joinpath(Pkg.dir("JuCUDA"),"src/kernels/array.h"))

function compile(src::String)
    name = src[1:searchindex(src,"(")-1]
    src = """
    #include "array.h"
    extern \"C\" __global__ void $(src)
    """
    ptx = NVRTC.compile(src, [array_h], ["array.h"])
    CuFunction(CuModule(ptx), name)
end

#=
macro nvrtc(expr, T...)
    sym = [gensym()]
    quote
        local s = Symbol($sym[1], $(T...))
        get!(functions, s) do
            local src = $(esc(expr))
            local name = src[1:searchindex(src,"(")-1]
            src = """
            #include "array.h"
            extern \"C\" __global__ void $(src)
            """
            ptx = NVRTC.compile(src, [array_h], ["array.h"])
            CuFunction(CuModule(ptx), name)
        end
    end
end
=#

abstract AbstractCuArray{T,N}
include("abstractcuarray.jl")
include("array.jl")
include("arraymath.jl")
include("subarray.jl")
include("cuarray.jl")
#include("reduce.jl")

include("CUBLAS.jl")
using .CUBLAS

#import CUDA: CuFunction, CuModule
const functions = Dict{Symbol,CuFunction}()

cstring(::Type{Float16}) = "half"
cstring(::Type{Float32}) = "float"
cstring(::Type{Float64}) = "double"
cstring(::Type{Int32}) = "int"
cstring(::Type{Int64}) = "long long int"

function read_function(src_path::String)
    src = open(readlines, src_path)
    # TODO: avoid fixed headers
    ptx = NVRTC.compile(join(src), [array_h], ["array.h"])
    m = CuModule(ptx)
    for l in src
        startswith(l, "// ") || break
        items = split(l)
        for i = 2:length(items)
            s = Symbol(items[i])
            functions[s] = CuFunction(m, string(s))
        end
    end
end

for name in [
    #"array.cu",
    "arraymath.cu",
    #"vector.cu",
    #"reduce.cu"
    ]
    read_function(joinpath(Pkg.dir("JuCUDA"),"src/kernels/$name"))
end

end


# Ex 3.1
println("First number: ")
num1 = parse(Int, chomp(readline()))
println("Second number: ")
num2 = parse(Int, chomp(readline()))

println(num1, " ", num2, " Average: ", Int(floor( (num1 + num2) / 2 )))

# Ex 3.2
# echo "54" > nums_file
# echo "72" >> nums_file
# cat nums_file | julia ex3.jl

# Ex 3.3

function count_negative(ln)
    count = 0
    match = findfirst(isequal('-'), ln)
    while match !== nothing
        count += 1
        match = findnext(isequal('-'), ln, match + 1)
    end
    count
end

num_neg = open("ex1.dat") do f
    num_neg = 0
    for ln in eachline(f)
        num_neg += count_negative(ln)
    end
    num_neg
end

# Ex 3.4
temperature = chomp(readline())
temp_num = parse(Int, temperature[1:end-1])
temp_scale = temperature[end]

if temp_scale == 'F'
    println((temp_num-32)/1.8, "C")
elseif temp_scale == 'C'
    println(temp_num*1.8 + 32, "F")
end

# Ex 3.5
function extract_acc(ln)
    last_index = findfirst(isequal(' '), ln)
    ln[2:last_index-1]
end

accs = open("/home/frederik/julia_course/data/orphans.sp") do f
    accs = String[]
    for ln in eachline(f)
        if ln[1] == '>'
            push!(accs, extract_acc(ln))
        end
    end
    accs
end

println(accs)

# Ex 3.6

# Ex 3.7-8
function read_dna(f)
    dna = ""
    for ln in eachline(f)
        dna = string(dna, rstrip(ln))
    end
    dna
end

in_dna = open(read_dna, "/home/frederik/julia_course/data/dna.dat")

function complement(str)
    trans = Dict('A' => 'T', 'T' => 'A', 'C' => 'G', 'G' => 'C')
    trans_str = map(x -> trans[x], str)
end

function reverse_complement(str)
    reverse(complement(str))
end

println(in_dna)
println(reverse_complement(in_dna))

# Ex 3.9
function to_outfile(seq)
    outfile = open("revdna.dat", "w")

    for i in 1:60:length(seq)
        if i+59 > length(seq)
            println(outfile, seq[i:end])
        else
            println(outfile, seq[i:(i+59)])
        end
    end

    close(outfile)
end

to_outfile(reverse_complement(in_dna))

# Ex 3.10
function read_fasta(f)
    headlines = String[]
    seqs = String[]
    seq = ""
    for line = eachline(f)
        ln = chomp(line)
        if ln[1] == '>'
            if seq != ""
                push!(seqs, seq)
                seq = ""
            end
            push!(headlines, ln)
        else
            seq *= ln
        end
    end
    push!(seqs, seq)
    headlines, seqs
end

open("data/revdna.fsa", "w") do outfile
    headlines, seqs = open(read_fasta, "data/dna.fsa")
    for (headline, seq) in zip(headlines, seqs)
        println(outfile, "$headline ComplementStrand")
        revseq = reverse_complement(seq)
        for i in 1:60:length(revseq)
            if i + 59 > length(revseq)
                println(outfile, revseq[i:end])
            else
                println(outfile, revseq[i:(i + 59)])
            end
        end
    end
end

# Exercise 11
function count_ATCG(seq)
    AT = 0
    GC = 0
    for char in seq
        if char in ('A', 'T')
            AT += 1
        elseif char in ('G', 'C')
            GC += 1
        end
    end
    return string("AT/GC content: ", AT/GC)
end

headlines, seqs = open(read_fasta, "data/dna.fsa")
println(count_ATCG(seqs[1]))

# Exercise 12
function bullseye(dimensions = (40, 40), c1 = "*", c2 = "-", c3 = " ")
    xdim, ydim = dimensions
    for x in 1:xdim
        for y in 1:ydim
            _r = sqrt((x-xdim/2)^2+(y-ydim/2)^2)
            if _r < 5
                print(c1)
            elseif _r < 20
                print(c2)
            else
                print(c3)
            end
        end
        println()
    end
end

bullseye()


# https://docs.julialang.org/en/latest/manual/parallel-computing/#The-@threads-Macro-1

# july 5, 2018
# threading does not seem to improve performance when applied to the innermost get_deviances loop

# ramp up
include(joinpath(Pkg.dir("DecisionTrees"), "testing", "prepare_a_dtmtable.jl"))

using StatsBase

function get_deviances_threaded(a::DecisionTrees.PoissonDevianceSplit, current_meanl::Float64, current_meanr::Float64, lo, ooo, f, numerator::Array{Float64,1}, denominator::Array{Float64,1}, weight::Array{Float64,1}, elementsInLeftChildBV)
    # for the poisson deviance consider the derivative of the poisson loss (or google, the reacfin paper or the 'axa' master thesis)
	# this is the core function of the modelling process
	# besides copying of the data, the vast majority of time is spent in here!
	# most of the time is spent here, if we can improve the for loop below, that would improve performance greatly!
	# one possibility would be to introduce parallelization here (which is not straightforward, though

	# dr and dl are 'reused' by each iteration
	dr = Threads.Atomic{Float64}(0.0)
	dl = Threads.Atomic{Float64}(0.0)
	# note: inbounds increases efficiency here (about a factor of 2), however if the bounds are violated something nasty might happen (quote: If the subscripts are ever out of bounds, you may suffer crashes or silent corruption.)
	Threads.@threads for count in 1:length(f)
		@inbounds idx = f.parent.refs[count] + ooo
		@inbounds ni = numerator[count]
        @inbounds wi = weight[count]
		@inbounds eli = elementsInLeftChildBV[idx]
        if eli
            if iszero(ni)
                Threads.atomic_add!(dl, -(ni - wi * current_meanl))
            else
                Threads.atomic_add!(dl, -(ni - wi * current_meanl) - ni * log(wi * current_meanl / ni))
            end
        else
            if iszero(ni)
                Threads.atomic_add!(dr, -(ni - wi * current_meanr))
            else
                Threads.atomic_add!(dr, -(ni - wi * current_meanr) - ni * log(wi * current_meanr / ni))
            end
        end
	end
	return dl[], dr[]
end

function get_deviancesALTERNATIVE(a::DecisionTrees.PoissonDevianceSplit, current_meanl::Float64, current_meanr::Float64, lo, ooo, f, numerator::Array{Float64,1}, denominator::Array{Float64,1}, weight::Array{Float64,1}, elementsInLeftChildBV)
    # for the poisson deviance consider the derivative of the poisson loss (or google, the reacfin paper or the 'axa' master thesis)
	# this is the core function of the modelling process
	# besides copying of the data, the vast majority of time is spent in here!
	# most of the time is spent here, if we can improve the for loop below, that would improve performance greatly!
	# one possibility would be to introduce parallelization here (which is not straightforward, though

	# dr and dl are 'reused' by each iteration
	dr = 0.0 # Threads.Atomic{Float64}(0.0)
	dl = 0.0 # Threads.Atomic{Float64}(0.0)
	# note: inbounds increases efficiency here (about a factor of 2), however if the bounds are violated something nasty might happen (quote: If the subscripts are ever out of bounds, you may suffer crashes or silent corruption.)
	for count in 1:length(f)
		@inbounds idx = f.parent.refs[count] + ooo
		@inbounds ni = numerator[count]
        @inbounds wi = weight[count]
		@inbounds eli = elementsInLeftChildBV[idx]
        if eli
            if iszero(ni)
                dl += -(ni - wi * current_meanl)
            else
                dl += -(ni - wi * current_meanl) - ni * log(wi * current_meanl / ni)
            end
        else
            if iszero(ni)
                dr += -(ni - wi * current_meanr)
            else
                dr += -(ni - wi * current_meanr) - ni * log(wi * current_meanr / ni)
            end
        end
	end
	return dl, dr
end

function check_equality(dataloops, nloops, nsizeNumerator)
# nloops=2#00

    for i = 1:dataloops
        nsize = nsizeNumerator # 15
        denominatoR = rand(nsize)
        numeratoR = rand(nsize)
        weight = rand(nsize)
        trni = sample(1:nsize, floor(Int, .4 * nsize), replace=false)
        sort!(trni)
        psplit = DecisionTrees.PoissonDevianceSplit()

        for thisloop2 = 1:nloops
            strArr = [randstring(1) for i = 1:nsize]
            floatArr = float.(abs.(rand(Int, nsize) .% 99))
            pdStr = DecisionTrees.PooledArray(strArr)
            pdFloat = DecisionTrees.PooledArray(floatArr)

            if rand() < .5
                pd = pdStr
            else
                pd = pdFloat
            end

            fpd = view(pd, trni)
            lo = one(eltype(fpd.parent.refs)) # UInt8(1)
            ooo = one(lo) - lo
            uqrefsmod = sort(unique(fpd.parent.refs)) .+ ooo
            elLBV = BitVector(rand(length(uqrefsmod)) .< rand())
            current_meanl = rand()
            current_meanr = rand()

            resl, resr = DecisionTrees.get_deviances(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
            resl2, resr2 =    get_deviances_threaded(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
            resl3, resr3 = get_deviancesALTERNATIVE(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
            @assert isapprox(resl, resl2)
            @assert isapprox(resl, resl3)
            @assert isapprox(resr, resr2)
            @assert isapprox(resr, resr3)
        end
    end

    return nothing

end

function timeOLD(dataloops, nloops, nsizeNumerator)
    # nloops=2#00

    for i = 1:dataloops
        nsize = nsizeNumerator # 15
        denominatoR = rand(nsize)
        numeratoR = rand(nsize)
        weight = rand(nsize)
        trni = sample(1:nsize, floor(Int, .4 * nsize), replace=false)
        sort!(trni)
        psplit = DecisionTrees.PoissonDevianceSplit()

        for thisloop2 = 1:nloops
            strArr = [randstring(1) for i = 1:nsize]
            floatArr = float.(abs.(rand(Int, nsize) .% 99))
            pdStr = DecisionTrees.PooledArray(strArr)
            pdFloat = DecisionTrees.PooledArray(floatArr)

            if rand() < .5
                pd = pdStr
            else
                pd = pdFloat
            end

            fpd = view(pd, trni)
            lo = one(eltype(fpd.parent.refs)) # UInt8(1)
            ooo = one(lo) - lo
            uqrefsmod = sort(unique(fpd.parent.refs)) .+ ooo
            elLBV = BitVector(rand(length(uqrefsmod)) .< rand())
            current_meanl = rand()
            current_meanr = rand()

            resl, resr = DecisionTrees.get_deviances(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
            # resl2,resr2=    get_deviances_threaded(psplit,current_meanl,current_meanr,lo,ooo,fpd,numeratoR,denominatoR,weight,elLBV)
            # @assert isapprox(resl,resl2)
            # @assert isapprox(resr,resr2)
        end
    end

    return nothing

end

function timeNEW(dataloops, nloops, nsizeNumerator)
    # nloops=2#00

    for i = 1:dataloops
        nsize = nsizeNumerator # 15
        denominatoR = rand(nsize)
        numeratoR = rand(nsize)
        weight = rand(nsize)
        trni = sample(1:nsize, floor(Int, .4 * nsize), replace=false)
        sort!(trni)
        psplit = DecisionTrees.PoissonDevianceSplit()

        for thisloop2 = 1:nloops
            strArr = [randstring(1) for i = 1:nsize]
            floatArr = float.(abs.(rand(Int, nsize) .% 99))
            pdStr = DecisionTrees.PooledArray(strArr)
            pdFloat = DecisionTrees.PooledArray(floatArr)

            if rand() < .5
                pd = pdStr
            else
                pd = pdFloat
            end

            fpd = view(pd, trni)
            lo = one(eltype(fpd.parent.refs)) # UInt8(1)
            ooo = one(lo) - lo
            uqrefsmod = sort(unique(fpd.parent.refs)) .+ ooo
            elLBV = BitVector(rand(length(uqrefsmod)) .< rand())
            current_meanl = rand()
            current_meanr = rand()

            # resl,resr=DecisionTrees.get_deviances(psplit,current_meanl,current_meanr,lo,ooo,fpd,numeratoR,denominatoR,weight,elLBV)
            resl2, resr2 =    get_deviances_threaded(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
            # @assert isapprox(resl,resl2)
            # @assert isapprox(resr,resr2)
        end
    end

    return nothing

end

function timeALT(dataloops, nloops, nsizeNumerator)
        # nloops=2#00

    for i = 1:dataloops
        nsize = nsizeNumerator # 15
        denominatoR = rand(nsize)
        numeratoR = rand(nsize)
        weight = rand(nsize)
        trni = sample(1:nsize, floor(Int, .4 * nsize), replace=false)
        sort!(trni)
        psplit = DecisionTrees.PoissonDevianceSplit()

        for thisloop2 = 1:nloops
            strArr = [randstring(1) for i = 1:nsize]
            floatArr = float.(abs.(rand(Int, nsize) .% 99))
            pdStr = DecisionTrees.PooledArray(strArr)
            pdFloat = DecisionTrees.PooledArray(floatArr)

            if rand() < .5
                pd = pdStr
            else
                pd = pdFloat
            end

            fpd = view(pd, trni)
            lo = one(eltype(fpd.parent.refs)) # UInt8(1)
            ooo = one(lo) - lo
            uqrefsmod = sort(unique(fpd.parent.refs)) .+ ooo
            elLBV = BitVector(rand(length(uqrefsmod)) .< rand())
            current_meanl = rand()
            current_meanr = rand()

                # resl,resr=DecisionTrees.get_deviances(psplit,current_meanl,current_meanr,lo,ooo,fpd,numeratoR,denominatoR,weight,elLBV)
            resl2, resr2 =    get_deviancesALTERNATIVE(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
                # @assert isapprox(resl,resl2)
                # @assert isapprox(resr,resr2)
        end
    end

    return nothing

end

@time check_equality(10, 20, 200000);

@time timeOLD(10, 20, 200000);
@time timeNEW(10, 20, 200000); # 50% more than old (with 1 thread only!)
@time timeALT(10, 20, 200000); # same as old

using BenchmarkTools

@benchmark get_deviances_threaded(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
@benchmark get_deviancesALTERNATIVE($psplit, $current_meanl, $current_meanr, $lo, $ooo, $fpd, $numeratoR, $denominatoR, $weight, $elLBV)
@benchmark DecisionTrees.get_deviances($psplit, $current_meanl, $current_meanr, $lo, $ooo, $fpd, $numeratoR, $denominatoR, $weight, $elLBV)

@code_warntype get_deviancesALTERNATIVE(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
@code_warntype DecisionTrees.get_deviances(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)
@code_warntype get_deviances_threaded(psplit, current_meanl, current_meanr, lo, ooo, fpd, numeratoR, denominatoR, weight, elLBV)


# __precompile__(true)  "Now the default"
module Symata

import MacroTools
import SpecialFunctions
import REPL
import Markdown
import InteractiveUtils
import Base: setindex!, getindex, replace
import Dates
import Statistics
import Formatting

export @symExpr, @extomx
export Mxpr, getsymata, isympy, margs, mhead, mxpr, mxpra, newargs, pytosj,
       setsymata, sjtopy, @sym, symeval, symparseeval, symparsestring, symprintln, symtranseval,
       unpacktoList
export mmul, mplus, mpow, mabs, mminus, symatamath
export debugmxpr

"""
    devimport()

import some symbols from `Symata` into `Main` that are useful for development.
"""
function devimport()
    Core.eval(Main, Meta.parse("""
       import Symata: @canonexpr, doeval, getpart, infseval, meval, mpmath, mxpr, pytypeof, setpart!,
                      setsymval, symata, symname, sympy, symval!, testex_wrap
"""))
end

export devimport

# FIXME: why export these *and* have devimport ?
export  @canonexpr, doeval, getpart, infseval, meval, mpmath, mxpr, pytypeof, setpart!,
        setsymval, symata, symname, sympy, symval!, testex_wrap

## Set const debugging parametres at compile-time in debug.jl
include("debug.jl") # must use include here, because @inc is defined in debug.jl
@inc("LambertW.jl")  # remove this when Pkg.jl is better developed.
@inc("version.jl")
@inc("util.jl")
@inc("sjcompat.jl")
@inc("mxpr.jl")
@inc("wrapout.jl") # move up once more !
@inc("attributes.jl")
@inc("mxpr_util.jl")
#@inc("wrapout.jl") #
@inc("exceptions.jl")
@inc("level_specification.jl")
@inc("sequence_specification.jl")
@inc("sjiterator.jl")
@inc("sortpattern.jl")
@inc("AST_translation_tables.jl")
@inc("julia_level.jl")
@inc("arithmetic.jl")
@inc("apprules_core.jl")
@inc("constants.jl")
@inc("autoload.jl")
@inc("namedparts.jl")
@inc("AST_translation.jl")
@inc("alteval.jl")
@inc("doc.jl")
@inc("misc_doc.jl")
@inc("symbols.jl")
@inc("random.jl")
@inc("numcomb.jl")
@inc("kernelstate.jl")
@inc("evaluation.jl")
@inc("comparison_logic.jl")
@inc("test.jl")
#@inc("wrapout.jl")
#@inc("formatting/Formatting.jl")
@inc("predicates.jl")
@inc("symata_julia_interface.jl")
@inc("measurements.jl")
@inc("flowcontrol.jl")
@inc("Output.jl")
@inc("system.jl")
@inc("IO.jl")
@inc("latex.jl")
@inc("pattern.jl")
## experimental, eventually replace pattern with this.
#@inc("pattern2.jl")
@inc("parts.jl")
@inc("lists.jl")
@inc("stats.jl")
#@inc("table.jl")
@inc("table_dyn.jl")
@inc("expressions.jl")
@inc("algebra.jl")
@inc("expanda.jl")
@inc("flatten.jl")
@inc("lexcmp.jl")
@inc("sortorderless.jl")
@inc("module.jl")
@inc("strings.jl")
@inc("wrappers.jl")
@inc("keyword_translation.jl")
@inc("sympy.jl")
@inc("math_functions.jl")
# @inc("mittleff_glue.jl") moved mittag-leffler to external dependence. But, it's not registered.
@inc("functionzeros.jl")
@inc("sympy_application.jl")
@inc("alternate_syntax.jl")
@inc("matrix.jl")
@inc("numerical.jl")
@inc("protected_symbols.jl")
@inc("REPL_symata.jl")
@inc("client_symata.jl")
@inc("Jupyter/Jupyter.jl")
@inc("plot.jl")
@inc("docautoloaded.jl")
@inc("function.jl")

import .Jupyter
import .Jupyter: isymata
import .Jupyter: is_isymata_mode
# These are for IJulia. We could probably import insymata in the interface code instead.
export isymata, insymata

function __init__()
    do_init()
end

is_loaded_IJulia() = isdefined(Main, :IJulia)

function do_init()
    init_sympy()
    sjimportall(:System, :Main) # These are Symata "contexts"
    set_current_context(:Main)
    if is_loaded_IJulia() # Use the Jupyter interface
        Jupyter.isymata()
    elseif isinteractive() # Started perhaps from the Julia REPL
        if isdefined(Base, :active_repl)
            RunSymataREPL(Base.active_repl)
        else
            nothing
        end
    else
        nothing
    end
end

end # module Symata

nothing