################################################################################
##############################    Tuple Stuff    ###############################
################################################################################
for op in [:typemin,:typemax]
    @eval Base.$op(::Type{Tuple{A,B,C,D,E,F}}) where {A,B,C,D,E,F}    = map(i->$op(i),(A,B,C,D,E,F))
    @eval Base.$op(::Type{Tuple{A,B,C,D,E}}) where {A,B,C,D,E}        = map(i->$op(i),(A,B,C,D,E))
    @eval Base.$op(::Type{Tuple{A,B,C,D}}) where {A,B,C,D}            = map(i->$op(i),(A,B,C,D))
    @eval Base.$op(::Type{Tuple{A,B,C}}) where {A,B,C}                = map(i->$op(i),(A,B,C))
    @eval Base.$op(::Type{Tuple{A,B}}) where {A,B}                    = map(i->$op(i),(A,B))
    @eval Base.$op(::Type{Tuple{A}}) where {A}                        = map(i->$op(i),(A,))
end
Base.Tuple{A,B,C,D,E,F}() where {A,B,C,D,E,F}    = map(i->i(0),(A,B,C,D,E,F))
Base.Tuple{A,B,C,D,E}() where {A,B,C,D,E}        = map(i->i(0),(A,B,C,D,E))
Base.Tuple{A,B,C,D}() where {A,B,C,D}            = map(i->i(0),(A,B,C,D))
Base.Tuple{A,B,C}() where {A,B,C}                = map(i->i(0),(A,B,C))
Base.Tuple{A,B}() where {A,B}                    = map(i->i(0),(A,B))
Base.Tuple{A}() where {A}                        = map(i->i(0),(A))

Base.typemin(::Type{NTuple{N,R}}) where {N,R} = NTuple{N,R}(map(i->typemin(R),1:N))
Base.typemax(::Type{NTuple{N,R}}) where {N,R} = NTuple{N,R}(map(i->typemax(R),1:N))
Base.NTuple{N,R}() where {N,R} = NTuple{N,R}(map(i->R(0), 1:N))
Base.NTuple{N,R}(val::R) where {N,R} = NTuple{N,R}(map(i->val, 1:N))
Base.typemin(c::Tuple) = typemin(typeof(c))
Base.typemax(c::Tuple) = typemax(typeof(c))

for op = (:(>), :(<), :(>=), :(<=))
    eval(quote
        Base.$op(a::T,b::R) where {N,T<:Tuple,R<:Real} = $op(a[1],b)
        Base.$op(b::R,a::T) where {N,T<:Tuple,R<:Real} = $op(b,a[1])
    end)
end

################################################################################
############################### Cost Model Stuff ###############################
################################################################################
export
    get_initial_cost,
    get_infeasible_cost,
    compute_path_cost,
    # get_transition_cost,
    accumulate_cost,
    add_heuristic_cost,
    aggregate_costs,

    AggregationFunction,
    MaxCost,
    SumCost,

    FullCostModel,
    get_aggregation_function,
    get_cost_model,
    cost_type,

    MetaCostModel,
    MetaCost,
    CompositeCostModel,
    construct_composite_cost_model,

    LowLevelCostModel,

    TravelTime,
    TravelDistance,
    FinalTime,
    NullCost,
    MakeSpan,
    SumOfTravelDistance,
    SumOfTravelTime

"""
    get_initial_cost(model)

Part of cost model interface. Defaults to zero.
"""
get_initial_cost(model)     = cost_type(model)(0)
# get_initial_cost(model::AbstractCostModel)     = cost_type(model)(0)

"""
    get_infeasible_cost(model)

Part of cost model interface. Defaults to zero.
"""
get_infeasible_cost(model)  = typemax(cost_type(model))
# get_infeasible_cost(model::AbstractCostModel)  = typemax(cost_type(model))

"""
    compute_path_cost(model,env,path,i)

Compute the cost of a path from scratch.
"""
compute_path_cost(model,env,path,i) = cost_type(model)(0)

# """
#     get_transition_cost(model,env,s,a,sp)
#     get_transition_cost(env,s,a,sp)
#     get_transition_cost(env,s,a)
#
# Part of cost model interface. Defines the cost of transitioning from state `s`
# to state `sp` via action `a` in environment `env` under cost model `model`.
# """
# function get_transition_cost end

"""
    accumulate_cost(model,current_cost,transition_cost)

Defines the way that a `transition_cost` updates the `current_cost` of a `Path`.
"""
function accumulate_cost end

"""
    add_heuristic_cost(cost_model,heuristic_model,cost,h_cost)

Defines the output heuristic cost that results from combining the current path
cost `cost` with a heuristic "cost-to-go" `h_cost`. Gener
"""
function add_heuristic_cost end
add_heuristic_cost(::C, cost, h_cost) where {C<:AbstractCostModel} = cost + h_cost
add_heuristic_cost(m::C, ::E, cost, h_cost) where {C<:AbstractCostModel,E<:AbstractLowLevelEnv} = add_heuristic_cost(m,cost,h_cost)
add_heuristic_cost(m::C, ::H, cost, h_cost) where {C<:AbstractCostModel,H<:AbstractCostModel}   = add_heuristic_cost(m,cost,h_cost)
add_heuristic_cost(m::C, h::H, ::E, cost, h_cost) where {C<:AbstractCostModel,H<:AbstractCostModel,E<:AbstractLowLevelEnv} = add_heuristic_cost(m,h,cost,h_cost)
add_heuristic_cost(env::E, cost, h_cost) where {E<:AbstractLowLevelEnv} = add_heuristic_cost(get_cost_model(env),get_heuristic_model(env),env,cost,h_cost)

"""
    compute_heuristic_cost(env,cost,sp)

Defaults to `add_heuristic_cost(env,cost,get_heuristic_cost(env,sp))`
Can be overridden so that state info can inform the heuristic cost directly.
"""
compute_heuristic_cost(m::C,h::H,env::E,cost,sp) where {C<:AbstractCostModel,H<:AbstractCostModel,E<:AbstractLowLevelEnv} = add_heuristic_cost(m,h,env,cost,get_heuristic_cost(h,env,sp))
compute_heuristic_cost(m::C,env::E,cost,sp) where {C<:AbstractCostModel,E<:AbstractLowLevelEnv} = add_heuristic_cost(m,env,cost,get_heuristic_cost(env,sp))
compute_heuristic_cost(env::E,args...) where {E<:AbstractLowLevelEnv} = compute_heuristic_cost(get_cost_model(env),get_heuristic_model(env),env,args...)

get_initial_cost(env::E) where {E<:AbstractLowLevelEnv}     = get_initial_cost(get_cost_model(env))
get_infeasible_cost(env::E) where {E<:AbstractLowLevelEnv}  = get_infeasible_cost(get_cost_model(env))
accumulate_cost(env::E, cost, transition_cost) where {E<:AbstractLowLevelEnv} = accumulate_cost(get_cost_model(env), cost, transition_cost)
# add_heuristic_cost(env::E, cost, h_cost) where {E<:AbstractLowLevelEnv} = add_heuristic_cost(env,get_cost_model(env),cost,h_cost)
get_transition_cost(env::E,s,a) where {E<:AbstractLowLevelEnv} = get_transition_cost(env,s,a,get_next_state(env,s,a))
get_transition_cost(env::E,s,a,sp) where {E<:AbstractLowLevelEnv} = get_transition_cost(get_cost_model(env),env,s,a,sp)

"""
    aggregate_costs(model, costs::Vector{T}) where {T}

Defines how costs from multiple distinct paths are combined into a single cost
for the whole solution. For example, the aggregation function for a
`SumOfTravelTime` objective is the `sum` of individual cost.
"""
function aggregate_costs end

"""
    a special version of aggregate_costs for the meta_env
"""
aggregate_costs_meta(m::AbstractCostModel,args...) = aggregate_costs(m,args...)

abstract type AggregationFunction end
struct MaxCost <: AggregationFunction end
(f::MaxCost)(costs...) = maximum(costs...)
struct SumCost <: AggregationFunction end
(f::SumCost)(costs...) = sum(costs...)

"""
    FullCostModel{F,T,M<:AbstractCostModel{T}} <: AbstractCostModel{T}

The `FullCostModel` defines all the behavior required for running CBS-based
algorithms.

Elements:
- f::F must be callable, and defines how a vector of path costs (e.g., the
    paths of a solution) should be combined into a single cost that reflects
    the cost of the entire path group together
- model::M<:AbstractCostModel{T} defines how the cost is computed during low
level search (when individual paths are being compared against each other).
"""
struct FullCostModel{F,T,M<:AbstractCostModel{T}} <: AbstractCostModel{T}
    f::F        # the aggregation function
    model::M    # the low level cost model
end
get_aggregation_function(m::FullCostModel)  = m.f
aggregate_costs(m::FullCostModel, costs)    = m.f(costs)
get_cost_model(m::FullCostModel)            = m.model
for op in [:accumulate_cost,:get_initial_cost,:compute_path_cost,
    :get_infeasible_cost,:add_heuristic_cost,:get_transition_cost]
    @eval $op(model::FullCostModel,args...) = $op(model.model,args...)
end
for T in [:FullCostModel]
    @eval begin
        add_heuristic_cost(m::$T, h::H, env::E, args...) where {H<:AbstractCostModel,E<:AbstractLowLevelEnv} = add_heuristic_cost(m.model, h, env, args...)
    end
end

"""
    CompositeCostModel{T}

Combines multiple cost models in a specific order (i.e., to use for cascaded
tie-breaking).
"""
struct CompositeCostModel{M<:Tuple,T<:Tuple} <: AbstractCostModel{T}
    cost_models::M
end
function construct_composite_cost_model(args...)
    models = Tuple(args)
    for m in models
        @assert typeof(m) <: AbstractCostModel
    end
    cost_types = map(m->cost_type(m),models)
    CompositeCostModel{typeof(models),Tuple{cost_types...}}(models)
end
# function get_transition_cost(model::C,env,s,a,sp) where {C<:CompositeCostModel}
#     cost_type(model)(map(m->get_transition_cost(m,env,s,a,sp), model.cost_models))
# end
function accumulate_cost(model::C, cost::T, transition_cost::T) where {T,M,C<:CompositeCostModel{M,T}}
    new_cost = map(x->accumulate_cost(x[1],x[2],x[3]),
    zip(model.cost_models, cost, transition_cost))
    T(new_cost)
end
function aggregate_costs(model::C, costs::Vector{T}) where {T,M,C<:CompositeCostModel{M,T}}
    aggregated_costs = map(
        i->aggregate_costs(model.cost_models[i], map(c->c[i], costs)), 1:length(model.cost_models))
    T(aggregated_costs)
end
function aggregate_costs_meta(model::C, costs::Vector{T}) where {T,M,C<:CompositeCostModel{M,T}}
    aggregated_costs = map(
        i->aggregate_costs_meta(model.cost_models[i], map(c->c[i], costs)), 1:length(model.cost_models))
    T(aggregated_costs)
end
for op in [:get_initial_cost,:get_infeasible_cost,:get_transition_cost,
    :compute_path_cost]
    @eval $op(model::CompositeCostModel,args...) = cost_type(model)(map(m->$op(m,args...),model.cost_models))
end
function add_heuristic_cost(model::C, cost::T, h_cost) where {T,M,C<:CompositeCostModel{M,T}}
    T(map(
        i->add_heuristic_cost(model.cost_models[i], cost[i], h_cost[i]),
        1:length(cost)
        ))
end
function compute_heuristic_cost(m::C,h::H,env::E,cost,s) where {C<:CompositeCostModel,H<:AbstractCostModel,E<:AbstractLowLevelEnv}
    # T([compute_heuristic_cost(env,m,c,s) for (m,c) in zip(model.cost_models,cost)])
    cost_type(m)(map(
        i->compute_heuristic_cost(
            m.cost_models[i],
            h.cost_models[i],
            env,
            cost[i],
            s),
        1:length(cost)
        ))
end

"""
    MetaCost

`MetaCost` maintaining separate costs for individual agents that have been
combined into a MetaAgent.
- independent_costs::Vector{T} a vector of costs, 1 per agent
- total_cost::T the total cost, which reflects the combined cost (its
    interpretation depends on the `MetaCostModel` used to define cost-
    upating behavior)
"""
struct MetaCost{T}
    independent_costs::Vector{T}
    total_cost::T
end
Base.isless(m1::MetaCost,m2::MetaCost) = m1.total_cost < m2.total_cost

"""
    MetaCostModel

Defines the cost-updating behavior of `MetaCost` for MetaAgent applications.
"""
struct MetaCostModel{T,M<:AbstractCostModel{T}} <: AbstractCostModel{MetaCost{T}}
    model::M
    num_agents::Int
end
aggregate_costs(m::C, costs::Vector{T}) where {T,C<:MetaCostModel} = aggregate_costs(m.model, costs)
# function add_heuristic_cost(m::C, cost, h_cost) where {C<:MetaCostModel}
#     costs = map(i->add_heuristic_cost(
#         m.model,
#         cost.independent_costs[i],
#         h_cost[i]),1:m.num_agents)
#     MetaCost(costs, aggregate_costs_meta(m.model, costs))
# end
function accumulate_cost(model::M, cost::MetaCost{T}, transition_cost::Vector{T}) where {T,M<:MetaCostModel}
    new_costs = Vector{T}()
    for (i,(c1,c2)) in enumerate(zip(
        cost.independent_costs,
        transition_cost))
        push!(new_costs, accumulate_cost(model.model, c1, c2))
    end
    total_cost = aggregate_costs_meta(model.model,new_costs)
    new_cost = MetaCost{T}(new_costs,total_cost)
    return new_cost
end
function accumulate_cost(model::M, cost::MetaCost{T}, transition_cost::MetaCost{T}) where {T,M<:MetaCostModel}
    accumulate_cost(model, cost, transition_cost.independent_costs)
end
function get_initial_cost(model::C) where {C<:MetaCostModel}
    costs = map(a->get_initial_cost(model.model),1:model.num_agents)
    MetaCost(costs,aggregate_costs(model,costs))
end
function get_infeasible_cost(model::C) where {C<:MetaCostModel}
    costs = map(a->get_infeasible_cost(model.model),1:model.num_agents)
    MetaCost(costs,aggregate_costs(model,costs))
end

################################################################################
############################## Atomic Cost Models ##############################
################################################################################

"""
    LowLevelCostModel{C}

The low level cost model defines the objective to be optimized by the
solver at the low level. An optimal low level solver will return a path if a
feasible path exists) of minimal cost under the objective specified by the
associated LowLevelCostModel.
The parameter `C` defines the `cost_type` of the objective. The following
functions must be implemented for a `LowLevelCostModel` to be used:
* `get_initial_cost(model::LowLevelCostModel,env)` - returns
the initial_cost for a path
* `get_transition_cost(model::LowLevelCostModel{C},path::Path,s::S,a::A,
    sp::S) where {S,A,C}` - defines the cost associated with taking action
    `a` from state `s` to arrive in state `sp` according to the objective
    defined by `model` given that `s` is the "tip" of `path`.
* `accumulate_cost(model::LowLevelCostModel{C}, current_cost::C,
    transition_cost::C)` - defines how cost accumulates as new `PathNode`s
    are added to a Path.
"""
abstract type LowLevelCostModel{T} <: AbstractCostModel{T} end

export
    TransformCostModel

"""
    TransformCostModel{T,M<:LowLevelCostModel{T}} <: LowLevelCostModel{T}

Applies a transformation to an underlying cost model.
    e.g., `TransformCostModel(c->2*c, TravelTime())`
"""
struct TransformCostModel{T,M<:LowLevelCostModel{T}} <: LowLevelCostModel{T}
    f::Function
    model::M
end
# get_transition_cost(m::TransformCostModel,args...) = m.f(get_transition_cost(m.model,args...))
# accumulate_cost(m::TransformCostModel, args...)     = accumulate_cost(m.model,args...)
# get_initial_cost(m::TransformCostModel,args...)     = get_initial_cost(m.model,args...)
# get_infeasible_cost(m::TransformCostModel)          = get_infeasible_cost(m.model)
# add_heuristic_cost(m::TransformCostModel,args...)   = add_heuristic_cost(m.model,args...)
for op in [:accumulate_cost,:get_initial_cost,
    :get_infeasible_cost,:add_heuristic_cost,:get_transition_cost]
    @eval $op(model::TransformCostModel,args...) = model.f($op(model.model,args...))
end
# function compute_heuristic_cost(m::TransformCostModel,env::E,args...) where {E<:AbstractLowLevelEnv}
#     m.f(compute_heuristic_cost(m.model,env,args...))
# end
for op in [:add_heuristic_cost,:compute_heuristic_cost]
    @eval begin
        function $op(m::TransformCostModel, h::H, env::E, args...) where {H<:AbstractCostModel,E<:AbstractLowLevelEnv}
            m.f($op(m.model, h, env, args...))
        end
        function $op(m::TransformCostModel, env::E, args...) where {E<:AbstractLowLevelEnv}
            m.f($op(m.model, env, args...))
        end
    end
end
# add_heuristic_cost(m::TransformCostModel, h::H, env::E, args...) where {H<:AbstractCostModel,E<:AbstractLowLevelEnv} = m.f(add_heuristic_cost(m.model, h, env, args...))
# compute_heuristic_cost(m::TransformCostModel, h::H, env::E, args...) where {H<:AbstractCostModel,E<:AbstractLowLevelEnv} = m.f(add_heuristic_cost(m.model, h, env, args...))

"""
    TravelTime <: LowLevelCostModel{Float64}

Cost model that assigns cost equal to the duration of a path.
"""
struct TravelTime       <: LowLevelCostModel{Float64} end
accumulate_cost(model::TravelTime,      cost,transition_cost) = cost+transition_cost

"""
    TravelDistance <: LowLevelCostModel{Float64}

Cost model that assigns cost equal to the length (distance) of a path.
"""
struct TravelDistance   <: LowLevelCostModel{Float64} end
accumulate_cost(model::TravelDistance,  cost,transition_cost) = cost+transition_cost

struct FinalTime        <: LowLevelCostModel{Float64} end
accumulate_cost(model::FinalTime,       cost,transition_cost) = cost+transition_cost

"""
    NullCost <: LowLevelCostModel{Float64}

Cost equal to `0.0`.
"""
struct NullCost         <: LowLevelCostModel{Float64} end
get_transition_cost(model::F,env,s,a,sp) where {F<:NullCost} = 0.0
accumulate_cost(model::NullCost,        cost,transition_cost) = cost

export
    DeadlineCost,
    set_deadline!,
    FullDeadlineCost,
    # WeightedDeadlinesCost,
    MultiDeadlineCost,
    SumOfMakeSpans

abstract type AbstractDeadlineCost <: LowLevelCostModel{Float64} end
"""
    DeadlineCost

Identical to `TravelTime`, except for the behavior of
`add_heuristic_cost`.

add_heuristic_cost: `c = max(0.0, t + Δt - deadline)`
"""
mutable struct DeadlineCost     <: AbstractDeadlineCost
    deadline::Float64 # deadline
    m::TravelTime
end
DeadlineCost(deadline::R) where {R<:Real} = DeadlineCost(deadline,TravelTime())
function set_deadline!(m::DeadlineCost,t_max)
    m.deadline = minimum(t_max)
    return m
end
set_deadline!(m::C,args...) where {C<:AbstractCostModel} = nothing
set_deadline!(m::C,args...) where {C<:FullCostModel} = set_deadline!(m.model,args...)
set_deadline!(m::C,args...) where {C<:MetaCostModel} = set_deadline!(m.model,args...)
function set_deadline!(m::C,args...) where {C<:CompositeCostModel}
    for model in m.cost_models
        set_deadline!(model,args...)
    end
end
add_heuristic_cost(m::C, cost, h_cost) where {C<:DeadlineCost} = max(0.0, cost + h_cost - m.deadline) # assumes heuristic is PerfectHeuristic
for op in [:accumulate_cost,:get_initial_cost,:get_transition_cost,:compute_path_cost]
    @eval $op(model::AbstractDeadlineCost,args...) = $op(model.m,args...)
end
FullDeadlineCost(model::DeadlineCost) = FullCostModel(costs->max(0.0, maximum(costs)),model)

"""
    MultiDeadlineCost

Combines multiple deadlines according to some specified aggregation function.
"""
struct MultiDeadlineCost{F} <: AbstractDeadlineCost
    f::F # aggregation function
    tF::Vector{Float64}
    root_nodes::Vector{Int} # weights and deadlines correspond to root_nodes only
    weights::Vector{Float64}
    deadlines::Vector{Float64}
    m::TravelTime
end
const SumOfMakeSpans = MultiDeadlineCost{SumCost}
const MakeSpan = MultiDeadlineCost{MaxCost}
SumOfMakeSpans(tF,root_nodes,weights,deadlines) = MultiDeadlineCost(SumCost(),Float64.(tF),root_nodes,Float64.(weights),Float64.(deadlines),TravelTime())
SumOfMakeSpans() = MultiDeadlineCost(SumCost(),Float64[],Int[],Float64[],Float64[],TravelTime())
MakeSpan(tF,root_nodes,weights,deadlines) = MultiDeadlineCost(MaxCost(),Float64.(tF),root_nodes,Float64.(weights),Float64.(deadlines),TravelTime())
MakeSpan() = MultiDeadlineCost(MaxCost(),Float64[],Int[],Float64[],Float64[],TravelTime())
function set_deadline!(m::M,t_max) where {M<:MultiDeadlineCost}
    m.deadlines .= t_max
    return m
end
add_heuristic_cost(m::C, cost, h_cost) where {C<:MultiDeadlineCost} = m.f(m.weights .* max.(0.0, cost .+ h_cost .- m.deadlines)) # assumes heuristic is PerfectHeuristic
aggregate_costs(m::C, costs::Vector{T}) where {T,C<:MultiDeadlineCost}  = m.f(m.tF[m.root_nodes] .* m.weights)
aggregate_costs_meta(m::C, costs::Vector{T}) where {T,C<:MultiDeadlineCost}  = maximum(costs)

# MakeSpan(model::FinalTime=FinalTime()) = FullCostModel(maximum,model)
SumOfTravelDistance(model::TravelDistance=TravelDistance()) = FullCostModel(sum,model)
SumOfTravelTime(model::TravelTime=TravelTime()) = FullCostModel(sum, model)

export EnvDeadlineCost
"""
    EnvDeadlineCost

Deadlines come from an external environment
"""
@with_kw struct EnvDeadlineCost{E,F} <: AbstractDeadlineCost
    env::E = nothing
    f::F   = SumCost()          # aggregation function
    m::TravelTime = TravelTime()
end
# EnvDeadlineCost() = EnvDeadlineCost(SumCost(),nothing,TravelTime())
EnvDeadlineCost{E,F}() where {E,F} = EnvDeadlineCost(env=E(),f=F())
aggregate_costs(::EnvDeadlineCost,costs) = maximum(costs)

################################################################################
############################# HardConflictHeuristic ############################
################################################################################
export
    AbstractConflictTable,
    HardConflictTable,
    get_time_horizon,
    get_planned_vtx,
    reset_path!,
    set_path!,
    partially_set_path!,
    get_conflict_value,
    get_conflicting_paths,
    construct_empty_lookup_table

abstract type AbstractConflictTable end

"""
    HardConflictTable

Stores a lookup table of planned paths for all agents.
When agent `i` queries the table, `table.paths[i]` (the existing path plan
for agent `i`) must be subtracted so that the agent does not try to avoid
conflicts with itself.
"""
@with_kw struct HardConflictTable{V<:AbstractVector,M<:AbstractMatrix} <: AbstractConflictTable
    paths   ::Vector{V} = Vector{SparseVector{Int,Int}}()
    CAT     ::M         = SparseMatrixCSC(zeros(2,2)) # global table
    start_time::Int     = 0
end
get_time_horizon(h::T) where {T<:HardConflictTable} = size(h.CAT,2)
function get_planned_vtx(h::T,agent_idx::Int,t::Int) where {T<:HardConflictTable}
    t_idx = t + (1-h.start_time)
    if agent_idx == -1
        return 0
    else
        return get(h.paths[agent_idx], t_idx, -1)
    end
end
function reset_path!(h::T,path_idx::Int) where {V,M,T<:HardConflictTable{V,M}}
    # first remove the old path from the lookup table
    for (t,vtx) in enumerate(h.paths[path_idx])
        if vtx > 0
            h.CAT[vtx,t] = h.CAT[vtx,t] - 1
        end
    end
    # initialize an empty new path
    h.paths[path_idx] = V(zeros(Int,get_time_horizon(h)))
    return h
end
function set_path!(h::T,path_idx::Int,path::Vector{Int},start_time::Int=0) where {V,M,T<:HardConflictTable{V,M}}
    # println("Updating conflict table with path ", path, " for agent ",path_idx)
    reset_path!(h,path_idx)
    # now add new path vtxs to new path and lookup table
    for (i,vtx) in enumerate(path)
        t = (start_time-h.start_time) + i
        h.paths[path_idx][t] = vtx
        h.CAT[vtx,t] = h.CAT[vtx,t] + 1
    end
    h
end
"""
    partially_set_path!

Only replaces the cached path from start_time to length(path). Useful if you
want the remainder of the cached path to stay in the lookup table (i.e. for
repairing an existing plan).
"""
function partially_set_path!(h::T,path_idx::Int,path::Vector{Int},start_time::Int=0) where {V,M,T<:HardConflictTable{V,M}}
    # now add new path vtxs to new path and lookup table
    t0 = (start_time-h.start_time)
    for (i,vtx) in enumerate(path)
        t = t0 + i
        # try
        checkbounds(Bool,h.paths[path_idx],t) ? nothing : throw(SolverException("t = $(t) Out of bounds in partially_set_path! with start_time = $(h.start_time)"))
        old_vtx = h.paths[path_idx][t]
        if old_vtx != 0
            h.CAT[old_vtx,t] = h.CAT[old_vtx,t] - 1
        end
        h.paths[path_idx][t] = vtx
        h.CAT[vtx,t] = h.CAT[vtx,t] + 1
        # catch e
        #     throw(e)
        # end
    end
    h
end
function get_conflict_value(h::HardConflictTable,agent_idx::Int,vtx::Int,t::Int)
    t_idx = t + (1-h.start_time)
    try
        # c = h.CAT[vtx,t_idx]
        c = get(h.CAT, (vtx,t_idx), 0)
        if get_planned_vtx(h,agent_idx,t) == vtx # conflict with own trajectory
            c = c - 1
        end
        return c
    catch e
        @show vtx,t_idx,size(h.CAT)
        rethrow(e)
    end
end

"""
    get_conflicting_paths

Operates over a lookup table and returns a dictionary mapping path index to the
time index at which the conflict occurred
"""
function get_conflicting_paths(ct::T) where {T<:HardConflictTable}
    conflict_idxs = map(idx->Int.([idx.I...]), findall(ct.CAT .> 1))
    agent_id_to_time_idx = Dict{Int,Int}()
    for idx in conflict_idxs
        vtx = idx[1]
        t = idx[2]+ct.start_time-1
        for (agent_id,path) in enumerate(ct.paths)
            if get_planned_vtx(ct,agent_id,t) == vtx
                agent_id_to_time_idx[agent_id] = t
            end
        end
    end
    agent_id_to_time_idx
end

"""
    construct_empty_lookup_table(G,T::Int)

Returns an empty lookup table.
"""
construct_empty_lookup_table(V::Int,T::Int) = SparseMatrixCSC(zeros(V,T))
construct_empty_lookup_table(graph::G,T::Int) where {G<:AbstractGraph} = construct_empty_lookup_table(nv(graph),T)
function HardConflictTable(graph::G,T::Int,num_agents::Int) where {G<:AbstractGraph}
    HardConflictTable(
        paths = map(i->SparseVector(zeros(Int,T)),1:num_agents),
        CAT = construct_empty_lookup_table(graph,T)
        )
end
function HardConflictTable(graph::G,T::Float64,num_agents::Int) where {G<:AbstractGraph}
    @assert abs(T - Int(round(T))) < 0.01
    HardConflictTable(graph,Int(round(T)),num_agents)
end

################################################################################
############################### SoftConflictHeuristic ##############################
################################################################################
export
    SoftConflictTable,
    get_fat_path,
    add_fat_path_to_table!,
    populate_soft_lookup_table!

"""
    SoftConflictTable
"""
@with_kw struct SoftConflictTable{V,M<:AbstractMatrix} <: AbstractConflictTable
    paths   ::Vector{V} = Vector{SparseMatrixCSC{Float64,Int}}()
    CAT     ::M         = SparseMatrixCSC(zeros(2,2)) # global table
    start_time::Int     = 0
end
get_time_horizon(h::SoftConflictTable) = size(h.CAT,2)
get_conflict_value(h::SoftConflictTable,vtx::Int,t::Int) = h.CAT[vtx,t]
get_conflict_value(h::SoftConflictTable,agent_idx::Int,vtx::Int,t::Int) = h.CAT[vtx,t] - h.paths[agent_idx][vtx,t]

"""
    get_fat_path(G,D,start_vtx,goal_vtx)

returns a fat path through `G` from `start_vtx` to `goal_vtx`. Each set
of vertices in the fat path contains all vertices with distance d1 from
start_vtx and distance d2 to goal_vtx, where d1+d2 == the length of the
shortest path(s) from `start_vtx` to `goal_vtx`

G is a graph, D is the distance matrix
"""
function get_fat_path(G,D,start_vtx::Int,goal_vtx::Int)
    fat_path = Vector{Set{Int}}([Set{Int}(start_vtx)])
    for i in 1:D[start_vtx,goal_vtx]
        next_set = Set{Int}()
        for src_vtx in fat_path[end]
            for dst_vtx in outneighbors(G,src_vtx)
                if D[dst_vtx,goal_vtx] <= D[src_vtx,goal_vtx] - 1
                    push!(next_set, dst_vtx)
                end
            end
        end
        push!(fat_path, next_set)
    end
    fat_path
end

"""
    add_fat_path_to_table(CAT,fat_path)
"""
function add_fat_path_to_table!(CAT,fat_path,t0=0)
    for t in 1:length(fat_path)
        idxs = collect(fat_path[t])
        if t+t0 > 0
            CAT[idxs,t+t0] .+= 1.0/length(idxs)
        end
    end
end

"""
    populate_soft_lookup_table!(CAT,start_times,start_vtxs,goal_vtxs)
"""
function populate_soft_lookup_table!(CAT,G,D,start_vtxs,goal_vtxs,start_times=zeros(Int,length(start_vtxs)))
    for (s,t,g) in zip(start_vtxs,start_times,goal_vtxs)
        fat_path = get_fat_path(G,D,s,g)
        add_fat_path_to_table!(CAT,t0,fat_path)
    end
    CAT
end

"""
    construct_empty_lookup_table(graph,T::Int)

Returns a soft lookup table to encode possible paths for each agent through
`graph`. The argument `T` defines the time horizon of the lookup table.
"""
function SoftConflictTable(n_agents::Int,N::Int,T::Int,t0=0)
    SoftConflictTable(
        paths = map(i->spzeros(Float64,N,T),1:n_agents),
        CAT = spzeros(Float64,N,T),
        t0=t0
        )
end
function SoftConflictTable(n_agents::Int,N::Int,T::Float64,t0=0)
    @assert abs(T - Int(round(T))) < 0.01
    @assert abs(t0 - Int(round(t0))) < 0.01
    SoftConflictTable(n_agents,N,Int(round(T)),Int(round(t0)))
end

# """
#     `construct_empty_lookup_table(graph,T::Int)`
#
#     Returns a soft lookup table to encode possible paths for each agent through
#     `graph`. The argument `T` defines the time horizon of the lookup table.
# """
# function SoftConflictTable(graph,n_agents::Int,T::Int,t0=0)
#     SoftConflictTable(
#         paths = map(i->construct_empty_lookup_table(graph,T),1:n_agents),
#         CAT = construct_empty_lookup_table(graph,T),
#         t0=0
#         )
# end

# """
#     `construct_and_populate_soft_lookup_table!`
# """
# function SoftConflictTable(graph,
#         start_vtxs::Vector{Int},
#         goal_vtxs::Vector{Int},
#         start_times=zero(Int,length(start_vtxs),
#         T = Int(round(maximum(start_times) + nv(graph))),
#         )
#     )
#     CAT = construct_empty_lookup_table(graph,T)
#     D = get_dist_matrix(graph)
#     populate_soft_lookup_table!(CAT,graph,D,start_vtxs,goal_vtxs,start_times)
#     SoftConflictTable(CAT)
# end

################################################################################
############################# ConflictCostModel ############################
################################################################################
export
    ConflictCostModel,
    HardConflictCost,
    SoftConflictCost

struct ConflictCostModel{T<:Union{HardConflictTable,SoftConflictTable}} <: LowLevelCostModel{Float64}
    table::T
end
get_conflict_value(h::H, args...) where {H<:ConflictCostModel} = get_conflict_value(h.table, args...)
accumulate_cost(h::H,cost,transition_cost) where {H<:ConflictCostModel} = cost + transition_cost

HardConflictCost(args...) = FullCostModel(sum,ConflictCostModel(HardConflictTable(args...)))
SoftConflictCost(args...) = FullCostModel(sum,ConflictCostModel(SoftConflictTable(args...)))
SoftConflictCost(table::SoftConflictTable,args...) = FullCostModel(sum,ConflictCostModel(table))

get_time_horizon(h::H) where {H<:ConflictCostModel} = get_time_horizon(h.table)
get_planned_vtx(h::H,args...) where {T<:HardConflictTable,H<:ConflictCostModel}  = get_planned_vtx(h.table,args...)
reset_path!(h::H,args...) where {H<:ConflictCostModel}    = reset_path!(h.table,args...)
reset_path!(h::H,args...) where {H<:AbstractCostModel}    = nothing
reset_path!(h::FullCostModel{F,T,M},args...) where {F,T,M<:ConflictCostModel} = reset_path!(h.model,args...)
function reset_path!(h::H,args...) where {H<:CompositeCostModel}
    for m in h.cost_models
        reset_path!(m,args...)
    end
end
set_path!(h::H,args...) where {H<:ConflictCostModel}    = set_path!(h.table,args...)
set_path!(h::H,args...) where {H<:AbstractCostModel}    = nothing
set_path!(h::FullCostModel{F,T,M},args...) where {F,T,M<:ConflictCostModel} = set_path!(h.model,args...)
function set_path!(h::H,args...) where {H<:CompositeCostModel}
    for m in h.cost_models
        set_path!(m,args...)
    end
end
partially_set_path!(h::H,args...) where {H<:ConflictCostModel}    = partially_set_path!(h.table,args...)
partially_set_path!(h::H,args...) where {H<:AbstractCostModel}    = nothing
partially_set_path!(h::FullCostModel{F,T,M},args...) where {F,T,M<:ConflictCostModel} = partially_set_path!(h.model,args...)
function partially_set_path!(h::H,args...) where {H<:CompositeCostModel}
    for m in h.cost_models
        partially_set_path!(m,args...)
    end
end


@recipe function f(p::Passive{T}) where {T}
    seriestype --> :csg
    linecolor --> :silver
    seriescolor --> :silver
    fillalpha --> 0.2
    l = p.name != "" ? p.name : "Passive $(p.id)"
    @series begin
        #add empty line so that label is shown with alpha = 1
        seriestype := :path
        label --> l
        linewidth := 1
        T[], T[]
    end
    label := ""
    p.geometry
end

@recipe function f(sc::Semiconductor{T}) where {T}
    seriestype --> :csg
    linecolor --> :grey
    seriescolor --> :grey
    @series begin
        #add empty line so that label is shown with alpha = 1
        seriestype := :path
        label --> "Semiconductor"
        linewidth := 1
        T[], T[]
    end
    label := ""
    sc.geometry
end

@recipe function f(contact::Contact{T}) where {T}
    seriestype --> :csg
    seriescolor --> contact.id
    linecolor --> contact.id
    fillalpha --> 0.2
    l = contact.name != "" ? "$(contact.name) (id: $(contact.id))" : "Contact - id: $(contact.id)"
    @series begin
        #add empty line so that label is shown with alpha = 1
        seriestype := :path
        label --> l
        linewidth := 1
        T[], T[]
    end
    label := ""
    contact.geometry
end

@recipe function f(det::SolidStateDetector; show_semiconductor = false, show_passives = true, n_samples = 40)

    show_normal --> false

    plot_objects = []
    append!(plot_objects, det.contacts)
    if show_semiconductor
        push!(plot_objects, det.semiconductor)
    end
    if show_passives && !ismissing(det.passives)
        append!(plot_objects, det.passives)
    end

    if haskey(plotattributes, :seriestype) && plotattributes[:seriestype] == :samplesurface
        scales = [get_scale(o.geometry) for o in plot_objects]
        max_scale = maximum(scales)
    end

    for (i,o) in enumerate(plot_objects)
        @series begin
            if haskey(plotattributes, :seriestype) && plotattributes[:seriestype] == :samplesurface
                n_samples --> Int(ceil(n_samples * scales[i]/max_scale))
                CSG_scale := scales[i]
            end
            o
        end
    end
end


__precompile__()

module Intervals

using Compat
using Compat.Dates
using Compat.Printf
using TimeZones

using Compat.Dates: value, coarserperiod
using Compat: AbstractDateTime

import Base: ⊆, ⊇, ⊈, ⊉, union, union!, merge

abstract type AbstractInterval{T} end

Base.eltype(::AbstractInterval{T}) where {T} = T

if VERSION >= v"0.7"
    Base.broadcastable(x::AbstractInterval) = Ref(x)
end

include("inclusivity.jl")
include("endpoint.jl")
include("interval.jl")
include("anchoredinterval.jl")
include("description.jl")

export AbstractInterval,
       Interval,
       AnchoredInterval,
       HourEnding,
       HourBeginning,
       HE,
       HB,
       Inclusivity,
       inclusivity,
       first,
       last,
       isclosed,
       anchor,
       span,
       merge,
       union,
       union!,
       less_than_disjoint,
       greater_than_disjoint,
       superset,
       .., ≪, ≫, ⊆, ⊇, ⊈, ⊉
end


using Test

using RealNeuralNetworks.NeuralNets
using RealNeuralNetworks.Neurons

using CSV

@testset "test NeuralNets module..." begin
    neuronId2neuron = Dict{Int, Neuron}()
    neuronId2neuron[77625] = Neurons.load(joinpath(__DIR__, "../asset/77625.swc.bin"))
    neuronId2neuron[77641] = Neurons.load(joinpath(__DIR__, "../asset/77641.swc.bin"))

    df = CSV.load(joinpath(__DIR__, "../asset/77265.pre.synapses.csv")
    #Neurons.attach_pre_synapses()
    net = NeuralNet(neuronId2neuron)
end


"""
`module JAC.AngularCoefficientsRatip2013`
    ... a submodel of JAC that provides data types for angular (T and V) coefficients and methods to calculate the angular
        coefficients between pairs of relativistic configuration state functions via an external Fortran library.
"""
module AngularCoefficientsRatip2013

    using  ..Basics,  ..Defaults,  ..ManyElectron
    using  Libdl: dlopen, dlsym

    export AngularTcoeff, AngularVcoeff

    const ANCOPATH = joinpath(@__DIR__, "..", "deps", "bin", "libanco-ratip2013.so")

    """
    `AngularCoefficientsRatip2013.twice(j::AngularJ64)`  ... returns `2*j` for a given angular momentum quantum number `j`.
    """
    twice(j::AngularJ64) = Int(2*j.num//j.den)

    """
    `AngularCoefficientsRatip2013.getsubshells(csf::CsfR)`
        ... returns the array of subshells of a relativistic configuration state function.
    """
    getsubshells(csf::CsfR) = csf.useStandardSubshells ?   Defaults.GBL_STANDARD_SUBSHELL_LIST : csf.subshells

    """
    `AngularCoefficientsRatip2013.have_same_subshells(csfa::CsfR, csfb::CsfR)`
        ... checks whether two relativistic configurations state functions have the same subshells; true is returned if
            both CSF have the same subshell list, and false otherwise.
    """
    function have_same_subshells(csfa::CsfR, csfb::CsfR)
        return csfa.useStandardSubshells && csfb.useStandardSubshells || getsubshells(csfa)==getsubshells(csfb)
    end

    """
    `struct AngularCoefficientsRatip2013.Fnkappa`
        ... type that mirrors `nkappa` from the Fortran library. This type is used to communicate with the external Fortran
            library. It can be converted from and to a `Subshell` using the appropriate constructors.
    """
    struct Fnkappa      # nkappa
         n    ::Int32   # integer
         kappa::Int32   # integer
    end

    Fnkappa(s::Subshell) = Fnkappa(s.n, s.kappa)

    """
    `AngularCoefficientsRatip2013.Subshell(nk::Fnkappa)`
        ... constructor to define a Subshell (instance) from the quantum numbers n, kappa.
    """
    Subshell(nk::Fnkappa) = Subshell(nk.n, nk.kappa)

    """
    `AngularCoefficientsRatip2013.maxocc(nk::Fnkappa)`  ... return the maximal occupation number for a given relativistic subshell.
    """
    maxocc(nk::Fnkappa) = 2abs(nk.kappa)

    """
    `struct AngularCoefficientsRatip2013.FancoTcoeff`
        ... type that mirrors `anco_T_coeff` from the Fortran library. This type is used to communicate with the external
            Fortran library. It can be converted to a `AngularTcoeff` using the appropriate constructor.
    """
    struct FancoTcoeff # anco_T_coeff
        nu::Int32      # integer
        a::Fnkappa     # nkappa
        b::Fnkappa     # nkappa
        T::Float64     # real(kind=dp)
    end

    """
    `struct AngularCoefficientsRatip2013.FancoVcoeff`
        ... type that mirrors `anco_V_coeff` from the Fortran library. This type is used to communicate with the external
            Fortran library. It can be converted to an `AngularVcoeff` using the appropriate constructor.
    """
    struct FancoVcoeff # anco_V_coeff
        nu::Int32      # integer
        a::Fnkappa     # nkappa
        b::Fnkappa     # nkappa
        c::Fnkappa     # nkappa
        d::Fnkappa     # nkappa
        V::Float64     # real(kind=dp)
    end

    """
    `struct AngularCoefficientsRatip2013.Fmtcoefficient`
        ... type that mirrors `mct_coefficient` from the Fortran library. This type is used to communicate with the
            external Fortran library. It can be converted to an `AngularTcoeff` using the appropriate constructor.
    """
    struct Fmctcoefficient # mct_coefficient
        r::Int32           # integer
        s::Int32           # integer
        a::Int16           # integer(kind=i2b)
        b::Int16           # integer(kind=i2b)
        ν::Int16           # integer(kind=i2b)
        T::Float64         # real(kind=dp)
    end

    """
    `struct AngularTcoeff`  ... type for representing angular T coefficients.
    """
    struct AngularTcoeff
        nu::Int
        a::Subshell
        b::Subshell
        T::Float64
    end

    # `Base.show(io::IO, coeff::AngularTcoeff)`  ... prepares a proper printout of the variable coeff::AngularTcoeff.
    function Base.show(io::IO, coeff::AngularTcoeff)
        sa = " T^$(coeff.nu)[" * string(coeff.a) * "," * string(coeff.b) * "] = $(coeff.T)"
        print(io, sa )
    end

    """
    `AngularCoefficientsRatip2013.AngularTcoeff(t::FancoTcoeff)`
        ... constructor to define an AngularTcoeff (instance) from an internal FancoTcoeff coefficient.
    """
    AngularTcoeff(t::FancoTcoeff) = AngularTcoeff(t.nu, Subshell(t.a), Subshell(t.b), t.T)

    """
    `AngularCoefficientsRatip2013.AngularTcoeff(t::Fmctcoefficient)`
        ... constructor to define an AngularTcoeff (instance) from an internal Fmctcoefficient and the list of relativistic
            subshells the CSFs are based on.
    """
    AngularTcoeff(t::Fmctcoefficient, subshells::AbstractArray{Subshell}) = AngularTcoeff(t.ν, subshells[t.a], subshells[t.b], t.T)

    """
    `struct AngularCoefficientsRatip2013.AngularTcoeff`  ... type for representing angular V coefficients.
    """
    struct AngularVcoeff
        nu::Int
        a::Subshell
        b::Subshell
        c::Subshell
        d::Subshell
        V::Float64
    end

    # `Base.show(io::IO, coeff::AngularVcoeff)`  ... prepares a proper printout of the variable coeff::AngularVcoeff.
    function Base.show(io::IO, coeff::AngularVcoeff)
        sa = " V^$(coeff.nu)[" * string(coeff.a) * "," * string(coeff.b) * ";" * string(coeff.c) * "," * string(coeff.d) * "] = $(coeff.V)"
        print(io, sa )
    end

    """
    `AngularCoefficientsRatip2013.AngularVcoeff(t::FancoVcoeff)`
        ... constructor to define an AngularVcoeff (instance) from an internal FancoVcoeff coefficient.
    """
    AngularVcoeff(t::FancoVcoeff) = AngularVcoeff(t.nu, Subshell(t.a), Subshell(t.b), Subshell(t.c), Subshell(t.d), t.V)

    const NUMBER_OF_MCT_COEFFICIENTS_MAX = 1000

    # The following `Ref`s need to be initialized at runtime (via the __init__() function)
    # in order to enable precompilation

    const ANCOLIB  = Ref{Ptr{Nothing}}() # dlopen(ANCOPATH)

    const no_anco_t_list             = Ref{Ptr{Int32}}() # cglobal(dlsym(ANCOLIB[], :no_anco_t_list), Int32)
    const no_anco_v_list             = Ref{Ptr{Int32}}() # cglobal(dlsym(ANCOLIB[], :no_anco_v_list), Int32)
    const number_of_mct_coefficients = Ref{Ptr{Int32}}() # cglobal(dlsym(ANCOLIB[], :number_of_mct_coefficients), Int32)

    const anco_t_list = Ref{Ptr{FancoTcoeff}}()     # cglobal(dlsym(ANCOLIB[], :anco_t_list), FancoTcoeff)
    const anco_v_list = Ref{Ptr{FancoVcoeff}}()     # cglobal(dlsym(ANCOLIB[], :anco_v_list), FancoVcoeff)
    const mct_list    = Ref{Ptr{Fmctcoefficient}}() # cglobal(dlsym(ANCOLIB[], :mct_list), Fmctcoefficient)

    const t_vector   = Ref{Vector{FancoTcoeff}}()     # unsafe_wrap(Vector{FancoTcoeff}, anco_t_list, 1000)
    const v_vector   = Ref{Vector{FancoVcoeff}}()     # unsafe_wrap(Vector{FancoVcoeff}, anco_v_list, 2000)
    const mct_vector = Ref{Vector{Fmctcoefficient}}() # unsafe_wrap(Vector{Fmctcoefficient}, mct_list, NUMBER_OF_MCT_COEFFICIENTS_MAX)

    function __init__()
        ANCOLIB[] = dlopen(ANCOPATH)

        no_anco_t_list[]             = cglobal(dlsym(ANCOLIB[], :no_anco_t_list), Int32)
        no_anco_v_list[]             = cglobal(dlsym(ANCOLIB[], :no_anco_v_list), Int32)
        number_of_mct_coefficients[] = cglobal(dlsym(ANCOLIB[], :number_of_mct_coefficients), Int32)

        anco_t_list[] = cglobal(dlsym(ANCOLIB[], :anco_t_list), FancoTcoeff)
        anco_v_list[] = cglobal(dlsym(ANCOLIB[], :anco_v_list), FancoVcoeff)
        mct_list[]    = cglobal(dlsym(ANCOLIB[], :mct_list), Fmctcoefficient)

        t_vector[]   = unsafe_wrap(Vector{FancoTcoeff}, anco_t_list[], 1000)
        v_vector[]   = unsafe_wrap(Vector{FancoVcoeff}, anco_v_list[], 2000)
        mct_vector[] = unsafe_wrap(Vector{Fmctcoefficient}, mct_list[], NUMBER_OF_MCT_COEFFICIENTS_MAX)
    end

    """
    `AngularCoefficientsRatip2013.number_of_t_coeffs()`
        ... to read the number of calculated T coefficients from the external library.
    """
    number_of_t_coeffs() = unsafe_load(no_anco_t_list[])

    """
    `AngularCoefficientsRatip2013.number_of_v_coeffs()`
        ... to read the number of calculated V coefficients from the external library.
    """
    number_of_v_coeffs() = unsafe_load(no_anco_v_list[])

    """
    `AngularCoefficientsRatip2013.number_of_mct_coeffs()`
        ... to read the number of calculated MCT coefficients from the external library.
    """
    number_of_mct_coeffs() = unsafe_load(number_of_mct_coefficients[])

    """
    `AngularCoefficientsRatip2013.view_t_coeffs()`
        ... to return the array of T coefficients as a view into the memory of the external library.
    """
    view_t_coeffs() = view(t_vector[], 1:number_of_t_coeffs())

    """
    `AngularCoefficientsRatip2013.view_v_coeffs()`
        ... to return the array of V coefficients as a view into the memory of the external library.
    """
    view_v_coeffs() = view(v_vector[], 1:number_of_v_coeffs())

    """
    `AngularCoefficientsRatip2013.view_mct_coeffs()`
        ... to return the array of MCT coefficients as a view into the memory of the external library.
    """
    view_mct_coeffs() = view(mct_vector[], 1:number_of_mct_coeffs())

    """
    `AngularCoefficientsRatip2013.angular_coefficients_pair(r::Integer, s::Integer)`
        ... to calculate the angular coefficients between the `r`-th and `s`-th configuration state function of the
            CSF set that was loaded into the external library.
    """
    function angular_coefficients_pair(r::Integer, s::Integer)
        ccall(dlsym(ANCOLIB[], :jac_anco_calculate_csf_pair), Nothing, (Ref{Int32}, Ref{Int32}), r, s)
        return AngularTcoeff.(view_t_coeffs()), AngularVcoeff.(view_v_coeffs())
    end

    """
    `AngularCoefficientsRatip2013.angular_coefficients_pair_1p(ν::Integer, r::Integer, s::Integer)`
        ... to calculate the angular coefficients between the `r`-th and `s`-th configuration state function of the CSF
            set that was loaded into the external library, for a non-scalar one-particle operator of rank `ν`.
    """
    function angular_coefficients_pair_1p(ν::Integer, r::Integer, s::Integer)
        ccall(dlsym(ANCOLIB[], :jac_anco_calculate_csf_pair_1p), Nothing, (Ref{Int32}, Ref{Int32}, Ref{Int32}), ν, r, s)
        return AngularTcoeff.(view_t_coeffs())
    end

    """
    `AngularCoefficientsRatip2013.load_csl(nocsf, nwshells, nwcore, number_of_electrons, subshell,
                                           totalJs, parities, occupations, seniorities, subshellJs, subshellXs)`
        ... a wrapper function for the call to the subroutine `jac_anco_load_csl` of the external library.
    """
    load_csl(nocsf::Integer, nwshells::Integer, nwcore::Integer, number_of_electrons::Integer,
             subshell::AbstractVector{Fnkappa},
             totalJs::AbstractVector{Int8}, parities::AbstractVector{UInt8},
             occupations::AbstractMatrix{Int8}, seniorities::AbstractMatrix{Int8},
             subshellJs::AbstractMatrix{Int8},  subshellXs::AbstractMatrix{Int8}) =
        ccall(dlsym(ANCOLIB[], :jac_anco_load_csl), Nothing,
             (Ref{Int32}, Ref{Int32}, Ref{Int32}, Ref{Int32}, Ref{Fnkappa}, Ref{Int8},
              Ref{UInt8}, Ref{Int8}, Ref{Int8}, Ref{Int8}, Ref{Int8}),
              nocsf, nwshells, nwcore, number_of_electrons, subshell, totalJs, parities,
              occupations, seniorities, subshellJs, subshellXs)

    """
    `AngularCoefficientsRatip2013.load_csl(csf::CsfR)`
        ... to load a single relativistic configuration state function into the external library. Returns the list of
            subshells the CSF is based on.
    """
    function load_csl(csf::CsfR)
        number_of_electrons = sum(csf.occupation)
        subshells = getsubshells(csf)
        nkappas = Fnkappa.(subshells)
        nwshells = length(nkappas)
        occ = reshape(Int8.(csf.occupation), nwshells, 1)
        sen = reshape(Int8.(csf.seniorityNr), nwshells, 1)
        suJ = reshape(Int8.(twice.(csf.subshellJ)), nwshells, 1)
        suX = reshape(Int8.(twice.(csf.subshellX)), nwshells, 1)
        totalJs =  Int8[twice(csf.J)]
        # parities = UInt8[Char(csf.parity)]
        if   csf.parity == Basics.plus    parities = UInt8[Char('+')]   else    parities = UInt8[Char('-')]   end
        nwcore = 0
        for i in eachindex(nkappas)
            if  occ[i]!=maxocc(nkappas[i])
                nwcore = i-1; break
            end
        end
        load_csl(1, nwshells, nwcore, number_of_electrons, nkappas, totalJs, parities, occ, sen, suJ, suX)
        subshells
    end

    """
    `AngularCoefficientsRatip2013.load_csl(csfa::CsfR, csfb::CsfR)`
        ... to load two relativistic configuration state functions into the external library. Returns a common
            list of subshells for both CSFs.
    """
    function load_csl(csfa::CsfR, csfb::CsfR)
        number_of_electrons = sum(csfa.occupation)
        sum(csfb.occupation) == number_of_electrons || error("CSFs must have the same number of electrons")
        if have_same_subshells(csfa, csfb)
            subshells = getsubshells(csfa)
            nwshells = length(subshells)
            occ = Matrix{Int8}(hcat(csfa.occupation, csfb.occupation))
            sen = Matrix{Int8}(hcat(csfa.seniorityNr, csfb.seniorityNr))
            suJ = Matrix{Int8}(twice.(hcat(csfa.subshellJ, csfb.subshellJ)))
            suX = Matrix{Int8}(twice.(hcat(csfa.subshellX, csfb.subshellX)))
        else
            subshells = sort(unique(vcat(getsubshells(csfa), getsubshells(csfb))))
            nwshells = length(subshells)
            occ = zeros(Int8, nwshells, 2)
            sen = zeros(Int8, nwshells, 2)
            suJ = zeros(Int8, nwshells, 2)
            suX = zeros(Int8, nwshells, 2)
            aindex, bindex = 1, 1
            ashells, bshells = getsubshells.((csfa, csfb))
            for i in eachindex(subshells)
                if ashells[aindex] == subshells[i]
                    occ[i,1] = csfa.occupation[aindex]
                    sen[i,1] = csfa.seniorityNr[aindex]
                    suJ[i,1] = twice(csfa.subshellJ[aindex])
                    suX[i,1] = twice(csfa.subshellX[aindex])
                    endof(ashells)<aindex && (aindex+=1)
                else
                    suX[i,1] = i==1 ? 0 : suX[i-1,1]
                end
                if bshells[bindex] == subshells[i]
                    occ[i,2] = csfb.occupation[bindex]
                    sen[i,2] = csfb.seniorityNr[bindex]
                    suJ[i,2] = twice(csfb.subshellJ[bindex])
                    suX[i,2] = twice(csfb.subshellX[bindex])
                    endof(bshells)<bindex && (bindex+=1)
                else
                    suX[i,2] = i==1 ? 0 : suX[i-1,2]
                end
            end
            aindex==endof(ashells) && bindex==endof(bshells) || error("Currently, only standard subshell ordering is supported")
        end
        totalJs =  Int8[twice(csfa.J), twice(csfb.J)]
        ## parities = UInt8[Char(csfa.parity), Char(csfb.parity)]
        parities = UInt8['a', 'a']
        if csfa.parity == Basics.plus      parities[1] = '+'     else       parities[1] = '-'      end
        if csfb.parity == Basics.plus      parities[2] = '+'     else       parities[2] = '-'      end
        nkappas = Fnkappa.(subshells)
        nwcore = 0
        for i in eachindex(subshells)
            coreocc = maxocc(nkappas[i])
            if  occ[i,1]!=coreocc || occ[i,2]!=coreocc
                nwcore = i-1; break
            end
        end
        load_csl(2, nwshells, nwcore, number_of_electrons, nkappas, totalJs, parities, occ, sen, suJ, suX)
        subshells
    end

    """
    `AngularCoefficientsRatip2013.mct_generate_coefficients(rl::Integer, ru::Integer, sl::Integer, su::Integer, iopar::Integer, rank::Integer)`
        ... to calculate the MCT coefficients between the `rl`..`ru`-th and `sl`..`su`-th configuration state functions of the CSF set
            that was loaded into the external library, for a non-scalar one-particle operator of parity `iopar` and rank `rank`.
    """
    function mct_generate_coefficients(rl::Integer, ru::Integer, sl::Integer, su::Integer, iopar::Integer, rank::Integer)
        ccall(dlsym(ANCOLIB[], :jac_mct_generate_coefficients), Nothing,
              (Ref{Int32}, Ref{Int32}, Ref{Int32}, Ref{Int32}, Ref{Int32}, Ref{Int32}), rl, ru, sl, su, iopar, rank)
        return view_mct_coeffs()
    end

end # module


function shufflevector_instrs(W, T, I, two_operands)
    typ = LLVM_TYPES[T]
    vtyp1 = "<$W x $typ>"
    M = length(I)
    vtyp3 = "<$M x i32>"
    vtypr = "<$M x $typ>"
    mask = '<' * join(map(x->string("i32 ", x), I), ", ") * '>'
    v2 = two_operands ? "%1" : "undef"
    M, """
        %res = shufflevector $vtyp1 %0, $vtyp1 $v2, $vtyp3 $mask
        ret $vtypr %res
    """
end
@generated function shufflevector(v1::Vec{W,T}, v2::Vec{W,T}, ::Val{I}) where {W,T,I}
    M, instrs = shufflevector_instrs(W, T, I, true)
    quote
        $(Expr(:meta, :inline))
        Vec(llvmcall($instrs, _Vec{$M,$T}, Tuple{_Vec{$W,$T}, _Vec{$W,$T}}, data(v1), data(v2)))
    end
end
@generated function shufflevector(v1::Vec{W,T}, ::Val{I}) where {W,T,I}
    M, instrs = shufflevector_instrs(W, T, I, false)
    quote
        $(Expr(:meta, :inline))
        Vec(llvmcall($instrs, _Vec{$M,$T}, Tuple{_Vec{$W,$T}}, data(v1)))
    end
end


function add!(n::NetworkP,extra::Extracellular)

    extra.v=zeros(Float64,length(n.t))
    coeffs=Array(Extra_coeffs,0)

    for j=1:length(fieldnames(n.neur))-4
        mycoeffs=add_extra(getfield(n.neur,j),extra)
        append!(coeffs,mycoeffs)
    end

    add_extra_(n,extra,coeffs)
end

function add!(n::NetworkP,intra::Intracellular)
    add_intra_(n,intra)
    (ind,newx)=neuron_ind(intra.neur,n.neur.c[intra.mtype])
    l=add_intra(ind,newx,intra.node,length(n.t),getfield(n.neur,intra.mtype))
    push!(n.neur.i,l)
    nothing
end

function add!(n::NetworkP,s::Stim)
    add_stim_(n,s)
    (ind,newx)=neuron_ind(s.neur,n.neur.c[s.mtype])
    l=add_stim(ind,newx,s.node,s.Is,getfield(n.neur,s.mtype))
    push!(n.neur.s,l)
end

function run!(n::NetworkP,init=false)

    #get initial conditions if uninitialized
    if init==true
        initialcon!(n.neur)
    end

    @inbounds main(n.neur)

    for j=1:length(n.extra)
        for k=1:length(fieldnames(n.neur))-4
            for l=1:length(n.neur.c[k])
                n.extra[j].v+=fetch_extra(getfield(n.neur,k),l,j)
            end
        end
    end

    for j=1:length(n.intra)
        @inbounds n.intra[j].v=fetch_intra(getfield(n.neur,n.intra[j].mtype),n.intra[j].mtype,n.neur.i[j])
    end

    reset_t!(n.neur)

    nothing
end

main{T<:Neuron}(n::Array{T,1})=(@inbounds for i=1:length(n);main(n[i]);end;nothing)

main{T<:Puddle}(p::Array{T,1})=(@inbounds main(p[1]))

function main(p::Puddle)

    id=myid()-1

    for p.i in 1:p.imax

        take!(p.r)

        for j=1:length(p.h.s)
            @inbounds p.n[p.h.s[j].neur].rhs[p.h.s[j].node]+=p.h.s[j].Is[p.i]
        end

        main(p.n)

        for j=1:length(p.h.e)
            for k=1:length(p.n)
                @inbounds p.h.e[j].v[p.i]+=a_mult_b(p.h.e[j].coeffs[k],p.n[k])
            end
        end

        for j=1:length(p.h.i)
            @inbounds p.h.i[j].v[p.i]=p.n[p.h.i[j].neur].v[p.h.i[j].node]
        end

        put!(p.r,id)

    end

    nothing
end

initialcon!{T<:Neuron}(n::Array{T,1})=(@inbounds for i=1:length(n);initialcon!(n[i]);end;nothing)

initialcon!{T<:Puddle}(p::Array{T,1})=(@inbounds initialcon!(p[1]); nothing)

initialcon!(p::Puddle)=(initialcon!(p.n); p.i=1)

function neuron_ind(x::Int64,y::Array{UnitRange{Int64},1})

    ind=0
    newx=0
    mysum=0

    for i in y

        if (x<=i[end])&&(x>=i[1])
            newx=x-mysum
            ind+=1
            break
        end

        mysum+=length(i[1])
        ind+=1
    end

    (ind,newx)

end

function get_dims(dims)

    y=Array(UnitRange{Int64},0)

    for i in dims
        push!(y,i[1])
    end
    y
end

function add_intra(p::Int64,neur::Int64,node::Int64,t::Int64,n::DArray)

    remotecall_fetch(((x,neur,node,t)->(push!(localpart(x)[1].h.i,(Intracellular(1,neur,node,zeros(Float64,t))));length(localpart(x)[1].h.i))),p+1,n,neur,node,t)

end

function add_stim(p::Int64,neur::Int64,node::Int64,s::Array{Float64,1},n::DArray)

    remotecall_fetch(((x,neur,node,t)->(push!(localpart(x)[1].h.s,(Stim(t,1,neur,node,0.0,0.0)));length(localpart(x)[1].h.s))),p+1,n,neur,node,s)

end

function fetch_intra(nd::DArray,p::Int64,ind::Int64)
    remotecall_fetch(((n,j)->localpart(n)[1].h.i[j].v),p+1,nd,ind)
end

function add_extra(n::DArray,e::Extracellular)

    mycoeffs=Array(Extra_coeffs,0)

    for p in workers()
        append!(mycoeffs,add_extra(n,e,p))
    end

    mycoeffs
end

function add_extra(n::DArray,e::Extracellular,p::Int64)
    remotecall_fetch(((x,y)->(c=extracellular(localpart(x)[1].n,y);push!(localpart(x)[1].h.e,typeof(e)(e.xyz,c,zeros(Float64,length(e.v))));c)),p,n,e)
end

function fetch_extra(nd::DArray,p::Int64,ind::Int64)
    remotecall_fetch(((n,j)->localpart(n)[1].h.e[j].v),p+1,nd,ind)
end

reset_t!{T<:Puddle}(p::Array{T,1})=(@inbounds for i=1:length(p);reset_t!(p[i]);end;nothing)

reset_t!(p::Puddle)=(p.i=1; nothing)


function DSSTSolarRadiationPressure(arg0::ExtendedPVCoordinatesProvider, arg1::jdouble, arg2::RadiationSensitive, arg3::jdouble)
    return DSSTSolarRadiationPressure((ExtendedPVCoordinatesProvider, jdouble, RadiationSensitive, jdouble), arg0, arg1, arg2, arg3)
end

function DSSTSolarRadiationPressure(arg0::jdouble, arg1::jdouble, arg2::ExtendedPVCoordinatesProvider, arg3::jdouble, arg4::RadiationSensitive, arg5::jdouble)
    return DSSTSolarRadiationPressure((jdouble, jdouble, ExtendedPVCoordinatesProvider, jdouble, RadiationSensitive, jdouble), arg0, arg1, arg2, arg3, arg4, arg5)
end

function DSSTSolarRadiationPressure(arg0::jdouble, arg1::jdouble, arg2::ExtendedPVCoordinatesProvider, arg3::jdouble, arg4::jdouble)
    return DSSTSolarRadiationPressure((jdouble, jdouble, ExtendedPVCoordinatesProvider, jdouble, jdouble), arg0, arg1, arg2, arg3, arg4)
end

function DSSTSolarRadiationPressure(arg0::jdouble, arg1::jdouble, arg2::jdouble, arg3::jdouble, arg4::ExtendedPVCoordinatesProvider, arg5::jdouble, arg6::jdouble)
    return DSSTSolarRadiationPressure((jdouble, jdouble, jdouble, jdouble, ExtendedPVCoordinatesProvider, jdouble, jdouble), arg0, arg1, arg2, arg3, arg4, arg5, arg6)
end

function get_equatorial_radius(obj::DSSTSolarRadiationPressure)
    return jcall(obj, "getEquatorialRadius", jdouble, ())
end

function get_events_detectors(obj::DSSTSolarRadiationPressure)
    return jcall(obj, "getEventsDetectors", Vector{EventDetector}, ())
end

function get_field_events_detectors(obj::DSSTSolarRadiationPressure, arg0::Field)
    return jcall(obj, "getFieldEventsDetectors", Vector{FieldEventDetector}, (Field,), arg0)
end

function get_spacecraft(obj::DSSTSolarRadiationPressure)
    return jcall(obj, "getSpacecraft", RadiationSensitive, ())
end


export zero_cost_transitions, transition_costs, gold_scores
using Flux

function zero_cost_transitions(state::GoldState)
  filter(trans -> is_valid(state.config, trans.move) && cost(state, trans.move, trans.label) <= 0, state.system.transitions)
end

# I inverse cost& because I need to zero cost transitions have best value after softmax
function transition_costs(state::GoldState)
  map(state.system.transitions) do trans
    is_valid(state.config, trans.move) ? cost(state, trans.move, trans.label) : FORBIDDEN_COST
  end
end

function gold_scores(costs::Vector{Int64})
  map(costs) do cost
    cost <= 0 ? 1 : 0
  end |> vec -> reshape(vec, 1, :)
end

function weight_mask(costs)
  shift_weights = [1, 1, 2]
  other_weights = [[2, 1, 2] for i in 1:(length(costs) - 1)]

  hcat(shift_weights, other_weights...)
end


export init, train

"abstract Hyperparameters"
abstract type AbstractHyperparam end;

"Abstract Model Data Object"
abstract type AbstractModelData end;

"Abstract Model Buffer Object"
abstract type AbstractModelBuffer end;

abstract type ModelType end;

abstract type InitialisationType end;

abstract type PosteriorInference end;

function init(X, model::ModelType, init::InitialisationType)
  throw(ErrorException("Initialisation $(typeof(init)) for $(typeof(model)) using $(typeof(X)) is not available."))
end

function extractpointestimate(B::AbstractModelBuffer)
  throw(ErrorException("No point estimate available for $(typeof(B))."))
end

function sampleparameters!(B::AbstractModelBuffer, P::AbstractHyperparam)
  throw(ErrorException("Parameter sampling for $(typeof(B)) using $(typeof(P)) is not available."))
end

function gibbs!(B::AbstractModelBuffer)
  throw(ErrorException("Gibbs sampling for $(typeof(B)) is not available."))
end

function slicesampling!(B::AbstractModelBuffer)
  throw(ErrorException("Slice sampling for $(typeof(B)) is not available."))
end

function variationalbayes!(B::AbstractModelBuffer)
  throw(ErrorException("Variational inference for $(typeof(B)) is not available."))
end

function train(B::AbstractModelBuffer, P::AbstractHyperparam, I::PosteriorInference)
  throw(ErrorException("Posterior inference for $(typeof(B)) with $(typeof(P)) and $(typeof(I)) is not available."))
end


module Transformers

using Flux
using Requires
using Requires: @init

export Transformer, TransformerDecoder
export Stack, stack, @nntopo_str, @nntopo, show_stackfunc

export dataset, datafile, get_batch, get_vocab

export todevice
export Gpt, load_gpt_pretrain, lmloss

const Abstract3DTensor{T} = AbstractArray{T, 3}
const Container{T} = Union{NTuple{N, T}, Vector{T}} where N

"move data to device, when CuArrays is loaded, basically = `CuArrays.cu` except `AbstractArray{Int}` become `CuArray{Int}`"
todevice(x) = x
todevice(x, xs...) = (x, xs...)

@init @require CuArrays="3a865a2d-5b23-5a0f-bc46-62713ec82fae" begin
    import .CuArrays

    "move data to device, basically = `CuArrays.cu` except `AbstractArray{Int}` become `CuArray{Int}`"
    todevice(x, xs...) = (todevice(x), todevice.(xs)...)
    todevice(x::AbstractArray{Int}) = CuArray(x)
    todevice(x) = CuArrays.cu(x)
end

#implement batchmul for flux
include("./fix/batchedmul.jl")

#implement of gelu for gpu
include("./fix/gelu.jl")

#dropout noise shape impl
include("./fix/dropout.jl")

include("./basic/Basic.jl")
include("./datasets/Datasets.jl")

include("./gpt/GenerativePreTrain.jl")

using .Basic
using .Datasets
using .GenerativePreTrain

end # module


@testset "reported issues" begin
    @testset "plot hang (tan → ∞) (#129)" begin
        p = lineplot([cos, sin, tan], -π/2, 2π)
        test_ref("references/issues/cos_sin_tan.txt", @show_col(p))
    end

    @testset "integer edges (#139)" begin
        p_int = histogram(fit(Histogram, rand(10), 0:3))
        p_float = histogram(fit(Histogram, rand(10), 0.0:1.0:3.0))
        @test @print_col(p_int) == @print_col(p_float)
    end
end


npics     = 6000 # number of pics to use for traning
newdim    = 416 # new size for pic upscaling; if you modify this you need to modify the network
# Load MNIST data:
include(Knet.dir("data","mnist.jl"))
# load raw MNIST: 60_000 pics
_mnist_xtrn,_mnist_ytrn,_mnist_xtst,_mnist_ytst = mnist()
# Select only a few pics
_mnist_xtrn = _mnist_xtrn[:,:,:,1:npics]
_mnist_ytrn = _mnist_ytrn[1:npics]
_mnist_xtst = _mnist_xtst[:,:,:,1:npics]
_mnist_ytst = _mnist_ytst[1:npics]
# Hacky upscale using Images.jl
_mnist_xtrn = imresize(_mnist_xtrn[:,:,1,:],newdim,newdim)
_mnist_xtrn = reshape(_mnist_xtrn,newdim,newdim,1,npics)
_mnist_xtst = imresize(_mnist_xtst[:,:,1,:],newdim,newdim)
_mnist_xtst = reshape(_mnist_xtst,newdim,newdim,1,npics)


using AMDGPU, Test

function vadd!(c, a, b)
    i = workitemIdx().x
    c[i] = a[i] + b[i]
    return
end

function test_amdgpu()
    # cpu version
    N     = 32
    a     = rand(Float64, N)
    b     = rand(Float64, N)
    c_cpu = a + b
    # AMD gpu version
    a_d   = ROCArray(a)
    b_d   = ROCArray(b)
    c_d   = similar(a_d)
    # run gpu kernel
    wait(@roc groupsize=N vadd!(c_d, a_d, b_d))
    # gather
    c     = Array(c_d)
    # test
    return @test isapprox(c, c_cpu)
end

test_amdgpu()


using Test
using PandaModels; const _PdM = PandaModels
import PowerModels; const _PM = PowerModels

_PM.silence()

pdm_path = joinpath(dirname(pathof(PandaModels)), "..")
data_path = joinpath(pdm_path, "test", "data")

case_pm =  joinpath(data_path, "test_pm.json")
case_pf = joinpath(data_path, "test_pf.json")
case_opf = joinpath(data_path, "test_opf.json")
case_custom = joinpath(data_path, "test_custom.json")
case_ots = joinpath(data_path, "test_ots.json")
case_tnep = joinpath(data_path, "test_tnep.json")

@testset "PandaModels.jl" begin
        @testset "test internal functions" begin
                pm = _PdM.load_pm_from_json(case_pm)

                @test length(pm["bus"]) == 6
                @test length(pm["gen"]) == 3
                @test length(pm["branch"]) == 7
                @test length(pm["load"]) == 3

                model =_PdM.get_model(pm["pm_model"])
                @test string(model) == "PowerModels.DCPPowerModel"

                solver = _PdM.get_solver(pm["pm_solver"], pm["pm_nl_solver"], pm["pm_mip_solver"],
                pm["pm_log_level"], pm["pm_time_limit"], pm["pm_nl_time_limit"], pm["pm_mip_time_limit"])

                @test string(solver.optimizer_constructor) == "Ipopt.Optimizer"

        end

        @testset "test exported executive functions" begin
                @testset "test for run_powermodels" begin
                        result=run_powermodels_opf(case_opf)
                        @test isa(result, Dict{String,Any})
                        @test string(result["termination_status"]) == "LOCALLY_SOLVED"
                        @test isapprox(result["objective"], 144.85; atol = 1e0)
                        @test result["solve_time"] > 0.0
                end
                @testset "test for run_powermodels_powerflow" begin
                        result=run_powermodels_powerflow(case_pf)
                        @test isa(result, Dict{String,Any})
                        @test string(result["termination_status"]) == "LOCALLY_SOLVED"
                        @test isapprox(result["objective"], 0.0; atol = 1e0)
                        @test result["solve_time"] > 0.0
                end
                @testset "test for powermodels_custom" begin
                        result=run_powermodels_custom(case_custom)
                        @test isa(result, Dict{String,Any})
                        @test string(result["termination_status"]) == "LOCALLY_SOLVED"
                        @test isapprox(result["objective"], 144.85; atol = 1e0)
                        @test result["solve_time"] > 0.0
                end
                @testset "test for powermodels_tnep" begin
                        result=run_powermodels_tnep(case_tnep)
                        @test isa(result, Dict{String,Any})
                        @test string(result["termination_status"]) == "LOCALLY_SOLVED"
                        @test isapprox(result["objective"], 0.0; atol = 1e0)
                        @test result["solve_time"] > 0.0
                end

                @testset "test for powermodels_ots" begin
                        result=run_powermodels_ots(case_ots)
                        @test isa(result, Dict{String,Any})
                        @test string(result["termination_status"]) == "LOCALLY_SOLVED"
                        @test isapprox(result["objective"], 14810.0; atol = 100.0)
                        @test result["solve_time"] > 0.0
                end
        end
end


using StaticArrays: SA

const atomic_symbols = SA["H","He","Li","Be","B","C","N","O","F","Ne",
        "Na","Mg","Al","Si","P","S","Cl","Ar","K","Ca",
        "Sc","Ti","V","Cr","Mn","Fe","Co","Ni","Cu","Zn",
        "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y","Zr",
        "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",
        "Sb","Te","I","Xe","Cs","Ba","La","Ce","Pr","Nd",
        "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",
        "Lu","Hf","Ta","W","Re","Os","Ir","Pt","Au","Hg",
        "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",
        "Pa","U"] #,"Np","Pu","Am"] # we don't have partition funcs for these last three

const Natoms = UInt8(length(atomic_symbols))
const atomic_numbers = Dict(atomic_symbols .=> UInt8.(1:Natoms))

#in grams
const atomic_masses = SA[
     1.008,4.003,6.941,9.012,10.81,12.01,14.01,16.00,19.00,20.18,
     22.99,24.31,26.98,28.08,30.97,32.06,35.45,39.95,39.10,40.08,
     44.96,47.90,50.94,52.00,54.94,55.85,58.93,58.71,63.55,65.37,
     69.72,72.59,74.92,78.96,79.90,83.80,85.47,87.62,88.91,91.22,
     92.91,95.94,98.91,101.1,102.9,106.4,107.9,112.4,114.8,118.7,
     121.8,127.6,126.9,131.3,132.9,137.3,138.9,140.1,140.9,144.2,
     145.0,150.4,152.0,157.3,158.9,162.5,164.9,167.3,168.9,173.0,
     175.0,178.5,181.0,183.9,186.2,190.2,192.2,195.1,197.0,200.6,
     204.4,207.2,209.0,210.0,210.0,222.0,223.0,226.0,227.0,232.0,
     231.0,238.0].* amu_cgs #,237.0,244.0,243.0]

#solar/meteoritic abundances per Asplund et al. (2009, Ann. Rev. Ast. Ap., 47, 481).
const solar_abundances = SA[
      12.00,10.93, 1.05, 1.38, 2.70, 8.43, 7.83, 8.69, 4.56, 7.93,
      6.24, 7.60, 6.45, 7.51, 5.41, 7.12, 5.50, 6.40, 5.03, 6.34,
      3.15, 4.95, 3.93, 5.64, 5.43, 7.50, 4.99, 6.22, 4.19, 4.56,
      3.04, 3.65, 2.30, 3.34, 2.54, 3.25, 2.52, 2.87, 2.21, 2.58,
      1.46, 1.88,-5.00, 1.75, 0.91, 1.57, 0.94, 1.71, 0.80, 2.04,
      1.01, 2.18, 1.55, 2.24, 1.08, 2.18, 1.10, 1.58, 0.72, 1.42,
     -5.00, 0.96, 0.52, 1.07, 0.30, 1.10, 0.48, 0.92, 0.10, 0.84,
      0.10, 0.85,-0.12, 0.85, 0.26, 1.40, 1.38, 1.62, 0.92, 1.17,
      0.90, 1.75, 0.65,-5.00,-5.00,-5.00,-5.00,-5.00,-5.00, 0.02,
      -5.00,-0.54]#,-5.00,-5.00,-5.00]


"""
    AbstractConstraints{T}

Abstract type for all possible constructs.
"""
abstract type AbstractConstraints{T} end

"""
    ElementConstraints{T}

Abstract Constraints with effect on the element that are allow in the constructs.
"""
abstract type ElementConstraints{T} <: AbstractConstraints{T} end

"""
    NoConstraint{T}

If no constraint is given.
"""
struct NoConstraint{T} <: ElementConstraints{T}
    con :: T
end

"""
    ConstructConstraints{T}

 If a particular construct is allowed is checked on the construct level,
 using fitler that block all unwanted constructs.
 This implies that first all constructed are generated and afterwards filterd,
 which is not efficient for large space with a limited number of constraints.
`filterconstrain` than filters the removes the unwanted constructs
 For more information see [`filterconstrain`](@ref)
"""
abstract type ConstructConstraints{T} <: ElementConstraints{T} end

"""
    SingleConstructConstraints{T}

 Type used to as input for a single constraint:
 two types are currently implemented:
 `UnOrderedConstraint` and `OrderedConstraint`.
"""
abstract type SingleConstructConstraints{T} <: ConstructConstraints{T} end

"""
    UnOrderedConstraint{T<:Mod}

an unorderedConstraint is a group of modules that cannot co-occur in one construct.
These group is given as:  ``Array{T,1} where T <: Mod``
This constraint is unorder because the location of these models in the constructed
is not considered

For more information see [`filterconstrain`]@ref
"""
struct UnOrderedConstraint{T<:Mod} <: SingleConstructConstraints{T}
    combination::Array{T}
end

make_unorderconstraint(c::Array) = UnOrderedConstraint([Mod(m) for m in c])

"""
    OrderedConstraint{T<:Mod}

Order Constraints a group of modules that can not co-occur in one construct if
these modules are in a specific position.
This type has two inputs both vector of the same length
The first vector contains the indexes,
the second contains the forbidden module at the correspoding possiton
The contraint is order because the location of these modules
in the constructed is used in the
evaluation of the constraint.

The corresponding positions are ``Array{::Int}``, these are the forbidden indexes for the corresponding module.
These modules are given as ``Array{T} where T <: Mod``

For more information see [`filterconstraint`]@ref.
or read the documentation regareding the use of constraints.

"""
struct OrderedConstraint{T<:Mod} <: SingleConstructConstraints{T}
    pos::Array{Int}
    combination::Array{T}
end

make_orderconstraint(pos::Array{Int},c::Array) = OrderedConstraint(pos,[Mod(m) for m in c])

"""
   ComposedConstructConstraints{T}

Constraint that are applicable on a generated construct.
These can only be used in a filter that is not very efficient for large design
spaces.
"""
struct ComposedConstructConstraints{T <: SingleConstructConstraints} <: ConstructConstraints{T}
    constructcon :: Array{T}
end

Base.eltype(K::ComposedConstructConstraints{T}) where {T} = T

# concentration of multiple Constraints can be done using + sign

"""
    +(con1::SingleConstructConstraints,con2::SingleConstructConstraints)

Returns a `ComposedConstructConstraints` containing both Constraints `con1` and `con2`.
"""
Base.:+(con1::SingleConstructConstraints,con2::SingleConstructConstraints) = ComposedConstructConstraints([con1,con2])

"""
    Base.:+(con1::ComposedConstructConstraints{T} where T, con2::N where N <: SingleConstructConstraints)

Returns a `ComposedConstructConstraints` containing where `con2` is add to the `con1`
"""

Base.:+(con1::ComposedConstructConstraints{T} where T, con2::N where N <: SingleConstructConstraints) = ComposedConstructConstraints([con1.constructcon;con2])

"""
    Base.:+(con1::SingleConstructConstraints,con2::ComposedConstructConstraints)

Returns a `ComposedConstructConstraints` containing where `con1` is add to the `con2`
"""
Base.:+(con1::SingleConstructConstraints,con2::ComposedConstructConstraints) = +(con2,con1)

"""
    Base.:+(con1::ComposedConstructConstraints{T} where T, con2::ComposedConstructConstraints{N} where N)

Returns a `ComposedConstructConstraints` concatenation of `con1` and `con2`
"""
Base.:+(con1::ComposedConstructConstraints{T} where T, con2::ComposedConstructConstraints{N} where N) = ComposedConstructConstraints([con1.constructcon;con2.constructcon;])

##################################
# Code below has currently no application in the package, this will probably not change shortly
# The idea is that for some constraints, we can prevent that unwanted constructs are generated, instead of generational filtering afterwards.
# To allow this a special type was constructed and see how it good be implemented
# No need to check,
#################################

#=
"""
space contrains has effect on how the space is repesanted without real evaluation of the constructs
"""

abstract type Space_Constraints{T} <: ElementConstraints{T} end

"""
Single possition constrain. If allow = 1 it indicaces that only these construcst are allow.
if allow = 0is false thes models aren't allowed on this possition, for non position specific constrain pos = 0
"""

struct Compose_Space_Constrain{Ts <: Space_Constraints{T} where T} <: Space_Constraints{Ts}
    space_con::AbstractArray{Ts}
end

abstract type Single_Space_Constraints{T} <: Space_Constraints{T} end

struct Possition_Constrain{T} <: Single_Space_Constraints{T}
    pos::Int
    mod::T
end

# add two  space Constraints
Base.:+(con1::N  ,con2::N ) where N <: Single_Space_Constraints{T} where T = Compose_Space_Constrain([con1,con2])
# add to compose Constraintse
Base.:+(con1::Compose_Space_Constrain,con2::Single_Space_Constraints) = Compose_Space_Constrain([con1.space_con;con2])
Base.:+(con1::Single_Space_Constraints,con2::Compose_Space_Constrain) = Compose_Space_Constrain([con2.space_con;con1])

Base.:+(con1::Compose_Space_Constrain,con2::Compose_Space_Constrain)  = Compose_space_Constraints([con1.space_con;con2.space_con])

Base.:+(con1::Element_Constraints,con2::Element_Constraints...) = +(con1,+(con2...))
"""
Group all elementConstraints
"""

struct Multipe_constrain{T<:Any, Ts<:Space_Constraints, Tc<:Construct_Constraints}  <: Element_Constraints{T}
    space_con:: Ts
    construct_con:: Tc
    function Multipe_constrain(space_con::Space_Constraints{T}, construct_con::Construct_Constraints{V}) where {T, V}
        return new{promote_type(T, V),typeof(space_con),typeof(construct_con)}(space_con,construct_con)
    end
end

Base.:+(con1:: Space_Constraints, con2:: Construct_Constraints) = Multipe_constrain(con1,con2)
Base.:+(con1:: Construct_Constraints, con2:: Space_Constraints) = Multipe_constrain(con2,con1)

Base.:+(con1::Multipe_constrain,con2::Construct_Constraints) = Multipe_constrain(con1.space_con, +(con1.construct_con,con2))
Base.:+(con1::Multipe_constrain,con2::Space_Constraints) = Multipe_constrain(+(con1.space_con,con2),con1.construct_con)

Base.:+(con1::Construct_Constraints,con2::Multipe_constrain) = Multipe_constrain(con2,con1)
Base.:+(con1::Space_Constraints,con2::Multipe_constrain) =  Multipe_constrain(con2,con1)

Base.:+(con1::Multipe_constrain,con2::Multipe_constrain) =  Multipe_constrain(con1.space_con + con2.space_con, con1.construct_con + con2.construct_con)

# fucntion(con1::Multipe_constrain,con2::Space_Constraints)

"""
Group length and Constraints to one object
"""
abstract type Full_Constrain{T} <: AbstractConstraints{T} end

"""
Gives an specific lenth to given Constraints
"""

struct Len_Constrain{T} <: Full_Constrain{T}
    len::Int
    el_con::Element_Constraints{T}
end

struct Group_Len_Constrain{T} <: Full_Constrain{T}
    group::Tuple{T}
end

group_len_constrain(con1::Len_Constrain,con2::Len_Constrain) = Group_Len_Constrain(Tuple(con1,con2))
group_len_constrain(con1::Len_Constrain,con2::Len_Constrain...) = Group_Len_Constrain(Tuple(con1,Tuple(con2...)))
=#


#tmp=readlines("ECCO_standard_Project.toml")[2:end];
#tmp=[split(i," = ")[1] for i in tmp];
#[Pkg.add(i) for i in tmp]

#import MeshArrays
#MeshArrays.GRID_LLC90_download()

#mkdir("ECCO_diags")

@everywhere include("ECCO_pkg_grid_etc.jl")
@everywhere include("ECCO_standard_analysis.jl")

@everywhere sol0="r2"
@everywhere sol="ECCOv4"*sol0*"_analysis"

pth0=joinpath("ECCO_diags",sol)
!isdir(pth0) ? mkdir(pth0) : nothing

@everywhere fil=joinpath("ECCO_diags",sol,"ECCO_standard_list.toml")
!isfile(fil) ? ECCO_standard_list_toml(fil) : nothing
@everywhere list0=TOML.parsefile(joinpath("ECCO_diags",sol,"ECCO_standard_list.toml"))

@everywhere pth0=joinpath("ECCO_diags",sol,"ECCO_transport_lines")
!isdir(pth0) ? ECCO_transport_lines(pth0) : nothing
@everywhere list_trsp,msk_trsp,ntr=reload_transport_lines(pth0)

for ff in 1:length(list0["kk"])
    save(joinpath("ECCO_diags",sol,"taskID.jld2"),"ID",ff)

    @sync @everywhere gg=load(joinpath("ECCO_diags",sol,"taskID.jld2"),"ID")

    @everywhere calc=list0["calc"][gg]
    @everywhere nam=list0["nam"][gg]
    @everywhere kk=list0["kk"][gg]

    @everywhere pth_in,pth_out,pth_tmp,nt,list_steps=ECCO_path_etc(sol0,calc,nam)
    !isdir(pth_tmp) ? mkdir(pth_tmp) : nothing

    main_function(calc,sol,nam,kk)

end


module MicroHH

## Export types and functions.
export Model
export prepare_model!, step_model!, save_model, load_model!

## Load packages.
using LoopVectorization
using Printf
using HDF5

## Include the necessary files.
include("StencilBuilder.jl")
include("Grid.jl")
include("Fields.jl")
include("Boundary.jl")
include("Timeloop.jl")
include("Dynamics.jl")
include("Pressure.jl")
include("Diagnostics.jl")
include("MultiDomain.jl")

## Global model data.
struct Model{TF <: Union{Float32, Float64}}
    name::String
    n_domains::Int
    last_measured_time::Ref{UInt64}

    grid::Vector{Grid}
    fields::Vector{Fields}
    boundary::Vector{Boundary}
    timeloop::Vector{Timeloop}
    pressure::Vector{Pressure}
    multidomain::Vector{MultiDomain}
end

function Model(name, n_domains, settings, TF)
    m = Model{TF}(name, n_domains, 0, [], [], [], [], [], [])
    for i in 1:n_domains
        push!(m.grid, Grid(settings[i]["grid"], TF))
        push!(m.fields, Fields(m.grid[i], settings[i]["fields"], TF))
        push!(m.boundary, Boundary(settings[i]["boundary"]))
        push!(m.timeloop, Timeloop(settings[i]["timeloop"]))
        push!(m.pressure, Pressure(m.grid[i], TF))
        push!(m.multidomain, MultiDomain(m.grid[i], settings[i]["multidomain"], TF))
    end

    return m
end

function calc_rhs!(m::Model, i)
    set_boundary!(m.fields[i], m.grid[i], m.boundary[i])
    calc_dynamics_tend!(m.fields[i], m.grid[i])
    calc_nudge_tend!(m.fields[i], m.grid[i], m.multidomain[i])
    calc_pressure_tend!(m.fields[i], m.grid[i], m.timeloop[i], m.pressure[i])
end

function prepare_model!(m::Model)
    # Prevent slow single precision performance due to subnormals.
    set_zero_subnormals(true)

    m.last_measured_time[] = time_ns()

    for i in 1:m.n_domains
        calc_rhs!(m, i)
    end

    check_model(m)

    return is_in_progress(m.timeloop[1])
end

function save_domain(m::Model, i)
    f = m.fields[i]
    g = m.grid[i]
    t = m.timeloop[i]
    b = m.boundary[i]

    # Update the boundary.
    set_boundary!(f, g, b)

    filename = @sprintf("%s.%02i.%08i.h5", m.name, i, round(t.time))
    h5open(filename, "w") do fid
        # Save the grid.
        write(fid, "x" , g.x[g.is:g.ie])
        write(fid, "xh", g.xh[g.is:g.ie])
        write(fid, "y" , g.y[g.js:g.je])
        write(fid, "yh", g.yh[g.js:g.je])
        write(fid, "z" , g.z[g.ks:g.ke])
        write(fid, "zh", g.zh[g.ks:g.keh])

        # Make the grid variables dimensions.
        HDF5.h5ds_set_scale(fid["x" ], "x" )
        HDF5.h5ds_set_scale(fid["xh"], "xh")
        HDF5.h5ds_set_scale(fid["y" ], "y" )
        HDF5.h5ds_set_scale(fid["yh"], "yh")
        HDF5.h5ds_set_scale(fid["z" ], "z" )
        HDF5.h5ds_set_scale(fid["zh"], "zh")

        # Save the fields.
        write(fid, "u", f.u[g.is:g.ie, g.js:g.je, g.ks:g.ke ])
        write(fid, "v", f.v[g.is:g.ie, g.js:g.je, g.ks:g.ke ])
        write(fid, "w", f.w[g.is:g.ie, g.js:g.je, g.ks:g.keh])
        write(fid, "s", f.s[g.is:g.ie, g.js:g.je, g.ks:g.ke ])
        write(fid, "s_bot", f.s_bot[g.is:g.ie, g.js:g.je])
        write(fid, "s_top", f.s_top[g.is:g.ie, g.js:g.je])
        write(fid, "s_gradbot", f.s_gradbot[g.is:g.ie, g.js:g.je])
        write(fid, "s_gradtop", f.s_gradtop[g.is:g.ie, g.js:g.je])

        # Attach the dimensions. Note the c-indexing.
        HDF5.h5ds_attach_scale(fid["u"], fid["xh"], 2)
        HDF5.h5ds_attach_scale(fid["u"], fid["y"], 1)
        HDF5.h5ds_attach_scale(fid["u"], fid["z"], 0)

        HDF5.h5ds_attach_scale(fid["v"], fid["x"], 2)
        HDF5.h5ds_attach_scale(fid["v"], fid["yh"], 1)
        HDF5.h5ds_attach_scale(fid["v"], fid["z"], 0)

        HDF5.h5ds_attach_scale(fid["w"], fid["x"], 2)
        HDF5.h5ds_attach_scale(fid["w"], fid["y"], 1)
        HDF5.h5ds_attach_scale(fid["w"], fid["zh"], 0)

        HDF5.h5ds_attach_scale(fid["s"], fid["x"], 2)
        HDF5.h5ds_attach_scale(fid["s"], fid["y"], 1)
        HDF5.h5ds_attach_scale(fid["s"], fid["z"], 0)

        HDF5.h5ds_attach_scale(fid["s_bot"], fid["x"], 1)
        HDF5.h5ds_attach_scale(fid["s_bot"], fid["y"], 0)

        HDF5.h5ds_attach_scale(fid["s_top"], fid["x"], 1)
        HDF5.h5ds_attach_scale(fid["s_top"], fid["y"], 0)

        HDF5.h5ds_attach_scale(fid["s_gradbot"], fid["x"], 1)
        HDF5.h5ds_attach_scale(fid["s_gradbot"], fid["y"], 0)

        HDF5.h5ds_attach_scale(fid["s_gradtop"], fid["x"], 1)
        HDF5.h5ds_attach_scale(fid["s_gradtop"], fid["y"], 0)
    end
end

function save_model(m::Model)
    for i in 1:m.n_domains
        save_domain(m, i)
    end
end

function load_domain!(m::Model, i)
    f = m.fields[i]
    g = m.grid[i]
    t = m.timeloop[i]
    b = m.boundary[i]

    filename = @sprintf("%s.%02i.%08i.h5", m.name, i, round(t.time))
    h5open(filename, "r") do fid
        f.u[g.is:g.ie, g.js:g.je, g.ks:g.ke ] = read(fid, "u")
        f.v[g.is:g.ie, g.js:g.je, g.ks:g.ke ] = read(fid, "v")
        f.w[g.is:g.ie, g.js:g.je, g.ks:g.keh] = read(fid, "w")
        f.s[g.is:g.ie, g.js:g.je, g.ks:g.ke ] = read(fid, "s")
        f.s_bot[g.is:g.ie, g.js:g.je] = read(fid, "s_bot")
        f.s_top[g.is:g.ie, g.js:g.je] = read(fid, "s_top")
        f.s_gradbot[g.is:g.ie, g.js:g.je] = read(fid, "s_gradbot")
        f.s_gradtop[g.is:g.ie, g.js:g.je] = read(fid, "s_gradtop")
    end

    set_boundary!(f, g, b)
end

function load_model!(m::Model)
    for i in 1:m.n_domains
        load_domain!(m, i)
    end
end

function step_model!(m::Model)
    # Prevent slow single precision performance due to subnormals.
    set_zero_subnormals(true)

    itime_next = m.timeloop[1].itime + m.timeloop[1].idt

    for i in 1:m.n_domains
        # Compute the nudging fields, which remain constant throughout the step.
        if i > 1
            calc_nudge_fields!(m.multidomain[i], m.fields[i], m.fields[i-1], m.grid[i], m.grid[i-1])
        end

        while (m.timeloop[i].itime < itime_next)
            integrate_time!(m.fields[i], m.grid[i], m.timeloop[i])
            step_time!(m.timeloop[i])

            if is_save_time(m.timeloop[i])
                save_domain(m, i)
            end

            calc_rhs!(m, i)
        end
    end

    if is_check_time(m.timeloop[1])
        check_model(m)
    end

    return is_in_progress(m.timeloop[1])
end

function check_model(m::Model)
    # Calculate the time since the last check.
    old_time = m.last_measured_time[]
    m.last_measured_time[] = time_ns()

    # First, print the model time and the wall clock since last sample.
    status_string = @sprintf("(%11.2f) Time = %8.3f",
        m.timeloop[1].time,
        (m.last_measured_time[] - old_time) * 1e-9)
    println(status_string)

    # Second, compute the divergence and CFL for each domain.
    for i in 1:m.n_domains
        status_string = @sprintf("  (%02i) Div = %6.3E, CFL = %6.3f, subnormals = %i",
            i,
            calc_divergence(m.fields[i], m.grid[i]),
            calc_cfl(m.fields[i], m.grid[i], m.timeloop[i]),
            sum(issubnormal.(m.fields[i].u)) + sum(issubnormal.(m.fields[i].v)) + sum(issubnormal.(m.fields[i].w)))
        println(status_string)
    end
end

end


#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct LCFilter <: Filter
        lf::Float64
        rf::Float64
        cf::Float64
        ext::Dict{String, Any}
        states::Vector{Symbol}
        n_states::Int64
    end

Parameters of a LCL filter outside the converter

# Arguments
- `lf::Float64`: filter inductance, validation range: (0, nothing)
- `rf::Float64`: filter resistance, validation range: (0, nothing)
- `cf::Float64`: filter capacitance, validation range: (0, nothing)
- `ext::Dict{String, Any}`
- `states::Vector{Symbol}`
- `n_states::Int64`
"""
mutable struct LCFilter <: Filter
    "filter inductance"
    lf::Float64
    "filter resistance"
    rf::Float64
    "filter capacitance"
    cf::Float64
    ext::Dict{String, Any}
    states::Vector{Symbol}
    n_states::Int64
end

function LCFilter(lf, rf, cf, ext=Dict{String, Any}(), )
    LCFilter(lf, rf, cf, ext, [:id_o, :iq_o], 4, )
end

function LCFilter(; lf, rf, cf, ext=Dict{String, Any}(), )
    LCFilter(lf, rf, cf, ext, )
end

# Constructor for demo purposes; non-functional.
function LCFilter(::Nothing)
    LCFilter(;
        lf=0,
        rf=0,
        cf=0,
        ext=Dict{String, Any}(),
    )
end

"""Get LCFilter lf."""
get_lf(value::LCFilter) = value.lf
"""Get LCFilter rf."""
get_rf(value::LCFilter) = value.rf
"""Get LCFilter cf."""
get_cf(value::LCFilter) = value.cf
"""Get LCFilter ext."""
get_ext(value::LCFilter) = value.ext
"""Get LCFilter states."""
get_states(value::LCFilter) = value.states
"""Get LCFilter n_states."""
get_n_states(value::LCFilter) = value.n_states

"""Set LCFilter lf."""
set_lf!(value::LCFilter, val::Float64) = value.lf = val
"""Set LCFilter rf."""
set_rf!(value::LCFilter, val::Float64) = value.rf = val
"""Set LCFilter cf."""
set_cf!(value::LCFilter, val::Float64) = value.cf = val
"""Set LCFilter ext."""
set_ext!(value::LCFilter, val::Dict{String, Any}) = value.ext = val
"""Set LCFilter states."""
set_states!(value::LCFilter, val::Vector{Symbol}) = value.states = val
"""Set LCFilter n_states."""
set_n_states!(value::LCFilter, val::Int64) = value.n_states = val