"""
    config_diagnostics(driver_config, timeend; interval=nothing)

Returns the state and tendency diagnostic groups
"""
function config_diagnostics(driver_config, timeend; interval = nothing)
    FT = eltype(driver_config.grid)
    info = driver_config.config_info
    if interval == nothing
        interval = "$(cld(timeend, 2) + 10)ssecs"
        #interval = "10steps"
    end

    boundaries = [
        FT(0) FT(0) FT(0)
        FT(info.hmax) FT(info.hmax) FT(info.zmax)
    ]
    axes = (
        [FT(1)],
        [FT(1)],
        collect(range(boundaries[1, 3], boundaries[2, 3], step = FT(50)),),
    )
    interpol = ClimateMachine.InterpolationConfiguration(
        driver_config,
        boundaries;
        axes = axes,
    )
    ds_dgngrp = setup_dump_state_diagnostics(
        SingleStackConfigType(),
        interval,
        driver_config.name,
        interpol = interpol,
    )
    dt_dgngrp = setup_dump_tendencies_diagnostics(
        SingleStackConfigType(),
        interval,
        driver_config.name,
        interpol = interpol,
    )
    return ClimateMachine.DiagnosticsConfiguration([ds_dgngrp, dt_dgngrp])
end


@testset "block test" begin
    @testset "flatten test" begin
		circuit = QCircuit(5)
		block2 = QGateBlock()
		block1 = QGateBlock()
		add!(block1, X(1), X(2), X(3), Y(3))
		add!(block2, block1(5,3,2))
		add!(circuit, block2(5,4,3,2,1))
		flatCircuit = QCircuit(5)
		add!(flatCircuit, X(1), X(3), X(4), Y(4))
		@test flatten(circuit) == flatCircuit
	end

	@testset "block extract param test" begin
		circuit = QCircuit(5)
		block2 = QGateBlock()
		block1 = QGateBlock()
		add!(block1, Rx[π](1), Rx[π/2](2), Rx[π/4](3), Ry[π/8](3))
		add!(block2, block1(5,3,2))
		add!(block2, X(1))
		add!(block2, block1(2,3,5))
		params = extractParams(block2)
		@test params ≈ repeat([π, π/2,π/4, π/8],2)
	end

	@testset "block insert param test" begin
	    circuit = QCircuit(5)
		block2 = QGateBlock()
		block1 = QGateBlock()
		add!(block1, Rx(1), X(2), Rx(3), Ry(3))
		addCopy!(block2, block1(5,3,2))
		add!(block2, X(1))
		addCopy!(block2, block1(2,3,5))
		newparams = rand(Float64, 6)
		insertParams!(block2, newparams)
		@test extractParams(block2) ≈ newparams
	end

	@testset "const gate inverse test" begin
	    @test data(inverse(T)) ≈ data(TDAG)
	    @test data(inverse(S)) ≈ data(SDAG)
	end

	@testset "block/circuit inverse test" begin
	    state = MPSState(5)
	    ref = copy(state)
	    circuit = randomQCircuit(5,10)
	    circuit_inv = inverse(circuit)
	    apply!(state, circuit)
	    apply!(state, circuit_inv)
	    @test ref ≈ state
	end
end


module ConstructiveM
    using LoopVectorization

    mutable struct Constructive
        save_layer::Any
        load_layer::Any
        activate::Any
        initialize::Any
        update::Any

        input_shape::Tuple
        output_shape::Tuple

        output::Array{Float32}
        δ::Array{Float32}

        function Constructive(;input_shape::Tuple, shape::Tuple)
            new(save_Constructive, load_Constructive, activate_Constructive, init_Constructive, update_Constructive, input_shape, shape)
        end
    end

    function init_Constructive(layer::Constructive, mini_batch::Int64)
        layer.output = zeros(Float32, layer.output_shape..., mini_batch)
        layer.δ = zeros(Float32, layer.input_shape..., mini_batch)
    end

    function activate_Constructive(layer::Constructive, input::Array{Float32})
        @avxt for i in eachindex(layer.output)
            layer.output[i] = input[i]
        end
    end

    function update_Constructive(layer::Constructive, optimizer::String, input::Array{Float32}, δₗ₊₁::Array{Float32}, α::Float64, parameters::Tuple, direction::Int64=1)
        @avxt for i in eachindex(δₗ₊₁)
            layer.δ[i] = δₗ₊₁[i]
        end
    end

    function save_Constructive(layer::Constructive, file::Any, id::String)
        write(file, id, "Constructive")
        write(file, id*"input_shape", collect(layer.input_shape))
        write(file, id*"shape", collect(layer.output_shape))
    end

    function load_Constructive(layer::Constructive, file::Any, id::String)
    end

    function get_args(file::Any, id::String)
        input_shape = tuple(read(file, id*"input_shape")...)
        shape = tuple(read(file, id*"shape")...)
        return (input_shape=input_shape, shape=shape)
    end
end


load_bits(s::String, skipbytes = 0) = load_bits(UInt, s, skipbytes)

function load_bits(::Type{T}, s::String, skipbytes = 0) where T<:Unsigned
    n = sizeof(s)
    # if n < skipbytes
    #     return zero(T)
    # else
        ns = (sizeof(T) - min(8, n - skipbytes))*8
        h = unsafe_load(Ptr{T}(pointer(s, skipbytes+1)))
        # h = unsafe_load(Ptr{T}(pointer(s)+skipbytes))
        h = h << ns
        h = h >> ns
        return h
    # end
end

load_bits(s::String, skipbytes = 0) = load_bits(UInt, s, skipbytes)

function load_bits(::Type{T}, s::String, skipbytes = 0) where T
    n = sizeof(s)
    if n < skipbytes
        return zero(T)
    elseif n - skipbytes >= sizeof(T)
        return unsafe_load(Ptr{T}(pointer(s, skipbytes+1)))
    else
        ns = (sizeof(T) - min(sizeof(T), n - skipbytes))*8
        h = unsafe_load(Ptr{T}(pointer(s, skipbytes+1)))
        # h = unsafe_load(Ptr{T}(pointer(s)+skipbytes))
        h = h << ns
        h = h >> ns
        return h
    end
end

function load_bits_fast(::Type{T}, s::String) where T
    n = sizeof(s)
    ns = (sizeof(T) - n)*8
    h = unsafe_load(Ptr{T}(pointer(s)))
    h = h << ns
    h = h >> ns
    return h
end

function load_bits_fast_ntoh(::Type{T}, s::String) where T
    n = sizeof(s)
    ns = (sizeof(T) - n)*8
    h = unsafe_load(Ptr{T}(pointer(s)))
    h = h << ns
    h = h >> ns
    return ntoh(h)
end

function roughhash(s::String)
    n = sizeof(s)
    if n >= 8
        return unsafe_load(Ptr{UInt64}(pointer(s)))
    else
        h = zero(UInt64)
        for i = 1:n
            @inbounds h = (h << 8) | codeunit(s, i)
        end
        return h
    end
end

function radixsort_ntoh!(svec::Vector{String}, skipbytes = 0, pointer_type = UInt)
    lens = reduce((x,y) -> max(x,sizeof(y)),0, svec)
    iters = ceil(lens*8/SortingAlgorithms.RADIX_SIZE)
    skipbytes = 0
    for i = iters:-1:1
        skipbytes += sizeof(pointer_type)
        x = ntoh.(unsafe_load.(Ptr{pointer_type}.(pointer.(svec) .+ skipbytes)))
        sorttwo!(x, svec)
    end
end


struct TestQss <: QuerySynthesisStrategy
    optimizer::QuerySynthesisOptimizer
    TestQss(; optimizer::QuerySynthesisOptimizer) = new(optimizer)
end

function qs_score_function(qs::TestQss, x::Array{T, 2}, labels::Dict{Symbol, Array{Int, 1}})::Function where T <: Real
    return x -> -abs.(sum(x, dims=1))
end


# Code for solving assuming that all other cars have a single identical behavior model

mutable struct SingleBehaviorSolver <: Solver
    inner_solver::Solver
    behavior::BehaviorModel
end

mutable struct SingleBehaviorPolicy <: Policy
    inner_policy::Policy
    behavior::BehaviorModel
end

set_rng!(solver::SingleBehaviorSolver, rng::AbstractRNG) = set_rng!(solver.inner_solver, rng)
srand(p::SingleBehaviorPolicy, s) = srand(p.inner_policy, s)

function solve(solver::SingleBehaviorSolver, mdp::NoCrashProblem)
    dmodel = NoCrashIDMMOBILModel(mdp.dmodel, DiscreteBehaviorSet(BehaviorModel[solver.behavior], Weights([1.0])))
    single_behavior_mdp = NoCrashMDP{typeof(mdp.rmodel), DiscreteBehaviorSet}(dmodel, mdp.rmodel, mdp.discount, mdp.throw)

    inner_policy = solve(solver.inner_solver, single_behavior_mdp)
    return SingleBehaviorPolicy(inner_policy, solver.behavior)
end

function action_info(p::SingleBehaviorPolicy, s::Union{MLPhysicalState, MLState})
    as = actions(p.inner_policy.mdp, s)
    if length(as) == 1
        return first(as), Dict(:tree_queries=>missing, :search_time_us=>missing)
    end
    return action_info(p.inner_policy, single_behavior_state(s, p.behavior))
end

action_info(p::SingleBehaviorPolicy, agg::AggressivenessBelief) = action_info(p, agg.physical)

action(p::SingleBehaviorPolicy, s) = first(action_info(p, s))

function single_behavior_state(s::Union{MLState, MLPhysicalState}, behavior)
    new_cars = Vector{CarState}(undef, length(s.cars))
    for (i,c) in enumerate(s.cars)
        new_cars[i] = CarState(c.x, c.y, c.vel, c.lane_change, behavior, c.id)
    end
    return MLState(s, new_cars)
end


# https://gitlab.com/ase/ase/blob/master/doc/ase/calculators/lammpsrun.rst

using ASEPotential

a = atoms([atom("Na", [0,0,0]), atom("Cl", [0,0,2.3608])], [6.5, 6.5, 7.7])
parameters = LAMMPSCalculatorParameters()

@time println(get_potential_energy(a, parameters))
@time println(get_forces(a, parameters))


#!/usr/bin/env julia

using Luxor

using Test

using Random
Random.seed!(42)

function image_testing(fname)
    width, height = 4000, 4000
    margin = 500
    Drawing(width, height, fname)
    origin()
    background("grey25")
    setline(5)
    sethue("green")
    image = readpng(dirname(@__FILE__) * "/stackoverflow.png")
    w = image.width
    h = image.height
    pagetiles = Tiler(width, height, 7, 9)
    tw = pagetiles.tilewidth/2
    for (pos, n) in pagetiles
        circle(pos, tw, :stroke)
        circle(pos, tw, :clip)
        gsave()
        translate(pos)
        scale(25, 25)
        rotate(rand(0.0:pi/8:2pi))
        placeimage(image, O, centered=true)
        grestore()
        clipreset()
    end
    @test finish() == true
end

fname = "test-image.png"
image_testing(fname)
println("...finished test: output in $(fname)")


cell_to_vtkcell(::Type{Line}) = VTKCellTypes.VTK_LINE
cell_to_vtkcell(::Type{QuadraticLine}) = VTKCellTypes.VTK_QUADRATIC_EDGE

cell_to_vtkcell(::Type{Quadrilateral}) = VTKCellTypes.VTK_QUAD
cell_to_vtkcell(::Type{QuadraticQuadrilateral}) = VTKCellTypes.VTK_BIQUADRATIC_QUAD
cell_to_vtkcell(::Type{Triangle}) = VTKCellTypes.VTK_TRIANGLE
cell_to_vtkcell(::Type{QuadraticTriangle}) = VTKCellTypes.VTK_QUADRATIC_TRIANGLE
cell_to_vtkcell(::Type{Cell{2,8,4}}) = VTKCellTypes.VTK_QUADRATIC_QUAD

cell_to_vtkcell(::Type{Hexahedron}) = VTKCellTypes.VTK_HEXAHEDRON
cell_to_vtkcell(::Type{Tetrahedron}) = VTKCellTypes.VTK_TETRA
cell_to_vtkcell(::Type{QuadraticTetrahedron}) = VTKCellTypes.VTK_QUADRATIC_TETRA

"""
    vtk_grid(filename::AbstractString, grid::Grid)

Create a unstructured VTK grid from a `Grid`. Return a `DatasetFile`
which data can be appended to, see `vtk_point_data` and `vtk_cell_data`.
"""
function WriteVTK.vtk_grid(filename::AbstractString, grid::Grid{dim,N,T}) where {dim,N,T}
    celltype = cell_to_vtkcell(getcelltype(grid))
    cls = MeshCell[]
    for cell in CellIterator(grid)
        push!(cls, MeshCell(celltype, copy(getnodes(cell))))
    end
    coords = reinterpret(T, getnodes(grid), (dim, getnnodes(grid)))
    return vtk_grid(filename, coords, cls)
end

function WriteVTK.vtk_point_data(vtk::WriteVTK.DatasetFile, data::Vector{Vec{dim,T}}, name::AbstractString) where {dim,T}
    npoints = length(data)
    data = reinterpret(T, data, (dim, npoints))
    return vtk_point_data(vtk, data, name)
end

function vtk_nodeset(vtk::WriteVTK.DatasetFile, grid::Grid{dim}, nodeset::String) where {dim}
    z = zeros(getnnodes(grid))
    z[collect(getnodeset(grid, nodeset))] = 1.0
    vtk_point_data(vtk, z, nodeset)
end

function vtk_cellset(vtk::WriteVTK.DatasetFile, grid::Grid{dim}, cellset::String) where {dim}
    z = zeros(getncells(grid))
    z[collect(getcellset(grid, cellset))] = 1.0
    vtk_cell_data(vtk, z, cellset)
end


# some utils shared among algorithms

# k: gap length
function affinegap_score(k, gap_open_penalty, gap_extend_penalty)
    return -(gap_open_penalty + gap_extend_penalty * k)
end

# trace type for pairwise alignment
typealias Trace UInt8

# trace bitmap
const TRACE_NONE      = 0b00000000
const TRACE_MATCH     = 0b00000001
const TRACE_GAPOPEN_A = 0b00000010
const TRACE_GAPEXTD_A = 0b00000100
const TRACE_GAPOPEN_B = 0b00001000
const TRACE_GAPEXTD_B = 0b00010000
const TRACE_DELETE    = 0b00100000
const TRACE_INSERT    = 0b01000000

# update counts of a gapped sequence
function update_counts!(counts, isgap)
    if isgap
        if counts[end] > 0
            push!(counts, 0, 0)
        end
        counts[end-1] += 1
    else
        counts[end] += 1
    end
    return counts
end

# traceback utils:
#   * match
#   * gap
#   * gapext
#   * gapopen

macro match()
    esc(quote
        update_counts!(counts_a, false)
        update_counts!(counts_b, false)
        i -= 1
        j -= 1
        gapext_a = false
        gapext_b = false
        continue
    end)
end

macro gap(seq)
    if seq == :a
        return esc(quote
            update_counts!(counts_a, true)
            update_counts!(counts_b, false)
            j -= 1
            continue
        end)
    elseif seq == :b
        return esc(quote
            update_counts!(counts_a, false)
            update_counts!(counts_b, true)
            i -= 1
            continue
        end)
    end
    error("unknown seq: $seq")
end

macro gapopen(seq)
    if seq == :a
        return esc(quote
            gapext_a = false
            @gap a
        end)
    elseif seq == :b
        return esc(quote
            gapext_b = false
            @gap b
        end)
    end
    error("unknown seq: $seq")
end

macro gapext(seq)
    if seq == :a
        return esc(quote
            gapext_a = true
            @gap a
        end)
    elseif seq == :b
        return esc(quote
            gapext_b = true
            @gap b
        end)
    end
    error("unknown seq: $seq")
end


function img_similarity(pathtoimage)
    imagelist = glob("*.jpg", pathtoimage)
    append!(imagelist,  glob("*.JPG", pathtoimage))
    append!(imagelist,  glob("*.png", pathtoimage))
    append!(imagelist,  glob("*.PNG", pathtoimage))

    temp = Array{Array{Float64,2},1}(undef,length(imagelist))
    @showprogress "Loading Images " for i in 1:length(imagelist)
        temp[i] = Float64.(Gray.(imresize(load(imagelist[i]),128,128)))
    end

    similarity_matrix = Matrix{Float64}(undef, length(temp), length(temp))
    for i in 1:length(temp)
        for j in 1:length(temp)
            similarity_matrix[j,i] = assess_ssim(temp[j],temp[i])
        end
    end
    return similarity_matrix
end


mutable struct History{I,V} <: UnivalueHistory{I}
    lastiter::I
    iterations::Vector{I}
    values::Vector{V}

    function History(::Type{V}, ::Type{I} = Int) where {I,V}
        new{I,V}(typemin(I), Array{I}(undef, 0), Array{V}(undef, 0))
    end
end

Base.length(history::History) = length(history.iterations)
Base.enumerate(history::History) = zip(history.iterations, history.values)
Base.first(history::History) = history.iterations[1], history.values[1]
Base.last(history::History) = history.iterations[end], history.values[end]
Base.get(history::History) = history.iterations, history.values

function Base.push!(
        history::History{I,V},
        iteration::I,
        value::V) where {I,V}
    lastiter = history.lastiter
    iteration > lastiter || throw(ArgumentError("Iterations must increase over time"))
    history.lastiter = iteration
    push!(history.iterations, iteration)
    push!(history.values, value)
    value
end

function Base.push!(
        history::History{I,V},
        value::V) where {I,V}
    lastiter = history.lastiter == typemin(I) ? zero(I) : history.lastiter
    iteration = lastiter + one(history.lastiter)
    history.lastiter = iteration
    push!(history.iterations, iteration)
    push!(history.values, value)
    value
end

Base.print(io::IO, history::History{I,V}) where {I,V} = print(io, "$(length(history)) elements {$I,$V}")

function Base.show(io::IO, history::History{I,V}) where {I,V}
    println(io, "History")
    println(io, "  * types: $I, $V")
    print(io,   "  * length: $(length(history))")
end

function Base.resize!(history::History, n)
    iter, vals = get(history)
    resize!(iter, n)
    resize!(vals, n)
    history.lastiter = last(iter)

    history
end

"""
    increment!(trace, iter, val)

Increments the value for a given iteration if it exists, otherwise adds the iteration with an ordinary push.
"""
function increment!(trace::History{I,V}, iter::Number, val)  where {I,V}
    if !isempty(trace.iterations)
        if trace.lastiter == iter # Check most common case to make it faster
            i = length(trace.iterations)
        else
            i = findfirst(isequal(iter), trace.iterations)
        end
        if i != nothing
            return (trace.values[i] += val)
        end
    end
    push!(trace, iter, val)
end


# Implements following functions
# - descent path: monotonically decreasing states visited (best solution throughout search)
# - acceptance path (current solution throughout search)

@recipe function descent_path(df::DataFrame, type::Type{SearchTrace}; xax = :iteration, yax = :value)
    ldf = df[:, :accepted .== true & :delta .<= 0]
    x = ldf[:,xax]
    y = ldf[:,yax]

    seriestype --> :scatter
    return x,y
end


include("Line.jl")
include("Edge.jl")
include("Ellipse.jl")


"""
    StructView{T, N, IT} <: AbstractView{T, N}
A type-view to array of structures as a structure of field arrays.
"""
struct StructView{T,N,IT} <: AbstractView{T,N}
    parent
    fields

    StructView{T,N}(parent, fields) where {T,N} = new{T,N,IndexStyle(parent)}(parent, fields)
end

function _hasnofields(t)
    !(t isa DataType || t isa UnionAll) || t === Any || (:abstract in propertynames(t) && t.abstract)
end

function _createfieldview(parenttype, parent, field, typestack)
    t = fieldtype(parenttype, field)
    fieldview = FieldView{t,field,ndims(parent)}(parent)

    if _hasnofields(t)
        return fieldview
    end
    fields = fieldnames(t)
    if isempty(fields)
        return fieldview
    else
        return _newstructview(fieldview, typestack)
    end
end

function _getfields(parent::A, typestack) where {A <: AbstractArray}
    parenttype = eltype(parent)
    if _hasnofields(parenttype)
        return parent
    end
    fields = filter(collect(fieldnames(parenttype))) do field
        t = fieldtype(parenttype, field)
        return !(t in typestack)
    end
    return NamedTuple{(fields...,)}([_createfieldview(parenttype, parent, field, typestack) for field in fields])
end

function _newstructview(parent::A, typestack) where {A <: AbstractArray}
    push!(typestack, eltype(parent))
    fields = _getfields(parent, typestack)
    delete!(typestack, eltype(parent))
    StructView{eltype(parent),ndims(parent)}(parent, fields)
end

"""
    StructView(parent::AbstractArray)
Create a new StructView, a view to array of structures as a structure of field arrays.
"""
function StructView(parent::A) where {A <: AbstractArray}
    typestack = Set(Type[])
    _newstructview(parent, typestack)
end

Base.@propagate_inbounds Base.getindex(view::StructView, i...) = getindex(view.parent, i...)

Base.@propagate_inbounds Base.setindex!(view::StructView, v, i...) = setindex!(view.parent, v, i...)

@inline Base.parent(view::StructView) = view.parent

function Base.getproperty(view::StructView, field::Symbol)
    fields = getfield(view, :fields)
    if haskey(fields, field)
        return fields[field]
    end
    return getfield(view, field)
end

function Base.propertynames(view::StructView)
    return (fieldnames(StructView)..., keys(view.fields)...)
end

function Base.push!(view::StructView{T,1,IT}, item) where {T,IT}
    push!(parent(view), item)
    return view
end

Base.pop!(view::StructView{T,1,IT}) where {T,IT} = pop!(parent(view))

function Base.append!(view::StructView{T,1,IT}, iter) where {T,IT}
    append!(parent(view), iter)
    return view
end

index_type(::Type{StructView{T,N,IT}}) where {T,N,IT} = IT


using DrWatson
@quickactivate "Medical segmentation evaluation"
2+2
datadir()


Base.flipsign(x::SSigned, y::SFloat16{Y}) where Y = flipsign(x, SInt16{Base.bitcast(Int16, Y::Float16)}())
Base.flipsign(x::SSigned, y::SFloat32{Y}) where Y = flipsign(x, SInt32{Base.bitcast(Int32, Y::Float32)}())
Base.flipsign(x::SSigned, y::SFloat64{Y}) where Y = flipsign(x, SInt{Base.bitcast(Int64, Y::Float64)}())
#Base.flipsign(x::SSigned, y::Real) = flipsign(x, -oftype(x, signbit(y)))

Base.copysign(x::SSigned, y::SSigned)  = flipsign(x, x ⊻ y)
Base.copysign(x::SSigned, y::SFloat16) = copysign(x, SInt16{Base.bitcast(Int16, Y::Float16)}())
Base.copysign(x::SSigned, y::SFloat32) = copysign(x, SInt32{Base.bitcast(Int32, Y::Float32)}())
Base.copysign(x::SSigned, y::SFloat64) = copysign(x, SInt{Base.bitcast(Int64, Y::Float64)}())
#Base.copysign(x::SSigned, y::SReal)    = copysign(x, -oftype(x, signbit(y)))

Base.:(==)(x::SSigned,   y::SUnsigned) = (x >= 0) & (unsigned(x) == y)
Base.:(==)(x::SUnsigned, y::SSigned  ) = (y >= 0) & (x == unsigned(y))
Base.:(<)( x::SSigned,   y::SUnsigned) = (x <  0) | (unsigned(x) <  y)
Base.:(<)( x::SUnsigned, y::SSigned  ) = (y >= 0) & (x <  unsigned(y))
Base.:(<=)(x::SSigned,   y::SUnsigned) = (x <  0) | (unsigned(x) <= y)
Base.:(<=)(x::SUnsigned, y::SSigned ) = (y >= 0) & (x <= unsigned(y))
# unsigned shift counts always shift in the same direction

Base.rem2pi(::SInt32{X}, r::RoundingMode) where X = SFloat64{rem2pi(X::Int32, r)}()
Base.rem2pi(::SInt64{X}, r::RoundingMode) where X = SFloat64{rem2pi(X::Int64, r)}()

Base.invmod(::SInteger, ::SIntegerZeroType) = throw(DomainError(m, "`m` must not be 0."))

for (ST,BT) in SI2BI
    @eval begin
        Base.:(>>)(x::$ST{X}, y::SUnsigned{Y}) where {X,Y} = $ST{>>(X::$BT, Y)}()
        Base.:(<<)(x::$ST{X}, y::SUnsigned{Y}) where {X,Y} = $ST{<<(X::$BT, Y)}()
        Base.:(>>>)(x::$ST{X}, y::SUnsigned{Y}) where {X,Y} = $ST{>>>(X::$BT, Y)}()
        Base.:(^)(::$ST{X}, y::SUnsigned{Y}) where {X,Y} = $ST{^(X::$BT, Y::$BT)}()
        @generated function Base.lcm(a::$ST{A}, b::$ST{B}) where {A,B}
            out = $ST{lcm(A::$BT,B::$BT)}()
            quote
                $out
            end
        end
        (/)(x::$ST, y::$ST) where {T<:Integer} = float(x) / float(y)
        function Base.invmod(n::$ST, m::$ST)
            g, x, y = gcdx(n, m)
            g != 1 && throw(DomainError((n, m), "Greatest common divisor is $g."))
            # Note that m might be negative here.
            # For unsigned T, x might be close to typemax; add m to force a wrap-around.
            r = mod(x + m, m)
            # The postcondition is: mod(r * n, m) == mod(T(1), m) && div(r, m) == 0
            r
        end
    end
end
Base.:(>>)(x::SInteger, y::SInt) =
    ifelse(0 <= y, x >> unsigned(y), x << unsigned(-y))
Base.:(<<)(x::SInteger, y::SInt) =
    ifelse(0 <= y, x << unsigned(y), x >> unsigned(-y))
Base.:(>>>)(x::SInteger, y::SInt) =
    ifelse(0 <= y, x >>> unsigned(y), x << unsigned(-y))

Base.:(<<)(x::SInteger, y::BaseInteger) = <<(values(x), y)

#
Base.:(>>)(x::SInteger, y::Int) = >>(values(x), y)
Base.:(>>)(x::BaseInteger, y::SInt) = >>(x, values(y))
Base.:(<<)(x::SInteger, y::Int) = <<(values(x), y)
Base.:(<<)(x::BaseInteger, y::SInt) = <<(x, values(y))
Base.:(>>>)(x::SInteger, y::Int) = >>(values(x), y)
Base.:(>>>)(x::BaseInteger, y::SInt) = >>(x, values(y))

#SIntegerNegOneType = Union{SInt8{-Int8(1)},SInt16{-Int16(1)},SInt32{-Int32(1)},SInt64{-1},SInt128{-Int128(1)}}

Base.div(x::SSigned, y::SUnsigned) = flipsign(signed(div(unsigned(abs(x)), y)), x)
Base.div(x::SUnsigned, y::SSigned) = unsigned(flipsign(signed(div(x, unsigned(abs(y)))), y))

Base.rem(x::SSigned, y::SUnsigned) = flipsign(signed(rem(unsigned(abs(x)), y)), x)
Base.rem(x::SUnsigned, y::SSigned) = rem(x, unsigned(abs(y)))

Base.fld(x::SSigned, y::SUnsigned) = div(x, y) - (signbit(x) & (rem(x, y) != 0))
Base.fld(x::SUnsigned, y::SSigned) = div(x, y) - (signbit(y) & (rem(x, y) != 0))

Base.bswap(x::SInt8) = x
Base.bswap(x::SUInt8) = x
# TODO: bswap other stuff (probably unnecessary, but why not)

@inline _gcdxrem(x::Tuple{A,B}) where {A<:SInteger,B<:SInteger} = (x[2], rem(x[1], x[2]))
@inline _gcdxsub(q::SInteger, x::Tuple{A,B}) where {A<:SInteger,B<:SInteger} = (x[2], x[1] - q*x[2])

@inline function _gcdxwhile(
    ab::Tuple{A,B},s::Tuple{S0,S1},t::Tuple{T0,T1}
   ) where {A<:SInteger,B<:SInteger,S0<:SInteger,S1<:SInteger,T0<:SInteger,T1<:SInteger}
    _gcdxwhile(_gcdxrem(ab),
               _gcdxsub(div(ab[1],ab[2]), s),
               _gcdxsub(div(ab[1],ab[2]), t))
end

@inline function _gcdxwhile(ab::Tuple{A,B},
                            s::Tuple{S0,S1},
                            t::Tuple{T0,T1}
   ) where {A<:SInteger,B<:SIntegerZeroType,
            S0<:SInteger,S1<:SInteger,
            T0<:SInteger,T1<:SInteger}
    if ab[1] < 0
        return (-ab[1], -s[1], -t[1])
    else
        return (ab[1], s[1], t[1])
    end
end
function Base.gcdx(a::SInteger, b::SInteger)
    _gcdxwhile((a, b), (oneunit(a), zero(b)), (zero(a), oneunit(b)))
end

# only alternating `A` and `B` so that they eventually get promoted
#Tuple{SInt64{_1} where _1,Union{SInt64{-1}, SInt64{1}},SInt64{0}}

"""
    gcd(::Union{Integer,SInteger}, Union{Integer,SInteger})

# Examples
```jldoctest
using StaticValues, Test

julia> gcd(SInt(6), SInt(9)) === SInt(3)
true

julia> gcd(SInt(6),SInt(-9)) === SInt(3)
true

julia> gcd(SInt(6), -9) === 3
true

julia> gcd(6,SInt(-9)) === 3
true

julia> @inferred(gcd(SInt(6), SInt(-9)))
3
```
"""
function Base.gcd(a::SInteger, b::SInteger)
    za = trailing_zeros(a)
    zb = trailing_zeros(b)
    k = min(za, zb)
    u, v = _gcdwhile(unsigned(abs(a >> za)), unsigned(abs(b >> zb)))
    r = u << k
    # T(r) would throw InexactError; we want OverflowError instead
    r > typemax(eltype(a)) && Base.__throw_gcd_overflow(a, b)
    r % typeof(a)
end

@inline _gcdwhile(u::U, v::U) where {U<:SInteger} = u, v
function _gcdwhile(u::SInteger, v::SInteger)
    if u > v
        _gcdwhile(v, u)
    end
    _gcdwhile(u, (v-u) >> trailing_zeros(v-u))
end

Base.gcd(a::SIntegerZeroType, b::SInteger) = abs(b)
Base.gcd(a::SIntegerZeroType, b::SIntegerZeroType) = abs(b)
Base.gcd(a::SInteger,         b::SIntegerZeroType) = abs(a)

"""
# StaticValues Examples
```jldoctest
julia> powermod(SInt(2), SInt(6), SInt(5)) === SInt(4)
true
```
"""
#=
Base.powermod(x::SInteger, p::SInteger, m::SInteger) =
    oftype(m, _powermod(one(m), p, m, prevpow(oftype(p, 2), p),oftype(m, mod(x,m))))

function _powermod(r::SInteger, p::SInteger, m::SInteger, t::SInteger, b::SInteger)
    if p >= t
        _powermod(mod(widemul(r,b), m), p - t, m, t, b)
    else
        _powermod2(r, p, m, t >>> SOne, b)
    end
end

Base.powermod(x::SInteger, p::SIntegerZeroType, m::SInteger) = mod(one(m), m)
Base.powermod(x::SInteger, p::SIntegerNegOneType, m::SInteger) = powermod(invmod(x, m), -p, m)
Base.powermod(x::SInteger, p::SInteger, m::SIntegerOneType) = zero(m)
Base.powermod(x::SInteger, p::SInteger, m::SIntegerNegOneType) = zero(m)

function _powermod2(r::SInteger, p::SInteger, m::SInteger, t::SInteger, b::SInteger)
    if t <= 0
        return r
    else
        return _powermod(mod(widemul(r, r), m), p, m, t, b)
    end
end

# there's not static bigint, so can't widen past 128
function _powermod2(r::SInt128, p::SInteger, m::SInteger, t::SInteger, b::SInteger)
    if t <= 0
        return r
    else
        return _powermod(mod(r*r, m), p, m, t, b)
    end
end

function _powermod(r::SInt128, p::SInteger, m::SInteger, t::SInteger, b::SInteger)
    if p >= t
        _powermod(mod(r * b, m), p - t, m, t, b)
    else
        _powermod2(r, p, m, t >>> SOne, b)
    end
end
=#
#_prevpow2(x::Unsigned) = one(x) << unsigned((sizeof(x)<<3)-leading_zeros(x)-SOne)
#_prevpow2(x::Integer) = reinterpret(typeof(x), x < 0 ? -_prevpow2(unsigned(-x)) : _prevpow2(unsigned(x)))


module LearnBase

# AGGREGATION MODES
include("aggmode.jl")

# OBSERVATION DIMENSIONS
include("obsdim.jl")

# LEARNING COSTS (e.g. loss & penalty)
include("costs.jl")

# OTHER CONCEPTS
include("other.jl")

end # module


using Dash
using PlotlyJS
using DashBootstrapComponents

#
# NOT USED! ATTEMPT TO USE ALL-PLOTLY DASHBOARD
#

#, DashHtmlComponents, DashCoreComponents

using Markdown
using DataFrames

include( "runner_libs.jl")
include( "dash_libs.jl")

const FORM_EXTRA =Dict(
	"border-bottom"=>"1px dashed #aaaaaa",
	"margin-bottom"=>"5px",
	"margin-top"=>"5px")

const PREAMBLE = """
"""

const INFO = """

* Created with [Julia](https://julialang.org/) | [Dash](https://dash-julia.plotly.com/) | [Plotly](https://plotly.com/julia/) | [Inequality an Poverty Measures](https://github.com/grahamstark/PovertyAndInequalityMeasures.jl);
* Part of the [Scottish Tax Benefit Model](https://github.com/grahamstark/ScottishTaxBenefitModel.jl);
* Open Source software released under the [MIT Licence](https://github.com/grahamstark/Visualisations.jl/blob/main/LICENSE). [Source Code](https://github.com/grahamstark/Visualisations.jl).
"""

"""
Create the block of sliders and radios on the LHS
"""
function get_input_block()
	return dbc_form([
		dbc_row([
			dbc_col(
				dbc_label("Basic Rate"; html_for="basic_rate"), width=3
			),
			dbc_col(
				dcc_slider(
					id = "basic_rate",
					min = 0,
					max = 100,
					marks = Dict([Symbol("$v") => Symbol("$v") for v in 0:10:100]),
					value = 10.0,
					step = 1
				)), # col
		]),
		dbc_row([
			dbc_col([
				dbc_button(
					id = "submit-button",
					class_name="primary",
					color = "primary",
					name = "Run",
					value = "Run",
					children = "submit"
					)
			]) # col
		]) # row
	]) # form
end

app = dash(external_stylesheets=[dbc_themes.UNITED])
# BOOTSTRAP|SIMPLEX|MINTY|COSMO|SANDSTONE|UNITED|SLATE|SOLAR|UNITED|
app.layout = dbc_container(fluid=true, className="p-5") do
	html_title( "You are The Finance Minister")
	html_h1("You are The Finance Minister"),
	dbc_row([
		dbc_col( dcc_markdown( PREAMBLE ), width=10)
	]), # row
	dbc_row([
    	dbc_col(get_input_block(), width=4),
		dbc_col([
			dcc_loading(
				id="model_running",
				type="default",
				children = [
					html_div(
						id="loading-output-1"
						#=
						children=(
							dcc_graph( id = "bc-1", figure=f1 )
						) # child graph
						=#
					) # div
				] # children
			) # loading
		]) # col
	]), # row

	dbc_row([
		dbc_col([
			dcc_graph( id = "bc-1" )])
	]),
	dbc_row([
		dbc_col( dcc_markdown( INFO ), width=10)
	]) # row
end # layout

function do_output( br )
	br /= 100.0
	sys = deepcopy( BASE_STATE.sys )
	println("sys.it.non_savings_rates[2] $(sys.it.non_savings_rates[2])")
	incr = br-sys.it.non_savings_rates[2]
	sys.it.non_savings_rates[1:3] .+= incr
	results = do_run( sys )
	gls1 = gain_lose_table(
		results.gain_lose )
	gls2 = gain_lose_table(
		results.gain_lose )
	gls3 = gain_lose_table(
		results.gain_lose )
	lorenz = draw_lorenz(
		BASE_STATE.summary.deciles[1][:,2],
		results.summary.deciles[1][:,2] )
	gbd = drawDeciles(
		results.summary.deciles[1][:,3],
		BASE_STATE.summary.deciles[1][:,3] )

	fig = make_subplots(
		rows=3,
		cols=3,
		column_widths=[0.3, 0.3, 0.4],
		row_heights=[0.33, 0.33, 0.33],
		specs=[
			Spec(kind="xy")  Spec(kind="table", rowspan=1, colspan=1)  Spec(kind="xy");
			Spec(kind="xy")  Spec(kind="table", rowspan=1, colspan=1)  Spec(kind="xy");
			Spec(kind="xy")  Spec(kind="xy")  Spec(kind="table", rowspan=1, colspan=1)]
	)
	for row in 1:3
		for col in 1:3
			if row > 1 && (row == col)
				add_trace!( fig, gls1, row=row, col=col)
			elseif row == 1 && col == 2
				add_trace!( fig, gls1, row=row, col=col)
			elseif rand(1:2) == 1
				add_trace!( fig, gbd, row=row, col=col )
			else
				for i in lorenz
					add_trace!( fig, i, row=row, col=col )
				end
			end
		end
	end
		# specs=[
		#		Spec(kind="table") Spec(kind="xy")
		#
		#		missing Spec(kind= "scene")
		#
		#	]

		#

	return fig

end

callback!(
    app,
    Output("model_running",  "children"),
	Output("bc-1", "figure"),
	Input("submit-button", "n_clicks"),
	State( "basic_rate", "value")) do n_clicks, basic_rate
	println( "n_clicks = $n_clicks")
	if ! isnothing( n_clicks )
		return [nothing,do_output( basic_rate )]
	end
	[nothing,do_output(20)]
end

run_server(app, "0.0.0.0", 8051; debug=true )


"""
The QNetwork type is a mutable structure that contains QNodes, QChannels, and
a dictionary of costs to weighted LightGraphs.
"""
mutable struct QNetwork <: QObject
    name::String
    nodes::Array{QNode}
    channels::Array{QChannel}
    # Dictionary between costs and corresponding LightGraphs weighted graphs.
    graph::Dict
    time::Float64

    # QNetwork() = new("QuNet", [], [], Dict())
    function QNetwork()
        network = new("QuNet", [], [], Dict(), 0.0)
        for cost_key in keys(zero_costvector())
            network.graph[cost_key] = SimpleWeightedDiGraph()
        end
        return network
    end
end

function QNetwork(graph::AbstractGraph; edge_costs=unit_costvector())
    network = QNetwork()

    vertexCount = 0
    for vertex in vertices(graph)
        vertexCount += 1
        add(network, BasicNode(string(vertexCount)))
    end

    edgeCount = 0
    for edge in edges(graph)
        edgeCount += 1
        add(network, BasicChannel(string(edgeCount), network.nodes[edge.src], network.nodes[edge.dst],
        edge_costs))
    end

    return network
end

function print(network::QNetwork)
    println("name: ", network.name)
    println("nodes: ", length(network.nodes))
    println("channels: ", length(network.channels))
end

# NOTE Outdated refresh_graph! below.
# We'll no longer use TemporalGraph structure to update graphs since it has
# A lot more bells and whistles now. Good practice to have something custom built.
# function refresh_graph!(network::QNetwork)
#    network.graph = TemporalGraph(network, 1).graph
# end

"""
    refresh_graph!(network::QNetwork)

Converts a QNetwork into several weighted LightGraphs (one
graph for each associated cost), then updates the QNetwork.graph attribute
with these new graphs.
"""
function refresh_graph!(network::QNetwork)

    refreshed_graphs = Dict{String, SimpleWeightedDiGraph}()

    for cost_key in keys(zero_costvector())
        refreshed_graphs[cost_key] = SimpleWeightedDiGraph()

        # Vertices
        add_vertices!(refreshed_graphs[cost_key], length(network.nodes))

        # Channels
        for channel in network.channels
            if channel.active == true
                src = findfirst(x -> x == channel.src, network.nodes)
                dest = findfirst(x -> x == channel.dest, network.nodes)
                weight = channel.costs[cost_key]
                add_edge!(refreshed_graphs[cost_key], src, dest, weight)
                add_edge!(refreshed_graphs[cost_key], dest, src, weight)
            end
        end
    end
    network.graph = refreshed_graphs
end

"""
    GridNetwork(dim::Int64, dimY::Int64)

Generates an X by Y grid network.
"""
function GridNetwork(dimX::Int64, dimY::Int64; edge_costs::Dict = unit_costvector())
    graph = LightGraphs.grid([dimX,dimY])
    net = QNetwork(graph; edge_costs=edge_costs)

    for x in 1:dimX
        for y in 1:dimY
            this = x + (y-1)*dimX
            net.nodes[this].location = Coords(x,y)
        end
    end

    refresh_graph!(net)
    return net
end

"""
```function update(network::QNetwork, new_time::Float64)```

The `update` function iterates through all objects in the network and updates
them according to a new global time.
"""
function update(network::QNetwork, new_time::Float64)
    old_time = network.time
    for node in network.nodes
        update(node, old_time, new_time)
    end

    for channel in network.channels
        update(channel, old_time, new_time)
    end
    network.time = new_time
end

"""
```function update(network::QNetwork)```

This instance of update iterates through all objects in the network and updates
them by the global time increment TIME_STEP defined in QuNet.jl
"""
function update(network::QNetwork)
    old_time = network.time
    new_time = old_time + TIME_STEP
    for node in network.nodes
        update(node, old_time, new_time)
    end

    for channel in network.channels
        update(channel, old_time, new_time)
    end
    network.time = new_time
end

"""
    getnode(network::QNetwork, id::Int64)

Fetch the node object corresponding to the given ID / Name
"""
function getnode(network::QNetwork, id::Int64)
    return network.nodes[id]
end

function getnode(network::QNetwork, name::String)
    for node in network.nodes
        if node.name == name
            return node
        end
    end
end

function getchannel(network::QNetwork, src::Union{Int64, String},
    dst::Union{Int64, String})
    src = getnode(network, src)
    dst = getnode(network, dst)
    for channel in network.channels
        if channel.src == src && channel.dest == dst
            return channel
        end
    end
end

"""
function add_qnode!(network::QNetwork; nodename::String="", nodetype::DataType=BasicNode)
    @assert nodetype in subtypes(QNode)
    new_node = nodetype(nodename)
    add(network, new_node)
end
"""

"""
function add_channel!(network::QNetwork, src::Union{Int64, String},
    dst::Union{Int64, String};
    name::string="", type=BasicChannel)
    src = getnode(src)
    dst = getnode(dst)
    @assert type in [BasicChannel, AirChannel]
    new_channel = type(name, src, dst)
    add(network, new_channel)
end
"""


module TestWithSequential

using Transducers: DistributedEx, SequentialEx, ThreadedEx
using Folds.Testing: test_with_sequential, eval_call_data

executors = [
    SequentialEx(),
    ThreadedEx(),
    ThreadedEx(basesize = 1),
    DistributedEx(),
    DistributedEx(basesize = 1),
]

test_with_sequential(executors)

raw_extra_testdata = """
reduce(Mean(), 1:10)
reduce(Mean(), (x for x in 1:10 if isodd(x)))
reduce(KHist(25), 1.0:10.0)
"""

preamble = quote
    using OnlineStats
end

extra_testdata = eval_call_data(raw_extra_testdata, preamble)

test_with_sequential(extra_testdata, executors)

end


"""
    enable_debug()

Activate debugging messages.
"""
function enable_debug()
    ENV["JULIA_DEBUG"] = Nystrom
end

error_green_formula(SL,DL,γ₀u,γ₁u,u,σ)                      = σ*u + SL*γ₁u  - DL*γ₀u
error_derivative_green_formula(ADL,H,γ₀u,γ₁u,un,σ)          = σ*un + ADL*γ₁u - H*γ₀u
error_interior_green_identity(SL,DL,γ₀u,γ₁u)                = error_green_formula(SL,DL,γ₀u,γ₁u,γ₀u,-1/2)
error_interior_derivative_green_identity(ADL,H,γ₀u,γ₁u)     = error_derivative_green_formula(ADL,H,γ₀u,γ₁u,γ₁u,-1/2)
error_exterior_green_identity(SL,DL,γ₀u,γ₁u)                = error_green_formula(SL,DL,γ₀u,γ₁u,γ₀u,1/2)
error_exterior_derivative_green_identity(ADL,H,γ₀u,γ₁u)     = error_derivative_green_formula(ADL,H,γ₀u,γ₁u,γ₁u,1/2)

"""
    @hprofile

A macro which
- resets the default `TimerOutputs.get_defaulttimer` to zero
- executes the code block
- prints the profiling details

This is useful as a coarse-grained profiling strategy to get a rough idea of
where time is spent. Note that this relies on `TimerOutputs` annotations
manually inserted in the code.
"""
macro hprofile(block)
    return quote
        TimerOutputs.enable_debug_timings(Nystrom)
        reset_timer!()
        $(esc(block))
        print_timer()
    end
end


module GraphSearch

export graph_search!, AbstractTreeSearchAlgorithm

include("tree_search_node.jl")
include("tree_search_algo.jl")

end


using Bio.Seq
using BioBenchmarks

chrom = first(open(datafile("chr1.fa"), FASTA)).seq
@benchmark translate(RNASequence(chrom))


module CMAEvolutionStrategy
using LinearAlgebra, Printf, Statistics, Dates, Random

export minimize, xbest, fbest, population_mean

include("optimizer.jl")
include("print.jl")
include("stop.jl")
include("log.jl")
include("constraints.jl")
include("noise.jl")

"""
    xbest(result)

Extract input `x` that resulted in the lowest function value ever.
"""
xbest(o::Optimizer{<:Any, <:BasicLogger}) = transform(o.p.constraints, o.logger.xbest[end])
"""
    fbest(result)

Extract lowest function value ever evaluated.
"""
fbest(o::Optimizer{<:Any, <:BasicLogger}) = o.logger.fbest[end]
"""
    population_mean(result)

Extract the current mean of the optimizer.
"""
population_mean(o::Optimizer) = transform(o.p.constraints, o.p.mean)

function run!(o, f)
    start!(o.stop)
    while true
        y = sample(o.p)
        fvals = evaluate(o.p, f, compute_input(o.p, y))
        perm = sortperm(fvals)
        log!(o, y, fvals, perm)
        noise_handling!(o.p, f, y, fvals, perm)
        update!(o.p, y, perm)
        terminate!(o) && break
        if o.logger.verbosity > 0 &&
           (o.stop.it < 4 ||
            o.stop.it % 100 == 0 ||
            time() - o.logger.times[end] > 2)
           print_state(o)
       end
    end
    if o.logger.verbosity > 0
        print_state(o)
    end
    o
end
"""
    minimize(f, x0, s0;
             lower = nothing,
             upper = nothing,
             constraints = _constraints(lower, upper),
             noise_handling = nothing,
             popsize = 4 + floor(Int, 3*log(length(x0))),
             callback = (o, y, fvals, perm) -> nothing,
             parallel_evaluation = false,
             multi_threading = false,
             verbosity = 1,
             seed = rand(UInt),
             logger = BasicLogger(x0, verbosity = verbosity, callback = callback),
             maxtime = nothing,
             maxiter = nothing,
             maxfevals = nothing,
             stagnation = 100 + 100 * length(x0)^1.5/popsize,
             ftarget = nothing,
             xtol = nothing,
             ftol = 1e-11)

Minimize function `f` starting around `x0` with initial covariance `s0 * I`
under box constraints `lower .<= x0 .<= upper`, where `x0`, `lower` and `upper`
are vectors of the same length or `nothing`.

The result is an `Optimizer` object from which e.g. [`xbest`](@ref), [`fbest`](@ref)
or [`population_mean`](@ref) can be extracted.

If `parallel_evaluation = true`, the objective function `f` receives matrices
of `n` rows (`n = length(x0)`) and `popsize` columns and should return a vector of
length `popsize`. To use multi-threaded parallel evaluation of the objective function,
set `multi_threading = true` and start julia with multiple threads
(c.f. julia manual for the multi-threading setup).
"""
function minimize(f, x0, s0;
                  kwargs...)
    o = Optimizer(x0, s0; kwargs...)
    run!(o, f)
end
end # module


# http://juliastats.github.io/StatsBase.jl/stable/weights.html
mutable struct Packed_AliasingScalarSampler
  samples::Vector{Float64}
  weights::Vector{Float64}
end

## avert your eyes, C++ below
# class MvNormal : public Distribution {
#   std::vector<double> mean_;
#   std::vector<std::vector<double>> cov_;
#
# public:
#   MvNormal(const std::vector<double> &mean,
#            const std::vector<std::vector<double>> &cov)
#       : mean_(mean), cov_(cov) {}
#   json ToJson(void) const {
#     json j;
#     j["mean"] = mean_;
#     j["cov"] = cov_;
#     return (j);
#   }
# };


# tests for the usual functions: optBL, extractQuartet, moves
# for network with n=6 (1_astral.out, add hybridization; true network)
# messy code intended to find specific bugs, does not follow an order
# Claudia April 2015

include("../src/types.jl")
include("../src/functions.jl")

quartets = readListQuartets("allQuartets.txt");

truenet = readTopologyUpdate("truenetwork.txt");
printEdges(truenet)
printNodes(truenet)

tree = readTopologyUpdate("1_astral.out");
printEdges(tree)
writeTopologyLevel1(tree)
net = deepcopy(tree);
srand(1234)
addHybridizationUpdate!(net);
printEdges(net)
writeTopologyLevel1(net)

extractQuartet!(truenet, quartets) # no errors
extractQuartet!(net,quartets) # errors: after delete leaf!: fixed!

printNodes(net)
nettest = deepcopy(net);
extractQuartet!(nettest,quartets[1]) # error here

qnet = QuartetNetwork(net);
deleteLeaf!(qnet,qnet.node[3])
printEdges(qnet)

## qnet2 = deepcopy(qnet); # for testing
## printNodes(qnet2)

## deleteLeaf!(qnet2,qnet2.node[4]) # no error!!!

deleteLeaf!(qnet,qnet.node[4])
updateHasEdge!(qnet,net)
parameters!(qnet,net) # error here: fixed

# now, error in minor edge 12 set as identifiable AND node.k=-1 even after updateInCycle
tree = readTopologyUpdate("1_astral.out");
printEdges(tree)
writeTopologyLevel1(tree)
net = deepcopy(tree);
srand(1234)
hybrid = addHybridization!(net);
printNodes(net)
printEdges(net)
flag, nocycle, edgesInCycle, nodesInCycle = updateInCycle!(net,hybrid);
net.node[11].k #4, so correct!
updateMajorHybrid!(net,hybrid);
flag2, edgesGammaz = updateGammaz!(net,hybrid,allow);

success,hybrid,flag,nocycle,flag2,flag3 = addHybridizationUpdate!(net);

# ----------------
include("../src/types.jl")
include("../src/functions.jl")

quartets = readListQuartets("allQuartets.txt");
df = readtable("HGT_truenet_expCF.csv")
d = readTableCF(df); #expCF

tree = readTopologyUpdate("1_astral.out");
printEdges(tree)
net = deepcopy(tree);
srand(1234)
addHybridizationUpdate!(net);
printEdges(net)

currT = deepcopy(net);
optBL!(currT,d)
newT = deepcopy(currT);
count = 0
move = whichMove(currT)
move = :CHdir
move = :MVorigin
move = :MVtarget
move = :nni

movescount = zeros(Int,18)
movesfail = zeros(Int,6)
flag = proposedTop!(move,newT,true,count,1, movescount, movesfail)
printEdges(newT)
printNodes(newT)
optBL!(newT,d)

newT0 = deepcopy(newT);

qnet = QuartetNetwork(newT);
q=quartets[3];
q.taxon
newT.names
extractQuartet!(newT,quartets) # error

qnet = QuartetNetwork(newT);
printNodes(qnet)
deleteLeaf!(qnet,qnet.node[4])
printNodes(qnet)
printEdges(qnet)
qnet0 = deepcopy(qnet);
qnet = deepcopy(qnet0);

identifyQuartet!(qnet)
qnet.which != 1 ? error("qnet which not correctly assigned") : nothing
qnet.hybrid[1].k != 2 ? error("qnet.hybrid[1].k not correctly assigned") : nothing
qnet.hybrid[1].typeHyb != 1 ? error("qnet.hybrid[1].typeHyb not correctly assigned") : nothing
qnet.hybrid[1].prev.number != -5 ? error("qnet.hybrid[1].prev not correctly assigned") : nothing

eliminateHybridization!(qnet) #error here
size(qnet.hybrid,1) != 0 || qnet.numHybrids != 0 ? error("qnet should not have hybrid nodes anymore") : nothing
qnet.t1 != 0.2-log(1-0.1*(1-exp(-1.1))) ? error("internal edge length not correctly updated") : nothing

#---
node = qnet.hybrid[1];

updateSplit!(qnet)
qnet.split != [1,1,2,2] ? error("qnet.split not correctly assigned") : nothing

updateFormula!(qnet)
qnet.formula != [2,1,2] ? error("qnet.formula not correctly assigned") :nothing

calculateExpCF!(qnet)
qnet.expCF != [1/3*exp(-qnet.t1),1-2/3*exp(-qnet.t1),1/3*exp(-qnet.t1)] ? error("qnet.expCF wrongly calculated") : nothing


function read_atoms(fn::IO)
	sane_readline() = strip(strip_split(readline(fn), ":")[1], '\'')
	nspecies = parse(Int, sane_readline())
	species_counter = 0

	reading_atoms = false
	atname = :nothing
	natoms = 0
	atcounter = 0
	positions = Point3{Float64}[]
	bfcmts    = Vec3{Float64}[]
	atoms     = Tuple{Symbol, Point3{Float64}, Vec3{Float64}}[]
	while species_counter < nspecies
		l = sane_readline()
		if !reading_atoms
			atname = Symbol(split(l, ".")[1])
			natoms = parse(Int, sane_readline())
			reading_atoms = true
		elseif atcounter < natoms
			sline = split(l)
			push!(positions, Point3{Float64}(parse.(Float64, sline[1:3])))
			push!(bfcmts, Vec3{Float64}(parse.(Float64, sline[4:end])))
			atcounter += 1
		elseif isempty(l)
			for (p, mag) in zip(positions, bfcmts)
				push!(atoms, (atname, p, mag))
			end
			atname = :nothing
			reading_atoms   = false
			natoms          = 0
			atcounter       = 0
			positions       = Point3{Float64}[]
			bfcmts          = Vec3{Float64}[]
			species_counter += 1
		end
	end
	return atoms
end

function elk_parse_DFTU(atoms::Vector{<:Atom}, blocknames_flaglines::Dict{Symbol, Any})
	lines = pop!(blocknames_flaglines, Symbol("dft+u"))
	t_l       = split(lines[1])
	species_start = 3
	if length(t_l) == 1
		dftu_type = parse(Int, t_l[1])
		inpdftu   = parse(Int, lines[2])
	else
		dftu_type, inpdftu = parse.(Int,  t_l)
		species_start = 2
	end
	species_names = name.(unique(atoms))
	for line in lines[species_start:end]
		sline = split(line)
		species_id, l = parse.(Int, sline[1:2])
		U, J0         = parse.(Float64, sline[3:4])
		for at in filter(x -> name(x) == species_names[species_id], atoms)
			at.dftu = DFTU{Float64}(U=U, J0=J0, l=l)
		end
	end
	return dftu_type, inpdftu
end

"""
    elk_read_input(filename; execs=[Exec("elk")], run=true, structure_name="noname")

Reads an Elk input file. The `ELK_EXEC` inside execs gets used to find which flags are allowed in this input file, and convert the read values to the correct Types.
Returns a `DFInput{Elk}` and the `Structure` that is found in the input.
"""
function elk_read_input(fn::String; execs=[Exec("elk")], run=true, structure_name="noname")
	blocknames_flaglines = Dict{Symbol, Any}()
	atoms = Atom{Float64}[]
	inputs = DFInput[]
	dir, file = splitdir(fn)

	#TODO it's a bit messy that the atoms block is not handled like this, not sure if we want to clean this up
	#blocks that require specific parsing
	special_blocks = (:avec, :tasks, Symbol("dft+u"))

	open(fn, "r") do f
		sane_readline() = strip_split(readline(f), ':')[1]
		while !eof(f)
			line = sane_readline()
			isempty(line) || line[1] == '!' && continue
			if !isempty(line)
				blockname = Symbol(line)
				info = elk_block_info(blockname)
				if blockname == :atoms
					atoms = read_atoms(f)
				elseif blockname == :wannierExtra
					@warn "Please supply the .win file instead of wannierExtra block for extracting the Wannier DFInput."

					wflags, wdata, ab, cb, proj_block = wan_read_input(f)
					continue
					# push!(inputs, DFInput{Wannier90}("wannier", dir, wflags, wdata, execs, run))
				elseif info != nothing
					blocklines = String[]
					line = sane_readline()
					while !isempty(line)
						push!(blocklines, strip(replace(strip(split(line, ':')[1]), "'" => ""), '.'))
						line = sane_readline()
					end
					if !in(blockname, special_blocks)
						blocknames_flaglines[blockname] =
						    parse_block_flags(blockname, collect(Iterators.flatten(split.(filter(x->!isempty(x), blocklines)))))
					else
						blocknames_flaglines[blockname] = filter(x->!isempty(x), blocklines)
					end
				end
			end
		end
	end
	# for i in
	#cell
	scale = haskey(blocknames_flaglines, :scale) ? last(pop!(blocknames_flaglines[:scale])) : 1.0
	cell  = uconvert.(Ang, Mat3(reshape(scale .* parse.(Float64, collect(Iterators.flatten(split.(pop!(blocknames_flaglines, :avec))))), 3, 3).*1a₀))'

    newats = [Atom{Float64, eltype(cell)}(name=x[1], element=element(x[1]), position_cryst=x[2], position_cart=cell*x[2], magnetization=x[3]) for x in atoms]
	#structure #TODO make positions be in lattices coordinates
	structure = Structure(structure_name, cell, newats)

	#different tasks
	tasks_     = pop!(blocknames_flaglines, :tasks)
	should_run = map(x -> !occursin("!", x), tasks_)
	tasks      = map(x -> strip(strip(x, '!')), tasks_)

	#The wan tasks can prbably be skipped completely and added depending on the wannier input
	wan_tasks = String[]
	for (x, run) in zip(tasks, should_run)
		if x ∈ ["601", "602", "603", "604", "605"]
			push!(wan_tasks, x)
		else
			push!(inputs, input_from_task(x, blocknames_flaglines, dir, execs, run))
		end
	end
	if haskey(blocknames_flaglines, :wannier) && !isempty(wan_tasks)
		flags = pop!(blocknames_flaglines, :wannier)
		# flags[:elk2wan_tasks] = wan_tasks
		push!(inputs, DFInput{Elk}(name="elk2wannier", dir=dir, flags=flags, execs=execs, run=true))
	end
	for f in (:ngrid, :vkloff, :plot1d, :plot2d, :plot3d)
		haskey(blocknames_flaglines, f) && pop!(blocknames_flaglines, f)
	end

	#elk2wannier

	flags = SymAnyDict()

	if haskey(blocknames_flaglines, Symbol("dft+u"))
		flags[:dftu], flags[:inpdftu] = elk_parse_DFTU(structure.atoms, blocknames_flaglines)
	end

	for (k, v) in blocknames_flaglines
		merge!(flags, v)
	end

	for i in inputs
		if isempty(i.flags)
			i.flags = deepcopy(flags)
		end
	end
	return inputs, structure
end

function find_data(flags, blocknames_flaglines)
	data    = InputData[]
	for f in flags
		if haskey(blocknames_flaglines, f)
			push!(data, InputData(f, :nothing, blocknames_flaglines[f]))
		end
	end
	return data
end

function input_from_task(task, blocknames_flaglines, dir, execs, run)
	if task ∈ ["0", "1", "2", "3", "5"]
		data = find_data((:ngridk, :vkloff), blocknames_flaglines)
	elseif task ∈ ["20", "21"]
		data = find_data((:plot1d, :plot2d, :plot3d), blocknames_flaglines)
	end
	return DFInput{Elk}(task, dir, SymAnyDict(), data, execs, run)
end

function parse(::Type{UnitRange{Int}}, l::AbstractString)
	for delim in [":", ",", "-"]
		if occursin(delim, l)
			return UnitRange(parse.(Int, split(l, delim))...)
		end
	end
end

function parse_block_flags(blockname::Symbol, lines::Vector{<:AbstractString})
	flags = SymAnyDict()
	i = 1
	totlen = length(lines)
	for flaginfo in elk_block_info(blockname).flags
		flagname = flaginfo.name
		i > length(lines) && break
		typ = eltype(flaginfo)
		if typ == String || typ == Vector{String}
			flags[flagname] = lines[i]
			i += 1
		elseif typ == Symbol
			flags[flagname] = Symbol(lines[i])
			i += 1
		else
			eltyp = eltype(typ)
			# The assumption is that the infinite repeating values are at the end of a block
			if flaginfo == last(elk_block_info(blockname).flags)
				typelen = length(typ)
				parsed = parse.(eltyp, lines[i:end])
				flags[flagname] = typ[]
				@assert length(parsed) % typelen == 0
				for j = 1:typelen:length(parsed)
					push!(flags[flagname], typ(parsed[j:j+typelen-1]...))
				end
			elseif typ <: UnitRange
				flags[flagname] = parse(typ, lines[i])
				i += 1
			else
				typelen = length(typ)
				parsed = parse.(eltyp, lines[i:i + typelen - 1])
				flags[flagname] = length(parsed) == 1 ? parsed[1] : typ(parsed)
				i += typelen
			end
		end
	end
	return flags
end

function elk_write_structure(f, structure)
	write(f, "atoms\n\t")
	species = unique(map(x->x.name, atoms(structure)))
	nspecies = length(species)
	write(f, "$nspecies\n")
	for s in species
		write(f, "\t'$s.in'\n")
		ats = filter(x->x.name == s, atoms(structure))
		write(f, "\t$(length(ats))\n")
		for a in ats
			write(f, "\t")
			for i in position_cryst(a)
				write(f, "$(round(i, digits=8)) ")
			end
			for i in magnetization(a)
				write(f, "$i ")
			end
			write(f, "\n")
		end
		write(f, "\n")
	end
	write(f, "\n")
	write(f, "avec\n\t")

	for i in eachindex(structure.cell')
		c = ustrip(uconvert(a₀, structure.cell[i]))
		if i%3 == 0
			write(f, "$c\n\t")
		else
			write(f, "$c ")
		end
	end
	write(f, "\n")
end

function construct_flag_blocks(inputs::Vector{DFInput{Elk}})
	all_flags = SymAnyDict()
	for i in inputs
		merge!(all_flags, i.flags)
		for d in data(i)
			merge!(all_flags, d.data)
		end
	end
	block_flags = SymAnyDict()

	for b in ELK_CONTROLBLOCKS
		bvals = Any[]
		for f in b.flags
			if haskey(all_flags, f.name)
				push!(bvals, pop!(all_flags, f.name))
			end
		end
		if !isempty(bvals)
			block_flags[b.name] = bvals
		end
	end
	return block_flags
end

function elk_write_flag_val(f, val)
	write(f, "\t")
	if isa(val, UnitRange)
		write(f, "$(first(val))-$(last(val))")
	elseif isa(val, AbstractVector)
		for i in val
			write(f, "$i ")
		end
	else
		if isa(val, AbstractString)
			write(f, "'$val'")
		else
			write(f, "$val")
		end
	end
	write(f, "\n")
end

function elk_write_DFTU(f::IO, structure::AbstractStructure, dftu_vals::Vector)
	write(f, "dft+u\n")
	for v in dftu_vals
		elk_write_flag_val(f, v)
	end
	species = unique(atoms(structure))
	for (i, s) in enumerate(species)
		write(f, "\t$i $(string(Elk, dftu(s)))\n")
	end
	write(f, "\n")
end

function save(inputs::Vector{DFInput{Elk}}, structure::Structure)
	tasks = map(x -> x.run ? "$(x.name)" : "!$(x.name)", filter(x->x.name != "elk2wannier", inputs))
	elk2wan_input = getfirst(x->x.name == "elk2wannier", inputs)
	if elk2wan_input != nothing && haskey(elk2wan_input.flags, :elk2wan_tasks)
		append!(tasks, pop!(elk2wan_input.flags, :elk2wan_tasks))
	end
	flags = construct_flag_blocks(inputs)
	open(joinpath(inputs[1].dir, "elk.in"), "w") do f

		write(f, "tasks\n")
		for t in tasks
			write(f, "\t$t\n")
		end
		write(f, "\n")

		elk_write_structure(f, structure)

		haskey(flags, Symbol("dft+u")) && elk_write_DFTU(f, structure, pop!(flags, Symbol("dft+u")))

		#remaining generic blocks
		for (k, v) in flags
			write(f, "$k\n")
			for v_ in values(v)
				if isa(v_, Vector{<:AbstractVector})
					for v__ in v_
						elk_write_flag_val(f, v__)
					end
				else
					elk_write_flag_val(f, v_)
				end
			end
			write(f, "\n")
		end
	end
end


module MLToolkit

export MLToolkit

greet() = print("Welcome to Kai's machine learning toolkit!")

using ArgCheck
import Distributions, DistributionsAD, Reexport, Requires, Tracker, Parameters, LinearAlgebra, Random

### Constants

const Float = Float32
const Double = Float64

export Float, Double

### Polluting functions

Base.sum(x::AbstractArray, drop::Val{:drop}; dims=:) = dropdims(sum(x; dims=dims); dims=dims)
Base.getproperty(ts::AbstractArray{<:NamedTuple}, k::Symbol) = getproperty.(ts, k)
Base.getindex(ts::AbstractArray{<:NamedTuple}, k::Symbol) = getindex.(ts, k)

Base.getindex(arr::AbstractArray{<:Any,1}, ::Colon, i) = arr[i]
Base.getindex(arr::AbstractArray{<:Any,3}, ::Colon, i) = arr[:,:,i]
Base.getindex(arr::AbstractArray{<:Any,4}, ::Colon, i) = arr[:,:,:,i]

function Random.shuffle(nt::NamedTuple{K, V}) where {K, V<:Tuple{Vararg{<:AbstractArray}}}
    ns = [last(size(v)) for v in values(nt)]
    n = first(ns)
    @argcheck all(n .== ns)
    idcs = Random.shuffle(1:n)
    return nt[:,idcs]
end

function Base.getindex(nt::NamedTuple{K, V}, ::Colon, i) where {K, V<:Tuple{Vararg{<:AbstractArray}}}
    v = tuple([v[:, i] for v in values(nt)]...)
    return NamedTuple{K, typeof(v)}(v)
end

Base.:(+)(nt1::T, nt2::T) where {T<:NamedTuple} = T(tuple((values(nt1) .+ values(nt2))...))

function Base.union(nt1::NamedTuple{S1, T1}, nt2::NamedTuple{S2, T2}) where {S1, T1, S2, T2}
    return NamedTuple{(S1..., S2...), Tuple{T1.types..., T2.types...}}((nt1..., nt2...))
end

# TODO: clean-up `special`
include("special.jl")

# TODO: move `transformations` somewhere
include("transformations.jl")
export break_stick_ibp, break_logstick_ibp

include("PlotsX/PlotsX.jl")
Reexport.@reexport using .PlotsX
include("Datasets/Datasets.jl")
Reexport.@reexport using .Datasets
include("Scripting/Scripting.jl")
Reexport.@reexport using .Scripting

# TODO: merge `distributions` and `DistributionsX`
include("distributions/distributions.jl")
include("DistributionsX/DistributionsX.jl")
Reexport.@reexport using .DistributionsX
include("MonteCarlo/MonteCarlo.jl")
Reexport.@reexport using .MonteCarlo
# TODO: merge `neural.deprecated` (which contains codes for RAVE) and `Neural`; variational layers are also important
# include("neural.deprecated/neural.jl")

function __init__()
    Requires.@require CuArrays="3a865a2d-5b23-5a0f-bc46-62713ec82fae" begin
        include("gpu.jl")
        export seed!
    end

    Requires.@require Flux="587475ba-b771-5e3f-ad9e-33799f191a9c" begin
        include("Neural/Neural.jl")
        Reexport.@reexport using .Neural
    end
end

end # module


@testset "AStar" begin
    using Agents.Pathfinding

    moore = Pathfinding.moore_neighborhood(2)
    vonneumann = Pathfinding.vonneumann_neighborhood(2)
    gspace = GridSpace((5, 5))
    cspace = ContinuousSpace((5., 5.))
    atol = 0.0001
    @testset "constructors" begin
        # GridSpace
        @test_throws AssertionError AStar(gspace; admissibility = -1.0)
        # Default/DirectDistance metric
        cost = AStar(gspace).cost_metric
        @test typeof(cost) <: DirectDistance{2}
        @test cost.direction_costs == [10, 14]
        @test_throws AssertionError AStar(gspace; cost_metric = DirectDistance{2}([1]))
        @test_throws AssertionError AStar(
            gspace;
            diagonal_movement = false,
            cost_metric = DirectDistance{2}([])
        )

        # MaxDistance metric
        cost = AStar(gspace; cost_metric = MaxDistance{2}()).cost_metric
        @test typeof(cost) <: MaxDistance{2}

        # PenaltyMap metric
        @test_throws TypeError AStar(gspace; cost_metric = PenaltyMap)
        @test_throws AssertionError AStar(gspace; cost_metric = PenaltyMap([1 1]))

        cost = AStar(gspace; cost_metric = PenaltyMap(fill(1, 5, 5))).cost_metric
        @test typeof(cost) <: PenaltyMap{2}
        @test typeof(cost.base_metric) <: DirectDistance{2}
        @test cost.pmap == fill(1, 5, 5)

        cost = AStar(gspace; cost_metric = PenaltyMap(fill(1, 5, 5), MaxDistance{2}())).cost_metric
        @test typeof(cost) <: PenaltyMap{2}
        @test typeof(cost.base_metric) <: MaxDistance{2}
        @test cost.pmap == fill(1, 5, 5)
        pmap = zeros(Int, 1, 1, 1)
        @test_throws TypeError AStar(gspace; cost_metric = PenaltyMap(pmap))

        # ContinuousSpace
        @test_throws AssertionError AStar(cspace; walkmap = trues((10, 10)), cost_metric = PenaltyMap(fill(1, 5, 5)))
        @test_throws AssertionError AStar(cspace; walkmap = trues((10, 10)), cost_metric = PenaltyMap(fill(1, 5, 5)))

        astar = AStar(cspace; walkmap = trues(10, 10))
        @test astar isa AStar{2,true,true,Float64}
        astar = AStar(cspace; cost_metric = PenaltyMap(fill(1, 5, 5)))
        @test astar isa AStar{2,true,true,Float64}
    end

    @testset "API functions" begin
        # GridSpace
        pathfinder = AStar(gspace)
        model = ABM(Agent3, gspace; properties = (pf = pathfinder,))
        a = add_agent!((5, 2), model, 654.5)
        @test is_stationary(a, model.pf)

        plan_route!(a, (1, 3), model.pf)
        @test !is_stationary(a, model.pf)
        @test length(model.pf.agent_paths) == 1

        move_along_route!(a, model, model.pf)
        @test a.pos == (1, 3)

        delete!(model.pf.agent_paths, 1)
        @test length(model.pf.agent_paths) == 0
        @test plan_best_route!(a, [(5, 1), (1, 1), (3, 3)], model.pf) == (5, 1)
        @test length(model.pf.agent_paths) == 1

        kill_agent!(a, model, model.pf)
        @test length(model.pf.agent_paths) == 0

        @test isnothing(penaltymap(model.pf))
        pmap = fill(1, 5, 5)
        pathfinder = AStar(gspace; cost_metric = PenaltyMap(pmap))
        model = ABM(Agent3, gspace; properties = (pf = pathfinder, ))
        @test penaltymap(model.pf) == pmap

        pathfinder.walkmap[:, 3] .= false
        npos = collect(nearby_walkable((5, 4), model, model.pf))
        ans = [(4, 4), (5, 5), (1, 4), (4, 5), (1, 5)]
        @test length(npos) == length(ans)
        @test all(x in npos for x in ans)
        @test all(pathfinder.walkmap[random_walkable(model, model.pf)...] for _ in 1:10)

        sp = GridSpace((5, 5); periodic = false)
        pf = AStar(sp)
        model = ABM(Agent3, sp; properties = (pf = pf,))
        model.pf.walkmap[3, :] .= 0
        a = add_agent!((1, 3), model, 0.)
        @test plan_best_route!(a, [(1, 3), (4, 1)], model.pf) == (1, 3)
        @test isnothing(plan_best_route!(a, [(5, 3), (4, 1)], model.pf))

        # ContinuousSpace
        pathfinder = AStar(cspace; walkmap = trues(10, 10))
        model = ABM(Agent6, cspace; properties = (pf = pathfinder,))
        a = add_agent!((0., 0.), model, (0., 0.), 0.)
        @test is_stationary(a, model.pf)

        plan_route!(a, (4., 4.), model.pf)
        @test !is_stationary(a, model.pf)
        @test length(model.pf.agent_paths) == 1

        move_along_route!(a, model, model.pf, 0.35355)
        @test all(isapprox.(a.pos, (4.75, 4.75); atol))
        # test waypoint skipping
        move_agent!(a, (0.25, 0.25), model)
        plan_route!(a, (0.75, 1.25), model.pf)
        move_along_route!(a, model, model.pf, 0.807106)
        @test all(isapprox.(a.pos, (0.75, 0.849999); atol)) || all(isapprox.(a.pos, (0.467156, 0.967156); atol))
        # make sure it doesn't overshoot the end
        move_along_route!(a, model, model.pf, 20.)
        @test all(isapprox.(a.pos, (0.75, 1.25); atol))

        delete!(model.pf.agent_paths, 1)
        @test length(model.pf.agent_paths) == 0

        model.pf.walkmap[:, 3] .= 0
        @test all(get_spatial_property(random_walkable(model, model.pf), model.pf.walkmap, model) for _ in 1:10)
        rpos = [random_walkable((2.5, 0.75), model, model.pf, 2.0) for _ in 1:50]
        @test all(get_spatial_property(x, model.pf.walkmap, model) && edistance(x, (2.5, 0.75), model) <= 2.0 + atol for x in rpos)

        pcspace = ContinuousSpace((5., 5.); periodic = false)
        pathfinder = AStar(pcspace; walkmap = trues(10, 10))
        model = ABM(Agent6, pcspace; properties = (pf = pathfinder,))
        a = add_agent!((0., 0.), model, (0., 0.), 0.)
        @test all(plan_best_route!(a, [(2.5, 2.5), (4.99,0.), (0., 4.99)], model.pf) .≈ (2.5, 2.5))
        @test length(model.pf.agent_paths) == 1
        move_along_route!(a, model, model.pf, 1.0)
        @test all(isapprox.(a.pos, (0.7071, 0.7071); atol))

        model.pf.walkmap[:, 3] .= 0
        move_agent!(a, (2.5, 2.5), model)
        @test all(plan_best_route!(a, [(3., 0.3), (2.5, 2.5)], model.pf) .≈ (2.5, 2.5))
        @test isnothing(plan_best_route!(a, [(3., 0.3), (1., 0.1)], model.pf))

        kill_agent!(a, model, model.pf)
        @test length(model.pf.agent_paths) == 0

        @test isnothing(penaltymap(model.pf))
        pmap = fill(1, 10, 10)
        pathfinder = AStar(cspace; cost_metric = PenaltyMap(pmap))
        @test penaltymap(pathfinder) == pmap

        @test all(get_spatial_property(random_walkable(model, model.pf), model.pf.walkmap, model) for _ in 1:10)
        rpos = [random_walkable((2.5, 0.75), model, model.pf, 2.0) for _ in 1:50]
        @test all(get_spatial_property(x, model.pf.walkmap, model) && edistance(x, (2.5, 0.75), model) <= 2.0 + atol for x in rpos)
    end

    @testset "metrics" begin
        pfinder_2d_np_m = AStar{2,false,true,Int64,DirectDistance{2}}(
            Dict(),
            (10, 10),
            copy(moore),
            0.0,
            trues(10, 10),
            DirectDistance{2}(),
        )
        pfinder_2d_np_nm = AStar{2,false,false,Int64,DirectDistance{2}}(
            Dict(),
            (10, 10),
            copy(vonneumann),
            0.0,
            trues(10, 10),
            DirectDistance{2}(),
        )
        pfinder_2d_p_m = AStar{2,true,true,Int64,DirectDistance{2}}(
            Dict(),
            (10, 10),
            copy(moore),
            0.0,
            trues(10, 10),
            DirectDistance{2}(),
        )
        pfinder_2d_p_nm = AStar{2,true,false,Int64,DirectDistance{2}}(
            Dict(),
            (10, 10),
            copy(vonneumann),
            0.0,
            trues(10, 10),
            DirectDistance{2}(),
        )
        pmap = fill(0, 10, 10)
        pmap[:, 6] .= 100
        pmap[1, 6] = 0

        @test delta_cost(pfinder_2d_np_m, DirectDistance{2}(), (1, 1), (4, 6)) == 62
        @test delta_cost(pfinder_2d_p_m, DirectDistance{2}(), (1, 1), (8, 6)) == 62
        @test delta_cost(pfinder_2d_np_nm, DirectDistance{2}(), (1, 1), (4, 6)) == 80
        @test delta_cost(pfinder_2d_p_nm, DirectDistance{2}(), (1, 1), (8, 6)) == 80

        @test delta_cost(pfinder_2d_np_m, MaxDistance{2}(), (1, 1), (4, 6)) == 5
        @test delta_cost(pfinder_2d_p_m, MaxDistance{2}(), (1, 1), (8, 6)) == 5
        @test delta_cost(pfinder_2d_np_nm, MaxDistance{2}(), (1, 1), (4, 6)) == 5
        @test delta_cost(pfinder_2d_p_nm, MaxDistance{2}(), (1, 1), (8, 6)) == 5

        @test delta_cost(pfinder_2d_np_m, PenaltyMap(pmap), (1, 1), (4, 6)) == 162
        @test delta_cost(pfinder_2d_p_m, PenaltyMap(pmap), (1, 1), (8, 6)) == 162
        @test delta_cost(pfinder_2d_np_nm, PenaltyMap(pmap), (1, 1), (4, 6)) == 180
        @test delta_cost(pfinder_2d_p_nm, PenaltyMap(pmap), (1, 1), (8, 6)) == 180
    end

    @testset "pathing" begin
        wlk = trues(7, 6)
        wlk[2:7, 1] .= false
        wlk[7, 3:6] .= false
        wlk[[2:4; 6], 4] .= false
        wlk[2:5, 5] .= false
        wlk[2, 2] = false
        wlk[4, 3] = false
        wlk[5, 3] = false

        pfinder_2d_np_m = AStar{2,false,true,Float64,DirectDistance{2}}(
            Dict(),
            (10., 10.),
            copy(moore),
            0.0,
            wlk,
            DirectDistance{2}(),
        )
        pfinder_2d_np_nm = AStar{2,false,false,Float64,DirectDistance{2}}(
            Dict(),
            (10., 10.),
            copy(vonneumann),
            0.0,
            wlk,
            DirectDistance{2}(),
        )
        pfinder_2d_p_m = AStar{2,true,true,Float64,DirectDistance{2}}(
            Dict(),
            (10., 10.),
            copy(moore),
            0.0,
            wlk,
            DirectDistance{2}(),
        )
        pfinder_2d_p_nm = AStar{2,true,false,Float64,DirectDistance{2}}(
            Dict(),
            (10., 10.),
            copy(vonneumann),
            0.0,
            wlk,
            DirectDistance{2}(),
        )

        p = collect(Pathfinding.find_path(pfinder_2d_np_m, (1, 1), (6, 6)))
        @test p == [(1, 2), (1, 3), (1, 4), (1, 5), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6)]
        p = collect(Pathfinding.find_path(pfinder_2d_np_nm, (1, 1), (6, 6)))
        @test p ==
            [(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6)]
        p = collect(Pathfinding.find_path(pfinder_2d_p_m, (1, 1), (6, 6)))
        @test p == [(2, 6), (3, 6), (4, 6), (5, 6), (6, 6)]
        p = collect(Pathfinding.find_path(pfinder_2d_p_nm, (1, 1), (6, 6)))
        @test p == [(1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6)]

        # Continuous
        model = ABM(Agent6, ContinuousSpace((10., 10.)))
        p = collect(Pathfinding.find_continuous_path(pfinder_2d_np_m, (0.25, 0.25), (7.8, 9.5)))
        testp = [(0.71429, 2.5), (0.71429, 4.16667), (0.71429, 5.83333), (0.71429, 7.5), (2.14286, 9.16667), (3.57143, 9.16667), (5.0, 9.16667), (6.42857, 9.16667), (7.85714, 9.16667), (7.8, 9.5)]
        @test length(p) == length(testp)
        @test all(all(isapprox.(p[i], testp[i]; atol)) for i in 1:length(p))

        p = collect(Pathfinding.find_continuous_path(pfinder_2d_p_m, (0.25, 0.25), (7.8, 9.5)))
        testp = [(2.14286, 9.16667), (3.57143, 9.16667), (5.0, 9.16667), (6.42857, 9.16667), (7.85714, 9.16667), (7.8, 9.5)]
        @test length(p) == length(testp)
        @test all(all(isapprox.(p[i], testp[i]; atol)) for i in 1:length(p))
    end
end


using Test
using SparseSampling
using POMDPs
using POMDPModels
using Random

Random.seed!(1)

m = BabyPOMDP()

@testset "POMDP" begin
    solver = SparseSamplingSolver(SSOptions(4, 4))
    planner = solve(solver, m)
    @test action(planner, [true]) == true # feed the baby when hungry
    @test action(planner, [false]) == false # don't feed the baby when it is not hungry
    @time action(planner, [true]) == true # feed the baby when hungry
end

@testset "POW" begin
    solver = SparseSamplingSolver(POWSSOptions(4, 4))
    planner = solve(solver, m)
    @test action(planner, [true]) == true # feed the baby when hungry
    @test action(planner, [false]) == false # don't feed the baby when it is not hungry
    @time action(planner, [false]) == false # don't feed the baby when it is not hungry
end


### Packages used
using Statistics
using NaNStatistics
using Optim
using DelimitedFiles

### Structs used
struct hyperParameters
    sampleRate              ::Int64          # [Hz] Sample rate.
    unloadingFitRange       ::Int64          # [-] Vector samples lengths (measured from start of unloading) to be included in unload fit.
    unloadingFitFunction    ::String         # [string] Function to use when fitting.
    compensateCreep         ::Bool           # [Bool] Compensate creep using Feng's method, y/n
    constrainHead           ::Int            # Experimental, not implemented
    constrainTail           ::Int            # Experimental, not implemented
    machineCompliance       ::Float64        # Machine compliance
end

struct control
    plotMode                ::Bool           # Activates plotting of intermediate results
    verboseMode             ::Bool           # Verbose output
end

struct metaInfoExperimentalSeries
    designatedName          ::String         # Designated name
    relativeHumidity        ::Float64        # Relative humidity
    indenterType            ::String
    indentationNormal       ::String
    springConstant          ::Float64
    areaFile                ::String
    targetDir               ::String
    thermalHoldTime         ::Int64          # Should be changed to something of unit "second".
end

"""
ReadIBW(filename)

Reads a .IBW binary file, extracting the position and force signal of the AFM indenter.

filename is the string to a file which either needs to be absolute or on the path.
"""
function readIBW(filename)

    A = open( filename, "r")
    firstByte = read(A, Int8)
    close(A)

    if firstByte == 0
        machineFormat = 'b'
    else
        machineFormat = 'l'
    end

    A = open( filename, "r")
    version         = read(A, Int16)
    checksum        = ltoh(read(A,UInt16)) # Does not work
    wfmSize         = ltoh(read(A, Int32))
    formulaSize     = ltoh(read(A, Int32))
    noteSize        = ltoh(read(A, Int32)) # Does not work
    dataEUnitsSize  = ltoh(read(A, Int32))
    dimEUnitsSize   = Array{Int32, 1}(undef, 4); read!(ltoh(A),dimEUnitsSize)
    dimLabelsSize   = Array{Int32, 1}(undef, 4); read!(ltoh(A),dimLabelsSize)
    sIndicesSize    = read(A, Int32)
    optionSize1     = read(A, Int32)
    optionSize2     = read(A, Int32)
    ignore          = read(A, Int32)
    CreationDate    = read(A, UInt32)
    modData         = read(A, UInt32)
    npnts           = read(A, Int32)                  # WORKS!

    type = read(A, Int16)
    if type == 2
        datatype = "single"
    elseif type == 4
        datatype = "double"
    else
        println("ERROR")
    end

    read(A, Int16)
    for aLoop = 1:6
        read(A, Char)
    end
    read(A, Int16)

    for aLoop = 1:32
        read(A, Char)
    end
    for aLoop = 1:6
        read(A, Int32)
    end

    sfA = Array{Float64, 1}(undef, 4)
    read!(A, sfA)
    sfB = Array{Float64, 1}(undef, 4)
    read!(A, sfB)

    dUnits = Array{Char, 1}(undef,4)
    for aLoop = 1:4
        dUnits[aLoop] = read(A, Char)
    end
    dUnits = join(dUnits)

    xUnits  = Array{Char, 1}(undef, 16)
    for aLoop = 1:16
        xUnits[aLoop] = read(A, Char)
    end
    xUnits = join(xUnits)

    fsValid = read(A, Int16)
    whpad3 = read(A, Int16)
    read(A, Float64)
    read(A, Float64)
    for aLoop = 1:26
        read(A, Int32)
    end
    for aLoop = 1:3
        read(A, Int16)
    end
    read(A, Char)
    read(A, Char)
    read(A, Int32)
    read(A, Int16)
    read(A, Int16)
    read(A, Int32)
    modDate = read(A, Int32)
    creationDate = read(A, Int32)
    wdata = Array{Float32, 1}(undef, npnts); read!(A,wdata)

    close(A)

    x0 = Float64(sfB[1]*fsValid)
    dx = Float64(sfA[1]*fsValid)
    return wdata#, npnts# , dUnits, x0, dx, xUnits

end

#data, dUnits, x0, dx, xUnits = ReadIBW(filename)
"""
IBWtoTXT(filename)

Takes a filename, calls ReadIBW and formats the returned data into x and y signals where
x is the position of the sensor and y is the deflection of the sensor.
"""
function IBWtoTXT(filename::String)
    data = readIBW(filename)
    lengthOfData = length(data) ÷ 3

    #t = data[1:lengthOfData]
    y = data[lengthOfData+1:2*lengthOfData]
    x = data[2*lengthOfData+1:3*lengthOfData]
    return [x y]
end

function dataPreProcessing(filename::String)
    xy0 = IBWtoTXT(filename)
    # Import signal
    xy0 .*= 1e9
    # Convert to nano-meters

    return xy0
end

function offsetAndDriftCompensation(xy::Matrix{Float32})
    endRangeBaseFit = 25;
    breakForContact = true;
    aLoop = 0;
    basefit = 0

    while breakForContact
        aLoop += 1

        sIdx = min(10+endRangeBaseFit*(aLoop-1), size(xy,1)-endRangeBaseFit);
        eIdx = min(10+endRangeBaseFit*(aLoop), size(xy,1)-1);

        dPdt = 100*(xy[eIdx,2] - xy[sIdx,2]) / endRangeBaseFit;

        if dPdt > 3.3  && aLoop > 3
            basefit = endRangeBaseFit*(aLoop-1);
            breakForContact = false;
            #println("if")
        elseif endRangeBaseFit*aLoop > 1000
            basefit = endRangeBaseFit*7;
            breakForContact = false;
            #println("elseif")
        end
    end

    zeroLineM = ones(length(xy[10:basefit,1])) \ xy[10:basefit,2]
    xy[:,2] .-= zeroLineM

    # Locate the point where the ramp begins.
    noise = max(0.1, std(xy[10:basefit,2]) );
    rampStartIdx = max(1, findfirst(x -> x > 4.0*noise, xy[:,2]) - 1);
    xZero = xy[rampStartIdx,1];
    xy[:,1] .-= xZero;                                       # Shift the curve by the value at the start of the ramp.
    xy[:,2] .-= xy[rampStartIdx,2];

    zeroLineD = xy[10:basefit,1] \ xy[10:basefit,2];

    zeroLine = [zeroLineD zeroLineM];

    return xy , basefit , zeroLine , rampStartIdx, xZero
end

function subdirImport(placeToLook::String,stringToMatch::String)
    return filter(x-> contains(x, stringToMatch) , readdir(placeToLook))
end

function findStartOfHold(xy::Matrix{Float32}, directionOfSearch::String)
    # 1. Determine range of deflection values.
    # 2. Bin the values (heuristic bin size at the moment)
    # 3. Determine the most common deflection value at the highest load levels under the assumption that this bin will contain
    #    the hold sequence.
    # 4. Determine the mean value of all values larger than this bin value.
    # 5. Find the first time the vector exceeds this value.
    # 6. This is taken as the first value in the hold sequence.

    sensorRange = maximum(xy[:,2]) - minimum(xy[:,2])
    vecLengthTemp = Int64(round(4.0*sensorRange))
    edgesOfHist = range(minimum(xy[:,2]), maximum(xy[:,2]), length = vecLengthTemp)
    histTemp = histcounts(xy[:,2] , edgesOfHist)

    tailVecStart = Int64(round(0.9*vecLengthTemp))

    peakIdx = argmax(histTemp[tailVecStart:end])
    peakIdx += tailVecStart-1

    idxTemp = map(x -> x > edgesOfHist[peakIdx], xy[:,2])

    meanOfPlateau = mean(xy[idxTemp,2])

    if cmp(directionOfSearch,"first") == 0
        returnIdx = findfirst(x -> x ≥ meanOfPlateau, xy[:,2])
    elseif cmp(directionOfSearch,"last") == 0
        returnIdx = findlast(x -> x ≥ meanOfPlateau, xy[:,2])
    end

    return returnIdx

end

function determineThermalCreep(xy::Matrix{Float32},sampleRate::Int64,thermalHoldTime::Int64,ctrl::control)
    sensorRange = maximum(xy[:,2]) - minimum(xy[:,2])
    vecLengthTemp = Int64(round(0.1*sensorRange));
    edgesOfHist = range(minimum(xy[:,2]), maximum(xy[:,2]), length = vecLengthTemp)
    peakIdx = argmax(histcounts(xy[:,2] , edgesOfHist))

    idxTemp = (xy[:,2] .< edgesOfHist[peakIdx+1]) .& (xy[:,2] .> edgesOfHist[max(1,peakIdx-1)])
    meanOfPlateau = mean(xy[idxTemp,2])
    stdOfPlateau = std(xy[idxTemp,2])

    noiseMultiplier = 5.0; # 15 # OBS HARD CODED AND SHOULD BE PULLED OUT!
    thermalHoldStartIdx = findlast(x -> x > meanOfPlateau+noiseMultiplier*stdOfPlateau, xy[:,2])
    thermalHoldEndIdx = findlast(x -> x > meanOfPlateau-noiseMultiplier*stdOfPlateau, xy[:,2])

    thermalHoldStartIdx += sampleRate
    thermalHoldEndIdx -= sampleRate

    # Ensure that the thermal hold sequence contains at least 25 seconds (out of the 30 secounds
    # specified).
    if thermalHoldStartIdx > thermalHoldEndIdx
        ctrl.verboseMode && println("Missed thermal hold. Increasing search range.")

        while thermalHoldTime+thermalHoldStartIdx > thermalHoldEndIdx
            noiseMultiplier += 1.0
            thermalHoldStartIdx = findlast(x -> x > meanOfPlateau+noiseMultiplier*stdOfPlateau, xy[:,2])
            thermalHoldEndIdx = findlast(x -> x > meanOfPlateau-noiseMultiplier*stdOfPlateau, xy[:,2])

            try
            thermalHoldStartIdx += sampleRate
            thermalHoldEndIdx -= sampleRate
            catch
                return 0.0
                println("Failure in the termal hold calculation")
            end
        end
        ctrl.verboseMode && println("Thermal hold found using multiplier $noiseMultiplier")
    end

    # Fit a function of displacement (due to thermal fluctuation)
    # h_thermal(time) = A1 + A2*time^A3
    thermalHoldDisplacement = xy[thermalHoldStartIdx:thermalHoldEndIdx,1];
    thermalHoldTime = collect(1:length(thermalHoldDisplacement))./sampleRate;

    deltaDisp = (xy[thermalHoldEndIdx,1] - xy[thermalHoldStartIdx,1])

    thermalCreepFun(x) = xy[thermalHoldStartIdx,1] .+ x[1].*(thermalHoldTime)#.^x[2]
    thermalHoldMinFun(x) = sqrt(sum((   (thermalCreepFun(x) .- thermalHoldDisplacement)./thermalHoldDisplacement ).^2))
    result = optimize(thermalHoldMinFun, [deltaDisp], BFGS())
    thermal_p = result.minimizer

    # Estimate the thermal drift rate by taking the median of the differentiated h_thermal
    # dhtdt = d(h_thermal(time))/d(time)
    # The functional form accounts for any viscous effects lingering from the unloading at the start
    # of the thermal hold, while the median provides a roboust average of the thermal drift rate.
    #dhtdt = median(thermal_p[1] .* thermal_p[2].*thermalHoldTime.^(thermal_p[2] - 1));
    dhtdt = thermal_p[1];
end

function determineCreepDuringHold(xy_hold,sampleRate::Int64)
    # Fit the creep during the hold time at maximum load to a linear spring-dashpot model
    #
    # Equation (17c) - not labeled in [1]
    #
    # h(t) = h_i + \beta * t.^(1/3)
    #
    #   h(t)    - Displacement as a function of time during hold sequence.
    #   h_i     - Fitting constant
    #   \beta   - Fitting constant
    #   t       - Time

    holdTimeVals = collect(1:length(xy_hold[:,1])) ./ sampleRate
    # Generate time signal

    deltaDisp = (xy_hold[end,1] - xy_hold[1,1])

    # Define fitting functions
    hOftFun(x) = xy_hold[1,1] .+ x[1].*holdTimeVals.^x[2]
    minFcn(x) = sqrt( sum( ( (hOftFun(x) - xy_hold[:,1])./ xy_hold[:,1] ).^2 ) )
    holdDrift = optimize(minFcn, [deltaDisp 0.33], BFGS())
    crp_p = holdDrift.minimizer

    h_dot_tot = crp_p[1] * crp_p[2]*holdTimeVals[end]^(crp_p[2] - 1.0)

    return max(h_dot_tot, 0.0)
end

function modulusfitter(indentationSet::metaInfoExperimentalSeries,hyperParameters,ctrl::control,resultFile::String)
    xy = dataPreProcessing(indentationSet.targetDir*resultFile)
    # Load raw displacements

    #ctrl.plotMode && display(plot([xy[1:100:end,1]],[xy[1:100:end,2]]))

    xy , basefit , zeroLine , rampStartIdx, xZero  = offsetAndDriftCompensation(xy)
    # Find initial contact
    #ctrl.plotMode && display(plot!(xy[1:100:end,1],xy[1:100:end,2]))

    xy[:,1] .-= xy[:,2]
    xy[:,2] .*= indentationSet.springConstant
    xy[:,1] .-= hyperParameters.machineCompliance.*xy[:,2];
    # Convert displacement-deflection matrix to indentation-force matrix

    #################################################################
    # Determine the start of the hold time at circa max force.
    #
    # 1. Determine range of deflection values.
    # 2. Bin the values (heuristic bin size at the moment)
    # 3. Determine the most common deflection value at the highest load levels under the assumption that this bin will contain
    #    the hold sequence.
    # 4. Determine the mean value of all values larger than this bin value.
    # 5. Find the first time the vector exceeds this value.
    # 6. This is taken as the first value in the hold sequence.
    holdStartIdx = findStartOfHold(xy,"first")
    ctrl.plotMode && display(plot(xy[:,1], xy[:,2], xlims = (0.0, maximum(xy[:,1])), xlab = "Indentation [nm]", ylab = "Force [uN]", legend = false))
    # ctrl.plotMode && display(plot!([xy[holdStartIdx,1]], [xy[holdStartIdx,2]],
    #                          seriestype = :scatter, lab = "Start of hold", legend = :topleft))

    # Split into loading and unloading.
    #xy_load = xy[1:holdStartIdx,:];
    xy_unld1 = xy[holdStartIdx:end,:];

    #Determine the end of the hold time.
    unloadStartIdx = findStartOfHold(xy_unld1,"last")
    #ctrl.plotMode && display(plot!([xy_unld1[unloadStartIdx,1]], [xy_unld1[unloadStartIdx,2]],seriestype = :scatter))

    # Split into two new pieces
    xy_hold = xy_unld1[1:unloadStartIdx-1,:];
    xy_unld = xy_unld1[unloadStartIdx:end,:];

    # Accept only indentations that had positive creep. "Negative" creep (indenter moves outwards
    # during hold sequence) can occur if the thermal drift is substantial, but this typically
    # indicates that the system was not in equilibrium (since the thermal drift dominates the creep)
    # and furthermore it messes up the mathematical framework if you accept such indentations (see
    # Cheng & Cheng articles.)
    condition1 = xy[holdStartIdx,1] < xy_unld1[unloadStartIdx,1]

    # Accept only monotonously increasing load-displacement curves. A curve may show weird behaviour
    # and our solution is to simply drop the curve in that case.
    condition2 = minimum(xy[rampStartIdx:holdStartIdx,1]) > (0.0-eps())
    condition3 = maximum(xy_unld[:,1]) < (xy_unld[1,1]+0.5)

    ctrl.plotMode && display(title!(string(condition3)))
    sleep(1.0)

    if condition1 && condition2 && condition3
        xy_unld5 = xy_unld[1:Int64(round(2000*0.95)),:];  # OBS 2000 is hard coded!
        # Make sure that the thermal hold sequence is not included in the unloading curve.

        dhtdt = determineThermalCreep(xy,hyperParameters.sampleRate,indentationSet.thermalHoldTime,ctrl)
        #dhtdt == 0.0 && return 0.0

        # Fitting of the unloading curve.
        stiffness_fit = Array{Float64}(undef,1)
        dispVals = xy_unld5[1:hyperParameters.unloadingFitRange,1]
        forceVals = xy_unld5[1:hyperParameters.unloadingFitRange,2]
        Fmax = xy_unld5[1,2]               # Maximum force during unloading

        if cmp(hyperParameters.unloadingFitFunction,"Oliver-Pharr") == 0
            Dmax = xy_unld5[1,1]
            # Maximum indentation depth during unloading

            function unloadFitFun(fitCoefs)
                return fitCoefs[1].*(dispVals .- fitCoefs[2]).^fitCoefs[3] .- forceVals
            end
            function unloadFitMinFun(fitCoefs)
                sqrt( sum( (unloadFitFun(fitCoefs) ./ forceVals).^2 ) )
            end

            lx = [0.0, 0.0 , 0.0]; ux = [Inf, minimum(dispVals)-1e-2 , Inf];
            dfc = TwiceDifferentiableConstraints(lx, ux)
            resultFit = optimize(unloadFitMinFun, dfc, [1.0, 1.0, 1.0], IPNewton())
            uld_p = resultFit.minimizer
            stiffness_fit = uld_p[1]*uld_p[3]*(Dmax - uld_p[2]).^(uld_p[3] - 1)

        elseif cmp(hyperParameters.unloadingFitFunction, "Feng") == 0

            unloadFitFun2(fitCoefs) = fitCoefs[1] .+ fitCoefs[2].*forceVals.^0.5 + fitCoefs[3].*forceVals.^fitCoefs[4] .- dispVals
            unloadFitMinFun2(fitCoefs) = sqrt(sum( (unloadFitFun2(fitCoefs) ./ dispVals).^2) )
            resultFit = optimize(unloadFitMinFun2, [1.0 1.0 1.0 1.0], BFGS())
            uld_p = resultFit.minimizer
            stiffness_fit = inv(( 0.5*uld_p[2].*Fmax.^-0.5 + uld_p[4]*uld_p[3].*Fmax.^(uld_p[4] - 1.0) ))

        end

        h_dot_tot = determineCreepDuringHold(xy_hold,hyperParameters.sampleRate)
        dPdt = [1/hyperParameters.sampleRate .* collect(0:(length(xy_unld5[:,1])-1)) ones(length(xy_unld5[:,1]))] \ xy_unld5[:,2]

        if hyperParameters.compensateCreep
            stiffness = inv(1/stiffness_fit + h_dot_tot/(abs(dPdt[1])));
        else
            stiffness = stiffness_fit;
        end

        # Equation (2) in [1]
        maxIndentation = median(xy_unld5[1,1]) - dhtdt*(length(xy[rampStartIdx:holdStartIdx,1])+length(xy_hold[:,1]))/hyperParameters.sampleRate;  #%OBS OBS OBS

        if cmp(indentationSet.indenterType,"pyramid") == 0
            x0 = maxIndentation - 0.72*Fmax/stiffness;
        elseif cmp(indentationSet.indenterType,"hemisphere") == 0
            x0 = maxIndentation - 0.75*Fmax/stiffness;
        end
        x0 < 0.0 && return 0.0

        area_xy = readdlm(indentationSet.areaFile, ' ', Float64, '\n')
        # % Determine the area by loading the calibration data and fitting a polynom to the data.

        if (x0 > 100.0)
            area_fit_end = length(area_xy[:,1])
        elseif (x0 < 100.0)
            area_fit_end = findfirst( x -> x > 100,area_xy[:,1])
        end

        tempVec = area_xy[1:area_fit_end,1]
        p_area = [tempVec.^2 tempVec tempVec.^0.5 tempVec.^0.25 tempVec.^0.125] \ area_xy[1:area_fit_end,2]

        unloadArea = [x0^2 x0 x0^0.5 x0^0.25 x0^0.125] * p_area
        unloadArea = unloadArea[1]
        unloadArea < 0.0 && return 0.0

        # % Equation (1) in [1]
        Er = sqrt(pi)/(2.0)/sqrt(unloadArea) / (1.0/stiffness )
        if cmp(indentationSet.indenterType,"pyramid") == 0
            Er = Er/1.05;
        end
        return Er
    else
        return 0.0
    end

    println(Er)
    #return Er
end

function calculateMachineCompliance(indentationSet::metaInfoExperimentalSeries,hyperParameters,ctrl::control)
    resultNames = subdirImport(indentationSet.targetDir,".ibw");     # Find the .ibw files in targetDir
    resultNames = resultNames[6:end]
    ap = []
    bp = []
    for file in resultNames
        println(file)
        tempS , tempA = extractSingleComplianceExperiment(indentationSet,hyperParameters,ctrl,file)
        push!(ap,tempS)
        push!(bp,tempA)
        #println([tempS tempA])
    end

    squaredInverseArea = 1.0 ./sqrt.(bp)
    #return squaredInverseArea
    #return ones(size(ap))
    return [squaredInverseArea[:] ap]

    effectiveCompliance = [squaredInverseArea[:] ones(size(ap))] \ ap;
    return effectiveCompliance

    return effectiveCompliance[2]
end

function extractSingleComplianceExperiment(indentationSet::metaInfoExperimentalSeries,hyperParameters,ctrl::control,resultFile::String)

    xy = dataPreProcessing(indentationSet.targetDir*resultFile)
    # Load raw displacements

    #ctrl.plotMode && display(plot([xy[1:100:end,1]],[xy[1:100:end,2]]))

    xy , basefit , zeroLine , rampStartIdx, xZero  = offsetAndDriftCompensation(xy)
    # Find initial contact
    #ctrl.plotMode && display(plot!(xy[1:100:end,1],xy[1:100:end,2]))

    xy[:,1] .-= xy[:,2]
    xy[:,2] .*= indentationSet.springConstant
    # Convert displacement-deflection matrix to indentation-force matrix

    holdStartIdx = findStartOfHold(xy,"first")

    # Split into loading and unloading.
    xy_unld1 = xy[holdStartIdx:end,:];

    #Determine the end of the hold time.
    unloadStartIdx = findStartOfHold(xy_unld1,"last")

    # Split into two new pieces
    xy_hold = xy_unld1[1:unloadStartIdx-1,:];
    xy_unld = xy_unld1[unloadStartIdx:end,:];

    xy_unld5 = xy_unld[1:min(Int64(round(2000*0.95)),size(xy_unld,1)),:];  # OBS 2000 is hard coded!

    # Fitting of the unloading curve.
    stiffness_fit = Array{Float64}(undef,1)
    dispVals = xy_unld5[1:min(hyperParameters.unloadingFitRange,size(xy_unld5,1)),1]
    forceVals = xy_unld5[1:min(hyperParameters.unloadingFitRange,size(xy_unld5,1)),2]
    Fmax = xy_unld5[1,2]               # Maximum force during unloading

    if cmp(hyperParameters.unloadingFitFunction,"Oliver-Pharr") == 0
        Dmax = xy_unld5[1,1]
        # Maximum indentation depth during unloading

        function unloadFitFun(fitCoefs)
            return fitCoefs[1].*(dispVals .- fitCoefs[2]).^fitCoefs[3] .- forceVals
        end
        function unloadFitMinFun(fitCoefs)
            sqrt( sum( (unloadFitFun(fitCoefs) ./ forceVals).^2 ) )
        end

        lx = [-Inf, -Inf , 0.0]; ux = [Inf, minimum(dispVals)-1e-2 , Inf];
        dfc = TwiceDifferentiableConstraints(lx, ux)
        resultFit = optimize(unloadFitMinFun, dfc, [1.0, 1.0, 1.0], IPNewton())
        uld_p = resultFit.minimizer
        println(uld_p)
        stiffness_fit = uld_p[1]*uld_p[3]*(Dmax - uld_p[2]).^(uld_p[3] - 1)

    elseif cmp(hyperParameters.unloadingFitFunction, "Feng") == 0

        unloadFitFun2(fitCoefs) = fitCoefs[1] .+ fitCoefs[2].*forceVals.^0.5 + fitCoefs[3].*forceVals.^fitCoefs[4] .- dispVals
        unloadFitMinFun2(fitCoefs) = sqrt(sum( (unloadFitFun2(fitCoefs) ./ dispVals).^2) )
        resultFit = optimize(unloadFitMinFun2, [1.0 1.0 1.0 1.0], BFGS())
        uld_p = resultFit.minimizer
        println(uld_p)
        stiffness_fit = inv(( 0.5*uld_p[2].*Fmax.^-0.5 + uld_p[4]*uld_p[3].*Fmax.^(uld_p[4] - 1.0) ))

    end
    stiffness = stiffness_fit
    dhtdt = 0;

    maxIndentation = median(xy_unld5[1,1]) - dhtdt*(length(xy[rampStartIdx:holdStartIdx,1])+length(xy_hold[:,1]))/hyperParameters.sampleRate;  #%OBS OBS OBS

    if cmp(indentationSet.indenterType,"pyramid") == 0
        x0 = maxIndentation - 0.72*Fmax/stiffness;
    elseif cmp(indentationSet.indenterType,"hemisphere") == 0
        x0 = maxIndentation - 0.75*Fmax/stiffness;
    end

    area_xy = readdlm(indentationSet.areaFile, ' ', Float64, '\n')
    # % Determine the area by loading the calibration data and fitting a polynom to the data.

    if (x0 > 100.0)
        area_fit_end = length(area_xy[:,1])
    elseif (x0 < 100.0)
        area_fit_end = findfirst( x -> x > 100,area_xy[:,1])
    end

    tempVec = area_xy[1:area_fit_end,1]
    p_area = [tempVec.^2 tempVec tempVec.^0.5 tempVec.^0.25 tempVec.^0.125] \ area_xy[1:area_fit_end,2]

    unloadArea = [x0^2 x0 x0^0.5 x0^0.25 x0^0.125] * p_area
    unloadArea = unloadArea[1]

    return 1/stiffness_fit , unloadArea
    # Assign outputs
end


mutable struct OutlinedTriangle <: Ranger.AbstractNode
    base::Nodes.NodeData
    transform::Nodes.TransformProperties{Float64}

    color::Rendering.Palette

    polygon::Geometry.Polygon

    aabb::Geometry.AABB{Float64}

    detection::Nodes.Detection
    inside::Bool

    function OutlinedTriangle(world::Ranger.World, name::String, parent::Ranger.AbstractNode)
        o = new()

        o.base = Nodes.NodeData(name, parent, world)
        o.transform = Nodes.TransformProperties{Float64}()
        o.color = Rendering.White()

        o.polygon = Geometry.Polygon{Float64}()

        o.aabb = Geometry.AABB{Float64}()
        o.detection = Nodes.Detection(Rendering.Lime(), Rendering.Red())
        o.inside = false

        Geometry.add_vertex!(o.polygon, -0.5, 0.5)
        Geometry.add_vertex!(o.polygon, 0.5, 0.5)
        Geometry.add_vertex!(o.polygon, 0.0, -0.5)

        Geometry.build!(o.polygon)

        Nodes.set_dirty!(o, true)

        o
    end
end

# --------------------------------------------------------
# Timing: either called by NodeManager or by a parent node
# --------------------------------------------------------
function Nodes.update(node::OutlinedTriangle, dt::Float64)
    Nodes.update!(node.detection, node)
end

function Nodes.draw(node::OutlinedTriangle, context::Rendering.RenderContext)
    if Nodes.is_dirty(node)
        Rendering.transform!(context, node.polygon)
        Nodes.set_dirty!(node, false)
    end

    Rendering.set_draw_color(context, node.color)
    Rendering.render_outlined_polygon(context, node.polygon, Rendering.CLOSED);

    aabb_color = Nodes.highlight_color(node.detection, node.inside)
    Rendering.set_draw_color(context, aabb_color)

    Geometry.set!(node.aabb, Nodes.get_bucket(node))
    Rendering.render_aabb_rectangle(context, node.aabb)
end

# --------------------------------------------------------
# Events
# --------------------------------------------------------
function Nodes.io_event(node::OutlinedTriangle, event::Events.MouseMotionEvent)
    # println("io_event ", event, ", node: ", node)
    Nodes.set_device_point!(node.detection, Float64(event.x), Float64(event.y))
    node.inside = Nodes.check!(node.detection, node, node.base.world)
end

function set!(node::OutlinedTriangle, v1::Point{Float64}, v2::Point{Float64}, v3::Point{Float64})
    Geometry.set!(node.polygon.mesh.vertices[1], v1)
    Geometry.set!(node.polygon.mesh.vertices[2], v2)
    Geometry.set!(node.polygon.mesh.vertices[3], v3)

    Nodes.set_dirty!(node, true)
end