using Parameters
using ModelParameters

abstract type IHACRESNode <: NetworkNode end

Base.@kwdef mutable struct BilinearNode{P, A<:Real} <: IHACRESNode
    @network_node

    # https://wiki.ewater.org.au/display/SD41/IHACRES-CMD+-+SRG
    d::P = Param(200.0, bounds=(10.0, 550.0))  # flow threshold
    d2::P = Param(2.0, bounds=(0.0001, 10.0))   # flow threshold2
    e::P = Param(1.0, bounds=(0.1, 1.5))  # temperature to PET conversion factor
    f::P = Param(0.8, bounds=(0.01, 3.0))  # plant stress threshold factor (multiplicative factor of d)
    a::P = Param(0.9, bounds=(0.1, 10.0))  # quickflow storage coefficient == (1/tau_q)
    b::P = Param(0.1, bounds=(1e-3, 0.1))  # slowflow storage coefficent == (1/tau_s)

    # These changes require ihacres_nim to be changed as well...
    # a::A = Param(0.9, bounds=(0.1, 10.0))  # quickflow storage coefficient == exp(-1/tau_q)
    # b::A = Param(0.1, bounds=(10.0, 1000.0))  # slowflow storage coefficent == exp(-1/tau_s)
    # storage_threshold::A = Param(10.0, ...)  # optional threshold controlling bounds between `a` and `b`
    storage_coef::P = Param(2.9, bounds=(1e-10, 10.0))
    alpha::P = Param(0.1, bounds=(1e-5, 1 - 1/10^9))

    level_params::Array{P, 1} = [
        Param(-0.01, bounds=(-10.0, -0.01)),  # p1
        Param(0.8, bounds=(0.0, 1.5)),  # p2
        Param(4.5, bounds=(0.0, 20.0)), # p3
        Param(5.0, bounds=(1.0, 10.0)), # p4
        Param(0.35, bounds=(0.0, 1.0)), # p5
        Param(1.41, bounds=(-2.0, 2.0)), # p6
        Param(-1.45, bounds=(-2.5, 0.0)), # p7
        Param(6.75, bounds=(0.0, 10.0)), # p8
        Param(150.0, bounds=(50.0, 200.0)) # ctf
    ]

    storage::Array{A} = [100.0]
    quick_store::Array{A} = [0.0]
    slow_store::Array{A} = [0.0]
    outflow::Array{A} = []
    effective_rainfall::Array{A} = []
    et::Array{A} = []
    inflow::Array{A} = []
    level::Array{A} = []
    gw_store::Array{A} = [0.0]
end

# function prep_state!(node::IHACRESNode, sim_length::Int64)
#     ini_storage = node.storage[1]
#     node.storage = fill(0.0, sim_length+1)
#     node.storage[1] = ini_storage

#     ini_qs = node.quick_store[1]
#     node.quick_store = fill(0.0, sim_length+1)
#     node.quick_store[1] = ini_qs

#     ini_ss = node.slow_store[1]
#     node.slow_store = fill(0.0, sim_length+1)
#     node.slow_store[1] = ini_ss

#     node.outflow = fill(0.0, sim_length)
#     node.effective_rainfall = fill(0.0, sim_length)
#     node.et = fill(0.0, sim_length)
#     node.inflow = fill(0.0, sim_length)
#     node.level = fill(0.0, sim_length)

#     ini_gw = node.gw_store[1]
#     node.gw_store = fill(0.0, sim_length+1)
#     node.gw_store[1] = ini_gw
# end

function BilinearNode(name::String, spec::Dict)
    n = create_node(BilinearNode, name, spec["area"])
    node_params = copy(spec["parameters"])

    if haskey(spec, "level_params")
        n_lparams = n.level_params
        s_lparams = spec["level_params"]
        node_params["level_params"] = Param[
            Param(s_lparams[1], bounds=n_lparams[1].bounds)
            Param(s_lparams[2], bounds=n_lparams[2].bounds)
            Param(s_lparams[3], bounds=n_lparams[3].bounds)
            Param(s_lparams[4], bounds=n_lparams[4].bounds)
            Param(s_lparams[5], bounds=n_lparams[5].bounds)
            Param(s_lparams[6], bounds=n_lparams[6].bounds)
            Param(s_lparams[7], bounds=n_lparams[7].bounds)
            Param(s_lparams[8], bounds=n_lparams[8].bounds)
            Param(s_lparams[9], bounds=n_lparams[9].bounds)
        ]
    end

    node_params["storage"] = [node_params["initial_storage"]]
    delete!(node_params, "initial_storage")

    for (k, p) in node_params
        s = Symbol(k)
        if p isa String
            p = eval(Meta.parse(p))
        end

        try
            f = getfield(n, s)
            setfield!(n, s, Param(p, bounds=f.bounds))
        catch err
            msg = sprint(showerror, err, catch_backtrace())
            if occursin("no field bounds", string(msg))
                setfield!(n, s, p)
            else
                throw(err)
            end
        end
    end

    return n
end

"""
    BilinearNode(name::String, area::Float64, d::Float64, d2::Float64, e::Float64, f::Float64,
                a::Float64, b::Float64, s_coef::Float64, alpha::Float64,
                store::Float64, quick::Float64, slow::Float64, gw_store::Float64)

Create a IHACRES node that adopts the bilinear CMD module.
"""
function BilinearNode(name::String, area::Float64, d::Float64, d2::Float64, e::Float64, f::Float64,
                      a::Float64, b::Float64, s_coef::Float64, alpha::Float64,
                      store::Float64, quick::Float64, slow::Float64, gw_store::Float64)
    n = create_node(BilinearNode, name, area)

    update_params!(n, d, d2, e, f, a, b, s_coef, alpha)

    n.storage = [store]
    n.quick_store = [quick]
    n.slow_store = [slow]
    n.gw_store = [gw_store]

    return n
end

function update_state(s_node::IHACRESNode, storage::Float64, e_rainfall::Float64, et::Float64,
                      qflow_store::Float64, sflow_store::Float64, outflow::Float64,
                      level::Float64, gw_store::Float64)
    push!(s_node.storage, storage)
    push!(s_node.effective_rainfall, e_rainfall)
    push!(s_node.et, et)
    push!(s_node.outflow, outflow)

    push!(s_node.quick_store, qflow_store)
    push!(s_node.slow_store, sflow_store)
    push!(s_node.level, level)
    push!(s_node.gw_store, gw_store)
end

"""
    run_node!(node::IHACRESNode, climate::Climate, timestep::Int;
                  inflow=nothing, extraction=nothing, exchange=nothing)

Run a specific node for a specified time step.

# Arguments
- `node::IHACRESNode` :
- `climate::Climate` :
- `timestep::Int` : current time step
- `inflow::DataFrame` : Time series of inflows from any upstream node.
- `extraction::DataFrame` : Time series of water orders (expects column of `_releases`)
- `exchange::DataFrame` : Time series of groundwater flux
"""
function run_node!(node::IHACRESNode, climate::Climate, timestep::Int;
                   inflow=nothing, extraction=nothing, exchange=nothing)
    ts = timestep
    if checkbounds(Bool, node.outflow, ts)
        if node.outflow[ts] != undef
            # already ran for this time step so no need to run
            return node.outflow[ts], node.level[ts]
        end
    end

    node_name = node.name
    rain, et = climate_values(node, climate, ts)
    wo = timestep_value(ts, node_name, "releases", extraction)
    ex = timestep_value(ts, node_name, "exchange", exchange)
    in_flow = timestep_value(ts, node_name, "inflow", inflow)

    return run_timestep!(node, rain, et, ts; inflow=in_flow, extraction=wo, exchange=ex)
end

"""
    run_step!(s_node::BilinearNode,
              rain::Float64,
              evap::Float64,
              inflow::Float64,
              ext::Float64,
              gw_exchange::Float64;
              current_store::Union{Nothing, Float64}=nothing,
              quick_store::Union{Nothing, Float64}=nothing,
              slow_store::Union{Nothing, Float64}=nothing,
              gw_store::Union{Nothing, Float64}=nothing)::Tuple{Float64, Float64}

Run node with ET data to calculate outflow and update state.

# Arguments
- `s_node::BilinearNode` : IHACRESNode
- rain : rainfall for time step
- evap : evapotranspiration for time step
- inflow : inflow from previous node
- ext : irrigation and other water extractions
- gw_exchange : flux in ML where positive is contribution to stream, negative is infiltration
- current_store : replacement cmd state value (uses last known state if not provided)
- quick_store : replacement quick store state value
- slow_store : replacement slow store state value
- gw_store : replacement groundwater store state value

# Returns
- float, outflow from node [ML/day], stream level
"""
function run_step!(s_node::BilinearNode,
                    rain::Float64,
                    evap::Float64,
                    inflow::Float64,
                    ext::Float64,
                    gw_exchange::Float64;
                    current_store::Union{Nothing, Float64}=nothing,
                    quick_store::Union{Nothing, Float64}=nothing,
                    slow_store::Union{Nothing, Float64}=nothing,
                    gw_store::Union{Nothing, Float64}=nothing)::Tuple{Float64, Float64}

    if !isnothing(current_store)
        s_node.storage[end] = current_store
    end

    if !isnothing(quick_store)
        s_node.quick_store[end] = quick_store
    end

    if !isnothing(slow_store)
        s_node.slow_store[end] = slow_store
    end

    if !isnothing(gw_store)
        s_node.gw_store[end] = gw_store
    end

    current_store = s_node.storage[end]
    quick_store = s_node.quick_store[end]
    slow_store = s_node.slow_store[end]
    gw_store = s_node.gw_store[end]

    interim_results = [0.0, 0.0, 0.0]
    @ccall IHACRES.calc_ft_interim_cmd(interim_results::Ptr{Cdouble},
                                       current_store::Cdouble,
                                       rain::Cdouble,
                                       s_node.d::Cdouble,
                                       s_node.d2::Cdouble,
                                       s_node.alpha::Cdouble)::Cvoid

    (mf, e_rainfall, recharge) = interim_results

    et::Float64 = @ccall IHACRES.calc_ET(
        s_node.e::Cdouble,
        evap::Cdouble,
        mf::Cdouble,
        s_node.f::Cdouble,
        s_node.d::Cdouble
    )::Cdouble

    cmd::Float64 = @ccall IHACRES.calc_cmd(
        current_store::Cdouble,
        rain::Cdouble,
        et::Cdouble,
        e_rainfall::Cdouble,
        recharge::Cdouble
    )::Cdouble

    flow_results = [0.0, 0.0, 0.0]
    @ccall IHACRES.calc_ft_flows(
        flow_results::Ptr{Cdouble},
        quick_store::Cdouble,
        slow_store::Cdouble,
        e_rainfall::Cdouble,
        recharge::Cdouble,
        s_node.area::Cdouble,
        s_node.a::Cdouble,
        s_node.b::Cdouble
    )::Cvoid

    (nq_store, ns_store, outflow) = flow_results

    routing_res = [0.0, 0.0]
    @ccall IHACRES.routing(
        routing_res::Ptr{Cdouble},
        gw_store::Cdouble,
        s_node.storage_coef::Cdouble,
        inflow::Cdouble,
        outflow::Cdouble,
        ext::Cdouble,
        gw_exchange::Cdouble)::Cvoid

    (gw_store, outflow) = routing_res

    level_params = Array{Float64}(s_node.level_params)
    level::Float64 = @ccall IHACRES.calc_ft_level(
        outflow::Cdouble,
        level_params::Ptr{Cdouble}
    )::Cdouble

    push!(s_node.inflow, inflow)
    update_state(s_node, cmd, e_rainfall, et, nq_store, ns_store, outflow, level, gw_store)

    return outflow, level
end

"""
    run_timestep!(s_node::BilinearNode,
                  rain::Float64,
                  evap::Float64,
                  timestep::Union{Int64, Nothing};
                  inflow::Float64,
                  extraction::Float64,
                  exchange::Float64)::Tuple{Float64, Float64}

Run node for a given time step.
"""
function run_timestep!(s_node::BilinearNode,
                       rain::Float64,
                       evap::Float64,
                       timestep::Union{Int64, Nothing};
                       inflow::Float64=0.0,
                       extraction::Float64=0.0,
                       exchange::Float64=0.0)::Tuple{Float64, Float64}
    ts = timestep
    if !isnothing(ts)
        current_store = s_node.storage[ts]
        quick_store = s_node.quick_store[ts]
        slow_store = s_node.slow_store[ts]
        gw_store = s_node.gw_store[ts]
    else
        current_store = nothing
        quick_store = nothing
        slow_store = nothing
        gw_store = nothing
    end

    return run_step!(s_node, rain, evap, inflow, extraction, exchange;
                     current_store, quick_store, slow_store, gw_store)
end

"""
    param_info(node::IHACRESNode; with_level::Bool = true)::Tuple

Extract node parameter names, values, and bounds for IHACRESNode types.
"""
function param_info(node::IHACRESNode; with_level::Bool = false)::Tuple
    tmp = Model(node)
    values = collect(tmp[:val])
    bounds = collect(tmp[:bounds])
    param_names = collect(tmp[:fieldname])

    if with_level
        level_param_vals = map(x->x.val, node.level_params)
        append!(values, level_param_vals)

        level_param_bounds = map(x->x.bounds, node.level_params)
        append!(bounds, level_param_bounds)
    end

    return param_names, values, bounds
end

function run_node_with_temp!(sn::StreamfallNetwork, nid::Int64, climate::Climate;
                             inflow=nothing, extraction=nothing, exchange=nothing)
    node = sn[nid]
    timesteps = sim_length(climate)
    for ts in 1:timesteps
        run_node_with_temp!(node, climate, ts;
                            inflow=inflow, extraction=extraction, exchange=exchange)
    end

    return node.outflow, node.level
end

function run_node_with_temp!(node::BilinearNode, climate::Climate;
                             inflow=nothing, extraction=nothing, exchange=nothing)
    timesteps = sim_length(climate)
    for ts in 1:timesteps
        run_node_with_temp!(node, climate, ts;
                            inflow=inflow, extraction=extraction, exchange=exchange)
    end

    return node.outflow, node.level
end

function run_node_with_temp!(node::BilinearNode, climate::Climate, timestep::Int64;
                             inflow=nothing, extraction=nothing, exchange=nothing)
    ts = timestep
    P, T = climate_values(node, climate, ts)
    i = timestep_value(ts, node.name, "_inflow", inflow)
    ext = timestep_value(ts, node.name, "_extraction", extraction)
    flux = timestep_value(ts, node.name, "_exchange", exchange)
    run_node_with_temp!(node, P, T, i, ext, flux)
end

"""
    run_node_with_temp!(s_node::BilinearNode,
                        rain::Float64,
                        temp::Float64,
                        inflow::Float64,
                        ext::Float64,
                        gw_exchange::Float64=0.0;
                        current_store=nothing,
                        quick_store=nothing,
                        slow_store=nothing
                        gw_store=nothing)::Tuple{Float64, Float64}

Run node with temperature data to calculate outflow and update state.
"""
function run_node_with_temp!(s_node::BilinearNode,
                             rain::Float64,
                             temp::Float64,
                             inflow::Float64,
                             ext::Float64,
                             gw_exchange::Float64=0.0;
                             current_store=nothing,
                             quick_store=nothing,
                             slow_store=nothing,
                             gw_store=nothing)::Tuple{Float64, Float64}
    if !isnothing(current_store)
        s_node.storage[end] = current_store
    end

    if !isnothing(quick_store)
        s_node.quick_store[end] = quick_store
    end

    if !isnothing(slow_store)
        s_node.slow_store[end] = slow_store
    end

    if !isnothing(gw_store)
        s_node.gw_store[end] = gw_store
    end

    current_store = s_node.storage[end]
    quick_store = s_node.quick_store[end]
    slow_store = s_node.slow_store[end]
    gw_store = s_node.gw_store[end]

    interim_results = [0.0, 0.0, 0.0]
    @ccall IHACRES.calc_ft_interim_cmd(interim_results::Ptr{Cdouble},
                                       current_store::Cdouble,
                                       rain::Cdouble,
                                       s_node.d::Cdouble,
                                       s_node.d2::Cdouble,
                                       s_node.alpha::Cdouble)::Cvoid

    @assert any(isnan.(interim_results)) == false
    (mf, e_rainfall, recharge) = interim_results

    et::Float64 = @ccall IHACRES.calc_ET_from_T(
        s_node.e::Cdouble,
        temp::Cdouble,
        mf::Cdouble,
        s_node.f::Cdouble,
        s_node.d::Cdouble
    )::Cdouble

    cmd::Float64 = @ccall IHACRES.calc_cmd(
        mf::Cdouble,
        rain::Cdouble,
        et::Cdouble,
        e_rainfall::Cdouble,
        recharge::Cdouble
    )::Cdouble

    flow_results = [0.0, 0.0, 0.0]
    @ccall IHACRES.calc_ft_flows(
        flow_results::Ptr{Cdouble},
        quick_store::Cdouble,
        slow_store::Cdouble,
        e_rainfall::Cdouble,
        recharge::Cdouble,
        s_node.area::Cdouble,
        s_node.a::Cdouble,
        s_node.b::Cdouble
    )::Cvoid

    @assert any(isnan.(flow_results)) == false
    (nq_store, ns_store, outflow) = flow_results

    routing_res = [0.0, 0.0]
    @ccall IHACRES.routing(
        routing_res::Ptr{Cdouble},
        gw_store::Cdouble,
        s_node.storage_coef::Cdouble,
        inflow::Cdouble,
        outflow::Cdouble,
        ext::Cdouble,
        gw_exchange::Cdouble)::Cvoid

    @assert any(isnan.(routing_res)) == false
    (gw_store, outflow) = routing_res

    level_params = Array{Float64}(s_node.level_params)
    level::Float64 = @ccall IHACRES.calc_ft_level(
        outflow::Cdouble,
        level_params::Ptr{Cdouble}
    )::Cdouble

    push!(s_node.inflow, inflow)
    update_state(s_node, cmd, e_rainfall, et, nq_store, ns_store, outflow, level, gw_store)

    return outflow, level
end

"""
    update_params!(node::BilinearNode, d::Float64, d2::Float64, e::Float64, f::Float64,
                   a::Float64, b::Float64, s_coef::Float64, alpha::Float64)::Nothing

Update model parameters.
"""
function update_params!(node::BilinearNode, d::Float64, d2::Float64, e::Float64, f::Float64,
                        a::Float64, b::Float64, s_coef::Float64, alpha::Float64)::Nothing
    node.d = Param(d, bounds=node.d.bounds::Tuple)
    node.d2 = Param(d2, bounds=node.d2.bounds::Tuple)
    node.e = Param(e, bounds=node.e.bounds::Tuple)
    node.f = Param(f, bounds=node.f.bounds::Tuple)
    node.a = Param(a, bounds=node.a.bounds::Tuple)
    node.b = Param(b, bounds=node.b.bounds::Tuple)
    node.storage_coef = Param(s_coef, bounds=node.storage_coef.bounds::Tuple)
    node.alpha = Param(alpha, bounds=node.alpha.bounds::Tuple)

    return nothing
end

"""
    update_params!(node::BilinearNode, d::Float64, d2::Float64, e::Float64, f::Float64,
                   a::Float64, b::Float64, s_coef::Float64, alpha::Float64,
                   p1::Float64, p2::Float64, p3::Float64, p4::Float64, p5::Float64, p6::Float64, p7::Float64, p8::Float64, CTF::Float64)::Nothing

Update all parameters.
"""
function update_params!(node::BilinearNode, d::Float64, d2::Float64, e::Float64, f::Float64,
                        a::Float64, b::Float64, s_coef::Float64, alpha::Float64,
                        p1::Float64, p2::Float64, p3::Float64, p4::Float64, p5::Float64,
                        p6::Float64, p7::Float64, p8::Float64, CTF::Float64)::Nothing
    node.d = Param(d, bounds=node.d.bounds::Tuple)
    node.d2 = Param(d2, bounds=node.d2.bounds::Tuple)
    node.e = Param(e, bounds=node.e.bounds::Tuple)
    node.f = Param(f, bounds=node.f.bounds::Tuple)
    node.a = Param(a, bounds=node.a.bounds::Tuple)
    node.b = Param(b, bounds=node.b.bounds::Tuple)
    node.storage_coef = Param(s_coef, bounds=node.storage_coef.bounds::Tuple)
    node.alpha = Param(alpha, bounds=node.alpha.bounds::Tuple)

    n_lparams = node.level_params
    node.level_params = [
        Param(p1, bounds=n_lparams[1].bounds::Tuple)
        Param(p2, bounds=n_lparams[2].bounds::Tuple)
        Param(p3, bounds=n_lparams[3].bounds::Tuple)
        Param(p4, bounds=n_lparams[4].bounds::Tuple)
        Param(p5, bounds=n_lparams[5].bounds::Tuple)
        Param(p6, bounds=n_lparams[6].bounds::Tuple)
        Param(p7, bounds=n_lparams[7].bounds::Tuple)
        Param(p8, bounds=n_lparams[8].bounds::Tuple)
        Param(CTF, bounds=n_lparams[9].bounds::Tuple)
    ]

    return nothing
end

# TODO
# update_bounds!()

"""
    reset!(s_node::IHACRESNode)::Nothing

Reset node. Clears all states back to their initial values.
"""
function reset!(s_node::IHACRESNode)::Nothing
    s_node.storage = [s_node.storage[1]]
    s_node.quick_store = [s_node.quick_store[1]]
    s_node.slow_store = [s_node.slow_store[1]]
    s_node.gw_store = [s_node.gw_store[1]]
    s_node.outflow = []
    s_node.level = []
    s_node.effective_rainfall = []
    s_node.et = []
    s_node.inflow = []

    return nothing
end


#!/usr/bin/env julia

# Draw a sierpinski tiangle.

using Compose

function sierpinski(n)
    if n == 0
        canvas() << polygon((1,1), (0,1), (1/2, 0))
    else
        t = sierpinski(n - 1)
        compose(canvas(),
                (canvas(1/4,   0, 1/2, 1/2), t),
                (canvas(  0, 1/2, 1/2, 1/2), t),
                (canvas(1/2, 1/2, 1/2, 1/2), t))
    end
end

#img = SVG("sierpinski.svg", 4inch, 4(sqrt(3)/2)inch)
img = D3("sierpinski.js", 4inch, 4(sqrt(3)/2)inch)
draw(img, sierpinski(8) << linewidth(0.1mm) << fill(nothing))


original_decks = open("cards.txt") do f
    map(m -> parse.(Int, split(strip(m.captures[1]), "\n")), eachmatch(r"Player .:\n((?:\d+\n?)+)", read(f, String)))
end

function play_combat(decks::Array{Array{Int64,1},1})::Int
    while minimum(map(d -> length(d), decks)) > 0
        topcards = map(d -> popfirst!(d), decks)
        winner = argmax(topcards)
        decks[winner] = append!(decks[winner], winner == 1 ? topcards : reverse(topcards))
    end

    return argmax(map(d -> length(d), decks))
end

function return_first(x::Array{Int64,1}, y::Int64)::Array{Int64,1}
    return deepcopy(x[1:y])
end

function play_recursive_combat(decks::Array{Array{Int64,1},1})::Int
    # println("Starting a game")

    history = Array{Array{Array{Int64,1},1},1}()
    while minimum(map(d -> length(d), decks)) > 0
        # for i in 1:2
        #     println("Player $(i)'s deck: $(join(decks[i], ", "))")
        # end
        if decks in history
            # println("We've already seen these decks, player 1 wins the game")
            return 1
        end

        push!(history, deepcopy(decks))
        topcards = map(d -> popfirst!(d), decks)
        # for i in 1:2
        #     println("Player $(i) plays: $(topcards[i])")
        # end

        if all(map(d -> length(d), decks) .>= topcards)
            new_decks = map(return_first, decks, topcards)
            winner = play_recursive_combat(new_decks)
        else
            winner = argmax(topcards)
            # println("Player $(winner) wins the round")
        end

        decks[winner] = append!(decks[winner], winner == 1 ? topcards : reverse(topcards))
    end

    # println("Player $(argmax(map(d -> length(d), decks))) wins the game")
    return argmax(map(d -> length(d), decks))

end

decks = deepcopy(original_decks)
winner = play_combat(decks)
println(sum(decks[winner] .* collect(length(decks[winner]):-1:1)))

decks = deepcopy(original_decks)
winner = play_recursive_combat(decks)
println(sum(decks[winner] .* collect(length(decks[winner]):-1:1)))


#------------------------
#--------  Functions

#--------  random generation

function GenSymArrow(n::Integer,i::Integer)
    # generates symmetric n x n arrowhad matrix with arrow top at (i,i)
    SymArrow(rand(n-1),rand(n-1),rand(),i)
end

function GenSymDPR1(n::Integer)
    # generates symmetric n x n DPR1 matrix
    SymDPR1(rand(n),rand(n),rand())
end

#-------- Inverses

function inv{T}(A::SymArrow{T},i::Integer,tols::Vector{Float64})

    # COMPUTES: inverse of a SymArrow matrix A, inv(A-A.D[i]*I) which is again SymArrow
    # uses higher precision to compute top of the arrow element accurately, if
    # needed.
    # tols=[tolb,tolz] are tolerances, usually [1e3, 10*n]
    # [0.0,0.0] forces DoubleDouble, [1e50,1e50] would never use it
    # RETURNS:  SymArrow(D,z,b,i), Kb, Kz, Qout
    # Kb - condition Kb, Kz - condition Kz, Qout = 1 / 0 - double was / was not used

    n=length(A.D)
    D=Array(T,n)
    z=Array(T,n)
    wz=1/A.z[i]
    shift=A.D[i]

    for k=1:i-1
        D[k]=one(T)/(A.D[k]-shift)
        z[k]=-A.z[k]*D[k]*wz
    end
    for k=i+1:n
        D[k-1]=one(T)/(A.D[k]-shift)
        z[k-1]=-A.z[k]*D[k-1]*wz
    end

    # D=[1./(A.D[1:i-1]-shift),1./(A.D[i+1:end]-shift)]
    # wz=1/A.z[i]
    # z=-[A.z[1:i-1], A.z[i+1:end]].*D1*wz1
    a=A.a-A.D[i]

    # compute the sum in a plain loop
    P=zero(T)
    Q=zero(T)
    Qout=0

    nn=n-1
    for k=1:i-1
        D[k]>0.0 ? P+=A.z[k]^2*D[k] : Q+=A.z[k]^2*D[k]
    end
    for k=i:nn
        D[k]>0.0 ? P+=A.z[k+1]^2*D[k] : Q+=A.z[k+1]^2*D[k]
    end
    a<0 ? P=P-a : Q=Q-a

    Kb=(P-Q)/abs(P+Q)
    Kz=maxabs(A.z)*abs(wz)

    if Kb<tols[1] ||  Kz<tols[2]
        b=(P+Q)*wz*wz
    else  # recompute in Double
        Qout=1
        shiftd=map(Double,A.D[i])
        Dd=[Double{Float64}[Double(A.D[k])-shiftd for k=1:i-1];
            Double{Float64}[Double(A.D[k])-shiftd for
                            k=i+1:length(A.D)]]
        wzd=Double(A.z[i])
        ad=Double(A.a)-shiftd

        Pd,Qd=map(Double,(0.0,0.0))

        for k=1:i-1
            Dd[k].hi>0.0 ? Pd+=Double(A.z[k])^2/Dd[k] :
            Qd+=Double(A.z[k])^2/Dd[k]
        end

        for k=i:nn
            Dd[k].hi>0.0 ? Pd+=Double(A.z[k+1])^2/Dd[k] :
            Qd+=Double(A.z[k+1])^2/Dd[k]
            # @show P,Q
        end

        ad.hi<0 ?   Pd=Pd-ad : Qd=Qd-ad

        bd=(Pd+Qd)/(wzd*wzd)
        b=bd.hi+bd.lo
    end

    if i<A.i
        # SymArrow([D[1:A.i-2],zero(T),D[A.i-1:end]],[z[1:A.i-2],wz,z[A.i-1:end]],b,i),Kb,Kz,Qout
        # This reduces memory allocation (4 vectors) and is 30-40% faster
        for k=n-1:-1:A.i-1
            D[k+1]=D[k]
            z[k+1]=z[k]
        end
        D[A.i-1]=zero(T)
        z[A.i-1]=wz
        SymArrow(D,z,b,i),Kb,Kz,Qout
    else
        # SymArrow([D[1:A.i-1],zero(T),D[A.i:end]],[z[1:A.i-1],wz,z[A.i:end]],b,i+1),Kb,Kz,Qout
        for k=n-1:-1:A.i
            D[k+1]=D[k]
            z[k+1]=z[k]
        end
        D[A.i]=zero(T)
        z[A.i]=wz
        SymArrow(D,z,b,i+1),Kb,Kz,Qout
    end

end # inv

function inv{T}(A::SymArrow{T}, shift::Float64, tolr::Float64)

    # COMPUTES: inverse of the shifted SymArrow A, inv(A-shift*I) which is SymDPR1
    # uses DoubleDouble to compute rho accurately, if needed.
    # tolr is tolerance, usually 1e3,  0.0 forces Double, 1e50 would never use it
    # RETURNS: SymDPR1(D,u,rho), Krho, Qout
    # Krho - condition Krho, Qout = 1 / 0 - Double was / was not used

    n=length(A.D)
    D=Array(T,n+1)
    u=Array(T,n+1)

    # Ds=A.D-shift

    # D=[1./Ds[1:A.i-1];0.0;1./Ds[A.i:end]];
    # u=[A.z[1:A.i-1]./Ds[1:A.i-1];-1.0;A.z[A.i:end]./Ds[A.i:end]];

    for k=1:A.i-1
        D[k]=one(T)/(A.D[k]-shift)
        u[k]=A.z[k]*D[k]
    end
    D[A.i]=zero(T)
    u[A.i]=-one(T)
    for k=A.i:n
        D[k+1]=one(T)/(A.D[k]-shift)
        u[k+1]=A.z[k]*D[k+1]
    end

    # compute rho and Krho
    #--- compute the sum in a plain loop

    a=A.a-shift
    P=zero(T)
    Q=zero(T)
    Qout=0

    for k=1:A.i-1
        D[k]>0.0 ? P=P+A.z[k]^2*D[k] : Q=Q+A.z[k]^2*D[k]
    end
    for k=A.i:n
        D[k+1]>0.0 ? P=P+A.z[k]^2*D[k+1] : Q=Q+A.z[k]^2*D[k+1]
    end

    a<0 ? P=P-a : Q=Q-a

    # Condition of rho
    Krho=(P-Q)/abs(P+Q)

    if Krho<tolr
        rho=-1.0/(P+Q)

    else  # recompute in Double
        Qout=1
        Pd,Qd=map(Double,(0.0,0.0))
        shiftd=Double(shift)
        ad=Double(A.a)-shiftd
        #   Dd=[Double(A.D[k])-shiftd for k=1:length(A.D) ]
        #   Dd=map(Double,A.D)-shiftd
        for k=1:A.i-1
            D[k]>0.0 ? Pd=Pd+Double(A.z[k])^2/(Double(A.D[k])-shiftd) : Qd=Qd+Double(A.z[k])^2/(Double(A.D[k])-shiftd)
        end
        for k=A.i:n
            D[k+1]>0.0 ? Pd=Pd+Double(A.z[k])^2/(Double(A.D[k])-shiftd) : Qd=Qd+Double(A.z[k])^2/(Double(A.D[k])-shiftd)
        end

        # for k=1:length(A.D)
        #    Dd[k].hi>0.0 ? Pd=Pd+Double(A.z[k])^2/Dd[k] : Qd=Qd+Double(A.z[k])^2/Dd[k]
        # end
        ad.hi+ad.lo<0 ? Pd=Pd-ad : Qd=Qd-ad
        r=Double(1.0)/(Pd+Qd)
        rho=-(r.hi+r.lo)
    end

    # returns the following
    SymDPR1(D,u,rho), Krho, Qout

end # inv

function inv{T}(A::SymArrow{T}, shift::Double)

    # COMPUTES: inverse of the shifted SymArrow A, inv(A-shift*I), which is a SymDPR1
    # here shift is Double so it uses Double to compute everything
    # RETURNS: SymDPR1(D1,u1,rho), Qout
    # Qout = 1 on exit meaning Double was used

    n=length(A.D)
    D=Array(Double,n+1)
    u=Array(Double,n+1)

    # D[1:n]=map(Double,A.D)-shift
    for k=1:n
        D[k]=Double(A.D[k])-shift
    end

    u[1:n]=map(Double,A.z)
    a=map(Double,A.a)-shift
    i=A.i

    oned=Double(1.0)
    zerod=Double(0.0)

    # Ds=A.D-shift
    # D=[1./Ds[1:A.i-1];0.0;1./Ds[A.i:end]];
    # u=[A.z[1:A.i-1]./Ds[1:A.i-1];-1.0;A.z[A.i:end]./Ds[A.i:end]];

    for k=1:i-1
        #   tmp=Double(A.D[k])-shift
        #    D[k]=oned/(Double(A.D[k])-shift)
        u[k]=u[k]/D[k]
        D[k]=oned/D[k]
        #   u[k]=Double(A.z[k])/tmp # *D[k]
    end

    for k=i:n
        u[k+1]=u[k]/D[k]
        D[k+1]=oned/D[k+1]
        # D[k+1]=oned/(Double(A.D[k])-shift)
        # u[k+1]=Double(A.z[k])*D[k+1]
    end

    D[i]=zerod
    u[i]=Double(-1.0)

    # D=[Double(1.0)./D1[1:A.i-1];Double(0.0);Double(1.0)./D1[A.i:end]];
    # u=[z1[1:A.i-1]./D1[1:A.i-1];Double(-1.0);z1[A.i:end]./D1[A.i:end]];

    # compute rho and Krho
    #--- compute the sum in a plain loop

    P,Q=zerod,zerod
    Qout=1

    for k=1:i-1
        # D[k].hi+D[k].lo > 0.0  # .lo cannot change sign, I think
        # D[k].hi > 0.0 ? P=P+Double(A.z[k])^2*D[k] : Q=Q+Double(A.z[k])^2*D[k]
        D[k].hi > 0.0 ? P=P+Double(A.z[k])*u[k] : Q=Q+Double(A.z[k])*u[k]
    end
    for k=i:n
        D[k+1].hi >0.0 ? P=P+Double(A.z[k])*u[k+1] : Q=Q+Double(A.z[k])*u[k+1]
    end

    a.hi+a.lo<0.0  ? P=P-a : Q=Q-a
    r=oned/(P+Q)

    # for k=1:length(A.D)
    #    D1[k].hi+D1[k].lo>0.0 ? P=P+z1[k]^2/D1[k] : Q=Q+z1[k]^2/D1[k]
    # end

    rho=-(r.hi+r.lo)

    # returns the following
    D1=Array(T,n+1)
    u1=Array(T,n+1)
    for k=1:n+1
        D1[k]=D[k].hi+D[k].lo
    end
    for k=1:n+1
        u1[k]=u[k].hi+u[k].lo
    end

    # SymDPR1(T[x.hi+x.lo for x=D],T[x.hi+x.lo for x=u],rho), Qout

    SymDPR1(D1,u1,rho), Qout

end # inv

function  eig{T}( A::SymArrow{T},k::Integer,tols::Vector{Float64})

    # COMPUTES: k-th eigenpair of an ordered irreducible SymArrow
    # A = [diag (D) z; z' alpha]
    # tols=[tolb,tolz,tolnu,tolrho,tollambda] = [1e3,10.0*n,1e3,1e3,1e3]
    # RETURNS: lambda, v, Sind, Kb, Kz, Knu, Krho, Qout
    # lambda - k-th eigenvalue in descending order
    # v - lambda's normalized eigenvector
    # Kb, Kz, Knu, Krho - condition numbers
    # Qout = 1 / 0 - Double was / was not used

    # If Qout>0, quad precision was used
    # i was the shift index

    # Set the dimension
    n = length(A.D) + 1

    # Set all conditions initially to zero
    Kb,Kz,Knu,Krho=0.0,0.0,0.0,0.0
    Qout=0
    v=zeros(T,n)
    # Kz is former kappa_nu

    # Determine the shift sigma, the shift index i, and whether lambda
    # is on the left or the right side of the nearest pole
    # Exterior eigenvalues (k = 1 or k = n):

    if k == 1
        sigma,i,side = A.D[1],1,'R'
    elseif k==n
        sigma,i,side = A.D[n-1],n-1,'L'
    else
        # Interior eigenvalues (k in (2,...n-1) ):
        Dtemp = A.D-A.D[k]
        atemp = A.a-A.D[k]
        middle = Dtemp[k-1]/2.0
        Fmiddle = (atemp-middle)-sum(A.z.^2./(Dtemp-middle))
        # middle=(A.D[k-1]-A.D[k])/2.0
        # Fmiddle=0.0
        # for l=1:n-1
        #     Fmiddle+=A.z[l]^2/((A.D[l]-A.D[k])-middle)
        # end
        # Fmiddle=((A.a-A.D[k])-middle)-Fmiddle
        # @show Fmiddle
        sigma,i,side = Fmiddle < 0.0 ? (A.D[k],k,'R') : (A.D[k-1],k-1,'L')
    end

    # Compute the inverse of the shifted matrix, A_i^(-1), Kb and Kz

    Ainv,Kb,Kz,Qout = inv(A,i,tols[1:2])

    # Compute the eigenvalue of the inverse shifted matrix

    nu = bisect( Ainv,side )

    #  nu=fastzero([invD1; 0; invD2], [w1;wz;w2], b, side); # implement later
    #  [nu-nu1]/nu, [nu-nueva]/nu, pause  # and compare

    if abs(nu)==Inf
        # this is nonstandard
        # Deflation in aheig (nu=Inf)
        v[i]=1.0
        lambda=sigma
    else
        # standard case, full computation
        # nu1 is the F- or 1-norm of the inverse of the shifted matrix
        # nu10=maximum([sum(abs(Ainv.z))+abs(Ainv.a), maximum(abs(Ainv.D)+abs(Ainv.z))])
        nu1=0.0
        for l=1:n-1
            nu1=max(nu1,abs(Ainv.D[l])+abs(Ainv.z[l]))
        end
        nu1=max(sumabs(Ainv.z)+abs(Ainv.a), nu1)

        Knu=nu1/abs(nu)
        if Knu<tols[3]
            # Accuracy is fine, compute the eigenvector
            mu = 1.0/nu
            # v=[ A.z./((A.D-sigma)-mu);-1.0]
            for l=1:n-1
            	v[l] = A.z[l]/((A.D[l]-sigma)-mu)
            end
            v[n]=-1.0
            lambda, v = mu+sigma, v/norm(v)
        else
            # Remedies according to Remark 3 - we shift between original
            # eigenvalues and compute DPR1 matrix
            # 1/nu1+sigma, 1/nu+sigma
            # println("Remedy 3 ")
            nu = side=='R' ? abs(nu) : -abs(nu)
            nu1=-sign(nu)*nu1
            sigma1=(nu1+nu)/(2.0*nu*nu1)+sigma

            if findfirst(A.D-sigma1,0.0)>0 # we came back with a pole
                # recompute sigmav more accurately according with dekker
                sigmav=(Double(nu1)+Double(nu))/(Double(2.0)*Double(nu)*Double(nu1))+Double(sigma)
                # Compute the inverse of the shifted arrowhead (DPR1)
                Ainv,Qout1=inv(A,sigmav) # Ainv is Float64
                nu1=bisect(Ainv,side)
                mu1 = 1.0/nu1
                D0=map(Double,A.D)-sigmav
                D1=[D0[l].hi+D0[l].lo for l=1:length(D0)]
                if findfirst(D1-mu1,0.0)>0
                    ind=find(D1-mu1==0.0)
                    v=zeros(n)
                    v[ind]=1.0
                else
                    v=[ A.z./(D1-mu1);-1.0]
                end
                # Shift the eigenvalue back in Double
                lam = Double(1.0)/Double(nu1)+sigmav
                # Return this
                lambda,v = lam.hi+lam.lo, v/norm(v)
            else
                # Compute the inverse of the shifted arrowhead (DPR1)
                Ainv, Krho,Qout1=inv(A,sigma1,tols[4]) # Ainv is Float64
                # Compute the eigenvalue by bisect for DPR1
	        # Note: instead of bisection could use dlaed4 (need a wrapper) but
	        # it is not faster. There norm(u)==1
                nu1= bisect(Ainv,side)
                mu1=1.0/nu1
                # standard v
                v=[ A.z./((A.D-sigma1)-mu1);-1.0]
                # Return this - shift the eigenvalue back and normalize the vector
                lambda, v = mu1+sigma1, v/norm(v)
            end
            Qout==1 && (Qout=Qout+2)
        end

        # Remedy according to Remark 1 - we recompute the the eigenvalue
        # near zero from the inverse of the original matrix (a DPR1 matrix).
        if (abs(A.D[i])+abs(1.0/nu))/abs(lambda)>tols[5]

            if k==1 && A.D[1]<0.0 || k==n && A.D[n-1]>0.0 || i<n-1 && side=='L' && sign(A.D[i])+sign(A.D[i+1])==0 ||
                side=='R' && sign(A.D[i])+sign(A.D[i-1])==0
                # println("Remedy 1 ")
                # Compute the inverse of the original arrowhead (DPR1)
                Ainv,Krho,Qout1 = inv(A,0.0,tols[4]) # Ainv is Float64
                Qout==1 && (Qout=Qout+4)
                if abs(Ainv.r)==Inf
                    lambda=0.0
                else
                    # Here we do not need bisection. We compute the Rayleigh
                    # quotient by using already computed vectors which is
                    # componentwise accurate
                    nu1=sum(v.^2.*Ainv.D)+Ainv.r*sum(v.*Ainv.u)^2;
                    lambda=1.0/nu1
                end
            end

        end
    end

    # Return this
    lambda,v,i,Kb,Kz,Knu,Krho,Qout

end # eig (k)

function eig{T}(A::SymArrow{T}, tols::Vector{Float64})

    # COMPUTES: all eigenvalues and eigenvectors of a real symmetric SymArrow
    # A = [diag(D) z;z' alpha] (notice, here we assume A.i==n)
    # tols = [tolb,tolz,tolnu,tolrho,tollambda] = [1e3,10.0*n,1e3,1e3,1e3] or similar
    # RETURNS: U, E, Sind, Kb, Kz, Knu, Krho, Qout
    # U = eigenvectors, E = eigenvalues in decreasing order
    # Sind[k] - shift index i for the k-th eigenvalue
    # Kb, Kz, Knu, Krho [k] - respective conditions for the k-th eigenvalue
    # Qout[k] = 1 / 0 - Double was / was not used when computing k-th eigenvalue

    n=length(A.D)+1
    n0=n

    # Ordering the matrix
    is=sortperm(A.D,rev=true)
    D=A.D[is]
    z=A.z[is]
    U=eye(T,n,n)
    E=zeros(T,n)
    Kb=zeros(T,n); Kz=zeros(T,n); Knu=zeros(T,n); Krho=zeros(T,n)
    Qout=zeros(Int,n); Sind=zeros(Int,n)

    # Quick return for 1x1
    if n==1
        return U,[A.a],Sind,Kb,Kz,Knu,Krho,Qout
    end

    #  test for deflation in z
    z0=find(z.==0)
    zx=find(z.!=0)

    if isempty(zx)  # nothing to do
        E=[A.D;A.a]
        isE=sortperm(E,rev=true)
        E=E[isE]
        U=U[:,isE]
        return U,E,Sind,Kb,Kz,Knu,Krho,Qout
    end

    if !isempty(z0)
        E[z0]=D[z0]
        D=D[zx]
        z=z[zx]
        if isempty(z)
            E[n]=A.a
            # return U,E,Sind,Kb,Kz,Knu,Krho,Qout
        else
            n=length(z)+1
        end
    end
    zx=[zx;n0]

    #  Test for deflation in D
    g=D[1:n-2]-D[2:n-1]
    # Can play with inexact deflation
    # g0=find(abs(g)<eps)
    # gx=find(abs(g)>=eps)
    # Only exact deflation !!
    g0=find(g.==0.0)
    gx=find(g.!=0.0)
    if !isempty(g0)
        # Deflation
        Dgx=D[gx]; zgx=z[gx]
        lg0=length(g0)
        R=Array(Tuple{Givens{Float64},Float64},lg0)
        for l=lg0:-1:1
            # This will be changed in v0.4
            R[l]=givens(z[g0[l]],z[g0[l]+1],zx[g0[l]],zx[g0[l]+1])
            z[g0[l]]=R[l][2]; z[g0[l]+1]=0.0
            # A_mul_Bc!(U,R) # multiply R'*U later
            E[zx[g0[l]+1]]=D[g0[l]+1]
        end
        # remains
        gx=[0;gx]+1
        nn=length(gx)

        zxx=zx[[gx;n]]
        for k=1:nn+1
            E[zxx[k]],U[zxx,zxx[k]],Sind[zxx[k]],Kb[zxx[k]],Kz[zxx[k]],Knu[zxx[k]],Krho[zxx[k]],Qout[zxx[k]]=
            eig(SymArrow(D[gx],z[gx],A.a,nn+1),k,tols)
        end

        for l=1:lg0
            U=R[l][1]'*U
        end

    else

        # No deflation in D
        for k=1:n
            E[zx[k]],U[zx,zx[k]],Sind[zx[k]],Kb[zx[k]],Kz[zx[k]],Knu[zx[k]],Krho[zx[k]],Qout[zx[k]]=
            eig(SymArrow(D,z,A.a,n),k,tols)
        end
    end
    # end
    # back premutation of vectors
    isi=sortperm(is)

    # must sort E once more
    es=sortperm(E,rev=true)
    E=E[es]

    U=U[[isi[1:A.i-1];n0;isi[A.i:n0-1]],es]

    # Return this
    U,E,Sind[es],Kb[es],Kz[es],Knu[es],Krho[es],Qout[es]

end # eig (all)

function bisect{T}(A::SymArrow{T}, side::Char)
    # COMPUTES: the leftmost (for side='L') or the rightmost (for side='R') eigenvalue
    # of a SymArrow A = [diag (D) z; z'] by bisection.
    # RETURNS: the eigenvalue

    # Determine the starting interval for bisection, [left; right]
    # left, right = side == 'L' ? {minimum([A.D-abs(A.z),A.a-sum(abs(A.z))]), minimum(A.D)} :
    #   {maximum(A.D),maximum([A.D+abs(A.z),A.a+sum(abs(A.z))])}

    absAz = abs(A.z)
    if side == 'L'
        left  = minimum(A.D - absAz)::T
        left  = min(left, A.a - sum(absAz))::T
        right = minimum(A.D)::T
    else
        left  = maximum(A.D)::T
        right = maximum(A.D + absAz)::T
        right = max(right, A.a + sum(absAz))::T
    end

    # Bisection
    middle = (left + right) / convert(T,2)
    z2 = A.z .^ 2
    count, n = 0, length(A.D)

    while (right-left) > 2.0 * eps() * max(abs(left), abs(right))
        # in @time 50% of the time was garbage collection. The following line
        # assigns new vector every time it is called, so it is much better in the
        # loop?? Original timing were 30 secs for n=4000, 2.17 sec for n=1000

        # Fmiddle = A.a-middle-sum(z2./(A.D-middle))

        Fmiddle = zero(T)
        for k=1:n
            Fmiddle += z2[k] / (A.D[k] - middle)
        end
        Fmiddle = A.a - middle - Fmiddle

        if Fmiddle > zero(T)
            left = middle
        else
            right = middle
        end
        middle = (left + right) / convert(T,2)
    end
    # Return the eigenvalue
    right

end # bisect

    function bisect( A::SymDPR1, side::Char )
        # COMPUTES: the leftmost (for side='L') or the rightmost (for side='R') eigenvalue
        # of a SymDPR1 matrix A = diagm(A.D) + A.r*A.u*(A.u)' by bisection.
        # RETURNS: the eigenvalue

        n=length(A.D)
        # Determine the starting interval for bisection, [left; right]
        indD=sortperm(A.D,rev=true)

        if A.r>0.0
            left, right = side == 'L' ? (A.D[indD[n]], A.D[indD[n-1]]) :  (A.D[indD[1]], A.D[indD[1]]+A.r*dot(A.u,A.u))
        else # rho<=0
            left, right = side == 'L' ? (A.D[indD[n]]+A.r*dot(A.u,A.u), A.D[indD[n]]) : (A.D[indD[2]], A.D[indD[1]])
        end

        # Bisection
        middle = (left + right)/2.0
        u2=A.u.^2
        n = length(A.D)
        while (right-left) > 2.0*eps()*maximum([abs(left),abs(right)])

            # Fmiddle = 1.0+A.r*sum(u2./(A.D-middle))

            Fmiddle=0.0
            for k=1:n
                Fmiddle=Fmiddle+u2[k]/(A.D[k]-middle)
            end
            Fmiddle=1.0+A.r*Fmiddle

            sign(A.r)*Fmiddle < 0.0 ? left = middle : right = middle
            middle = (left + right)/2.0
        end
        # Return the eigenvalue
        right

    end # bisect

    # this is just for convenience
    # tols=[1e3,1000.0,1e3,1e3,1e3]


# Tests for Radial DEA Models
@testset "AdditiveDEAModel" begin

    ## Test Additive DEA Models with FLS Book data
    # Test against results with R
    X = [5 13; 16 12; 16 26; 17 15; 18 14; 23 6; 25 10; 27 22; 37 14; 42 25; 5 17]
    Y = [12; 14; 25; 26; 8; 9; 27; 30; 31; 26; 12]

    # Additive CRS
    deaaddcrs1 = deaadd(X, Y, :Custom, wX = ones(size(X)), wY = ones(size(Y)), rts = :CRS)

    @test nobs(deaaddcrs1) == 11
    @test ninputs(deaaddcrs1) == 2
    @test noutputs(deaaddcrs1) == 1
    @test efficiency(deaaddcrs1) ≈ [0.0000000000;
                               10.76923077;
                               10.83783784;
                               0.0000000000;
                               22.15384615;
                               17.92307692;
                               0.0000000000;
                               12.07692308;
                               11.61379310;
                               35.000000000;
                               4.000000000]

    @test convert(Matrix, peers(deaaddcrs1)) ≈
    [ 1.0000000000  0  0 0.0000000000  0  0 0.0000000000  0  0   0   0
      0.0000000000  0  0 0.5384615385  0  0 0.0000000000  0  0   0   0
      0.1216216216  0  0 0.9054054054  0  0 0.0000000000  0  0   0   0
      0.0000000000  0  0 1.0000000000  0  0 0.0000000000  0  0   0   0
      0.0000000000  0  0 0.3076923077  0  0 0.0000000000  0  0   0   0
      0.0000000000  0  0 0.3461538462  0  0 0.0000000000  0  0   0   0
      0.0000000000  0  0 0.0000000000  0  0 1.0000000000  0  0   0   0
      0.0000000000  0  0 1.1538461538  0  0 0.0000000000  0  0   0   0
      0.0000000000  0  0 0.4689655172  0  0 0.6965517241  0  0   0   0
      0.0000000000  0  0 1.0000000000  0  0 0.0000000000  0  0   0   0
      1.0000000000  0  0 0.0000000000  0  0 0.0000000000  0  0   0   0]

    # Additive VRS model
    deaaddvrs1 = deaadd(X, Y, :Custom, wX = ones(size(X)), wY = ones(size(Y)), rts = :VRS)

    @test nobs(deaaddvrs1) == 11
    @test ninputs(deaaddvrs1) == 2
    @test noutputs(deaaddvrs1) == 1
    @test efficiency(deaaddvrs1) ≈ [0.0000000000;
                               7.333333333;
                               0.0000000000;
                               0.0000000000;
                               18.000000000;
                               0.0000000000;
                               0.0000000000;
                               0.0000000000;
                               0.0000000000;
                               35.000000000;
                               4.000000000]

    @test convert(Matrix, peers(deaaddvrs1)) ≈
    [1.000000000  0  0  0  0  0 0.00000000000  0  0   0   0;
     0.6666666667 0  0  0  0  0 0.3333333333   0  0   0   0;
     0.000000000  0  1  0  0  0 0.00000000000  0  0   0   0;
     0.000000000  0  0  1  0  0 0.00000000000  0  0   0   0;
     1.000000000  0  0  0  0  0 0.00000000000  0  0   0   0;
     0.000000000  0  0  0  0  1 0.00000000000  0  0   0   0;
     0.000000000  0  0  0  0  0 1.00000000000  0  0   0   0;
     0.000000000  0  0  0  0  0 0.00000000000  1  0   0   0;
     0.000000000  0  0  0  0  0 0.00000000000  0  1   0   0;
     0.000000000  0  0  1  0  0 0.00000000000  0  0   0   0;
     1.000000000  0  0  0  0  0 0.00000000000  0  0   0   0]

    # Check weights equal to one equals default model
    deaadddefaultcrs = deaadd(X, Y, rts = :CRS)
    @test efficiency(deaaddcrs1) ≈ efficiency(deaadddefaultcrs)

    deaadddefaultvrs = deaadd(X, Y)
    @test efficiency(deaaddvrs1) ≈ efficiency(deaadddefaultvrs)

    # Test model :Ones equals model with all weights equal to 1
    deaaddonescrs = deaadd(X, Y, :Ones, rts = :CRS)
    @test deaaddonescrs.weights == :Ones
    @test efficiency(deaaddonescrs) ≈ efficiency(deaadddefaultcrs)

    deaaddonesvrs = deaadd(X, Y, :Ones)
    @test deaaddonesvrs.weights == :Ones
    @test efficiency(deaaddonesvrs) ≈ efficiency(deaadddefaultvrs)

    # MIP CRS
    deaaddmipcrs = deaadd(X, Y, :MIP, rts = :CRS)
    @test deaaddmipcrs.weights == :MIP
    @test efficiency(deaaddmipcrs) ≈ [0.0000000000;
                               0.7577160494;
                               0.4168399168;
                               0.0000000000;
                               2.2219512195;
                               1.1478260870;
                               0.0000000000;
                               0.4867909868;
                               0.4041184041;
                               1.0726153846;
                               0.2352941176]
    @test deaaddmipcrs.slackX ≈ [0 0;
                                 3.037037037 6.814814815;
                                 0 10.837837838;
                                 0 0;
                                 0 0;
                                 8.000000000  0;
                                 0 0;
                                 7.384615385 4.692307692;
                                 8.296296296 2.518518519;
                                 0.000000000  8.200000000;
                                 0 4.000000000]

    @test deaaddmipcrs.slackY ≈ [0;
                                  0;
                                  0;
                                  0;
                                  17.77560976 ;
                                  7.20000000;
                                  0;
                                  0;
                                  0;
                                  19.36000000;
                                  0]

    # MIP VRS
    deaaddmipvrs = deaadd(X, Y, :MIP, rts = :VRS)
    @test deaaddmipvrs.weights == :MIP
    @test efficiency(deaaddmipvrs) ≈ [0.0000000000;
                               0.5075187970;
                               0.0000000000;
                               0.0000000000;
                               2.2039473684;
                               0.0000000000;
                               0.0000000000;
                               0.0000000000;
                               0.0000000000;
                               1.0432234432;
                               0.2352941176]
    @test deaaddmipvrs.slackX ≈ [0 0;
                                 0 0;
                                 0 0;
                                 0 0;
                                 0 0;
                                 0 0;
                                 0 0;
                                 0 0;
                                 0 0;
                                 17 15;
                                 0 4]
    @test deaaddmipvrs.slackY ≈ [0;
                                 7.105263158;
                                 0;
                                 0;
                                 17.631578947;
                                 0;
                                 0;
                                 0;
                                 0;
                                 1.000000000;
                                 0]

    # Normalized CRS
    deaaddnormalizedcrs = deaadd(X, Y, :Normalized, rts = :CRS)
    @test deaaddnormalizedcrs.weights == :Normalized
    @test efficiency(deaaddnormalizedcrs) ≈ [0.0000000000;
                                 1.3569256615;
                                 1.7407259078;
                                 0.0000000000;
                                 2.6877810368;
                                 1.6953153107;
                                 0.0000000000;
                                 1.6540884810;
                                 1.1212042237;
                                 4.0172855145;
                                 0.6424624298]

    @test deaaddnormalizedcrs.slackX ≈ [0.000000000  0.000000000;
                                 3.037037037  6.814814815;
                                 0.000000000 10.837837838;
                                 0.000000000  0.000000000;
                                 10.592592593 11.037037037;
                                 14.666666667  2.666666667;
                                 0.000000000  0.000000000;
                                 0.000000000 10.298429319;
                                 8.296296296  2.518518519;
                                 17.925925926 15.370370370;
                                 0.000000000  4.000000000]

    @test deaaddnormalizedcrs.slackY ≈ [0;
                                  0;
                                  0;
                                  0;
                                  0 ;
                                  0;
                                  0;
                                  0;
                                  0;
                                  0;
                                  0]

    # Normalized VRS
    deaaddnormalizedvrs = deaadd(X, Y, :Normalized, rts = :VRS)
    @test deaaddnormalizedvrs.weights == :Normalized
    @test efficiency(deaaddnormalizedvrs) ≈ [0.0000000000;
                                 0.8049248943;
                                 0.0000000000;
                                 0.0000000000;
                                 2.0149704908;
                                 0.0000000000;
                                 0.0000000000;
                                 0.0000000000;
                                 0.0000000000;
                                 3.9898943952;
                                 0.6424624298]

    @test deaaddnormalizedvrs.slackX ≈ [0  0.00;
                                 0  0.65;
                                 0  0.00;
                                 0  0.00;
                                 0  2.95;
                                 0  0.00;
                                 0  0.00;
                                 0  0.00;
                                 0  0.00;
                                 17 15.00;
                                 0  4.00]

    @test deaaddnormalizedvrs.slackY ≈ [0.00;
                                6.25;
                                0.00;
                                0.00;
                                13.75;
                                0.00;
                                0.00;
                                0.00;
                                0.00;
                                1.00;
                                0.00]

    # RAM CRS
    deaaddramcrs = deaadd(X, Y, :RAM, rts = :CRS)
    @test deaaddramcrs.weights == :RAM
    @test efficiency(deaaddramcrs) ≈ [0.0000000000;
                                0.14094094094;
                                0.18063063063;
                                0.0000000000;
                                0.27937937938;
                                0.17657657658;
                                0.0000000000;
                                0.17164048866;
                                0.11671671672;
                                0.41766766767;
                                0.06666666667]

    @test deaaddramcrs.slackX ≈ [ 0.000000000  0.000000000;
                                3.037037037  6.814814815;
                                0.000000000  10.837837838;
                                0.000000000  0.000000000;
                                10.592592593 11.037037037;
                                14.666666667 2.666666667;
                                0.000000000  0.000000000;
                                0.000000000  10.298429319;
                                8.296296296  2.518518519;
                                17.925925926 15.370370370;
                                0.000000000  4.000000000]

    @test deaaddramcrs.slackY ≈ [0;
                                   0;
                                   0;
                                   0;
                                   0 ;
                                   0;
                                   0;
                                   0;
                                   0;
                                   0;
                                   0]

    # RAM VRS
    deaaddramvrs = deaadd(X, Y, :RAM, rts = :VRS)
    @test deaaddramvrs.weights == :RAM
    @test efficiency(deaaddramvrs) ≈ [0.0000000000;
                              0.10297482838;
                              0.0000000000;
                              0.0000000000;
                              0.25553012967;
                              0.0000000000;
                              0.0000000000;
                              0.0000000000;
                              0.0000000000;
                              0.41764590678 ;
                              0.06666666667]

    @test deaaddramvrs.slackX ≈ [0 0;
                                0 0;
                                0 0;
                                0 0;
                                0 0;
                                0 0;
                                0 0;
                                0 0;
                                0 0;
                                17 15;
                                0 4]
    @test deaaddramvrs.slackY ≈ [0;
                                7.105263158;
                                0;
                                0;
                                17.631578947;
                                0;
                                0;
                                0;
                                0;
                                1.000000000;
                                0]

    # BAM CRS
    deaaddbamcrs = deaadd(X, Y, :BAM, rts = :CRS)
    @test deaaddbamcrs.weights == :BAM
    @test efficiency(deaaddbamcrs) ≈ [0.0000000000;
                                0.4578892372;
                                0.3051750381;
                                0.0000000000;
                                0.6732181854;
                                0.3239568683;
                                0.0000000000;
                                0.5255235602;
                                0.1913580247;
                                0.6902867780;
                                0.1212121212]

    @test deaaddbamcrs.slackX ≈ [0.000000000  0.000000000;
                                4.110344828  6.000000000;
                                0.000000000  0.000000000;
                                0.000000000  0.000000000;
                                13.000000000  8.000000000;
                                17.493670886  0.000000000;
                                0.000000000  0.000000000;
                                0.000000000  9.225130890;
                                8.296296296  2.518518519;
                                13.296296296 13.518518519;
                                0.000000000  4.000000000]
    @test deaaddbamcrs.slackY ≈ [0;
                                0;
                                5.4931506849;
                                0;
                                0.4520547945 ;
                                0;
                                0;
                                1;
                                0;
                                5;
                                0]

    # BAM VRS
    deaaddbamvrs = deaadd(X, Y, :BAM, rts = :VRS)
    @test deaaddbamvrs.weights == :BAM
    @test efficiency(deaaddbamvrs) ≈ [0.0000000000;
                              0.1998936736;
                              0.0000000000;
                              0.0000000000;
                              0.4329710145;
                              0.0000000000;
                              0.0000000000;
                              0.0000000000;
                              0.0000000000;
                              0.5713608345;
                              0.1212121212]

    @test deaaddbamvrs.slackX ≈ [0 0;
                                6.596491228 0;
                                0 0;
                                0 0;
                                13.000000000 1;
                                0 0;
                                0 0;
                                0 0;
                                0 0;
                                5 11;
                                0 4]
    @test deaaddbamvrs.slackY ≈ [0;
                                0;
                                0;
                                0;
                                4;
                                0;
                                0;
                                0;
                                0;
                                5;
                                0]

    # Test model with Custom weights
    deaddcustomcrs = deaadd(X, Y, :Custom, wX = 1 ./ X, wY = 1 ./ Y, rts = :CRS)
    @test deaddcustomcrs.weights == :Custom
    @test efficiency(deaddcustomcrs) ≈ efficiency(deaaddmipcrs)

    deaddcustomvrs = deaadd(X, Y, :Custom, wX = 1 ./ X, wY = 1 ./ Y, rts = :VRS)
    @test deaddcustomvrs.weights == :Custom
    @test efficiency(deaddcustomvrs) ≈ efficiency(deaaddmipvrs)

    ## Test if one-by-one DEA using evaluation and reference sets match initial results
    deaadddefaultcrs_ref_eff = zeros(size(X, 1))
    deaadddefaultvrs_ref_eff = zeros(size(X, 1))

    deaaddcustomcrs_ref_eff = zeros(size(X, 1))
    deaaddcustomvrs_ref_eff = zeros(size(X, 1))

    deaaddonescrs_ref_eff = zeros(size(X, 1))
    deaaddonesvrs_ref_eff = zeros(size(X, 1))

    deaaddmipcrs_ref_eff = zeros(size(X, 1))
    deaaddmipvrs_ref_eff = zeros(size(X, 1))

    Xref = X[:,:]
    Yref = Y[:,:]

    for i = 1:size(X, 1)
        Xeval = X[i:i,:]
        Xeval = Xeval[:,:]
        Yeval = Y[i:i,:]
        Yeval = Yeval[:,:]

        deaadddefaultcrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, rts = :CRS, Xref = Xref, Yref = Yref))[1]
        deaadddefaultvrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, rts = :VRS, Xref = Xref, Yref = Yref))[1]

        deaaddcustomcrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, :Custom, rts = :CRS, wX = 1 ./ Xeval, wY = 1 ./ Yeval, Xref = Xref, Yref = Yref))[1]
        deaaddcustomvrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, :Custom, rts = :VRS, wX = 1 ./ Xeval, wY = 1 ./ Yeval, Xref = Xref, Yref = Yref))[1]

        deaaddonescrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, :Ones, rts = :CRS, Xref = Xref, Yref = Yref))[1]
        deaaddonesvrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, :Ones, rts = :VRS, Xref = Xref, Yref = Yref))[1]

        deaaddmipcrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, :MIP, rts = :CRS, Xref = Xref, Yref = Yref))[1]
        deaaddmipvrs_ref_eff[i] = efficiency(deaadd(Xeval, Yeval, :MIP, rts = :VRS, Xref = Xref, Yref = Yref))[1]

    end

    @test deaadddefaultcrs_ref_eff ≈ efficiency(deaadddefaultcrs)
    @test deaadddefaultvrs_ref_eff ≈ efficiency(deaadddefaultvrs)

    @test deaaddcustomcrs_ref_eff ≈ efficiency(deaaddmipcrs)
    @test deaaddcustomvrs_ref_eff ≈ efficiency(deaaddmipvrs)

    @test deaaddonescrs_ref_eff ≈ efficiency(deaaddcrs1)
    @test deaaddonesvrs_ref_eff ≈ efficiency(deaaddvrs1)

    @test deaaddmipcrs_ref_eff ≈ efficiency(deaaddmipcrs)
    @test deaaddmipvrs_ref_eff ≈ efficiency(deaaddmipvrs)

    # Print
    show(IOBuffer(), deaaddcrs1)

    # Test errors
    @test_throws ErrorException deaadd([1; 2 ; 3], [4 ; 5], :Ones) #  Different number of observations
    @test_throws ErrorException deaadd([1; 2], [4 ; 5], Xref = [1; 2; 3; 4], :Ones) # Different number of observations in reference sets
    @test_throws ErrorException deaadd([1 1; 2 2], [4 4; 5 5], Xref = [1 1 1; 2 2 2], :Ones) # Different number of inputs
    @test_throws ErrorException deaadd([1 1; 2 2], [4 4; 5 5], Yref = [4 4 4; 5 5 5], :Ones) # Different number of inputs
    @test_throws ErrorException deaadd([1; 2; 3], [4; 5; 6], :Ones, rts = :Error) # Invalid returns to scale
    @test_throws ErrorException deaadd([1; 2; 3], [4; 5; 6], :Error) # Invalid model

    @test_throws ErrorException deaadd([1; 2 ; 3], [4 ; 5]) #  Different number of observations
    @test_throws ErrorException deaadd([1; 2], [4 ; 5], Xref = [1; 2; 3; 4]) # Different number of observations in reference sets
    @test_throws ErrorException deaadd([1 1; 2 2], [4 4; 5 5], Xref = [1 1 1; 2 2 2]) # Different number of inputs
    @test_throws ErrorException deaadd([1 1; 2 2], [4 4; 5 5], Yref = [4 4 4; 5 5 5]) # Different number of inputs
    @test_throws ErrorException deaadd([1; 2; 3], [4; 5; 6], rts = :Error) # Invalid returns to scale
    @test_throws ErrorException deaadd([1; 2; 3], [4; 5; 6], :Custom, wX = [1; 2; 3; 4]) # Different size of weights
    @test_throws ErrorException deaadd([1; 2; 3], [4; 5; 6], :Custom, wY = [4; 5; 6; 7]) # Different size of weights

end


### Struct variable
struct PowerFlowDC
    main::Array{Float64,2}
    flow::Array{Float64,2}
    generation::Array{Float64,2}
end

### DC power flow
@inbounds function rundcpf(system, num, settings, info)
    printstyled("Algorithm: DC power flow\n"; bold = true)

    ########## Pre-processing ##########
    busi, type, Pload, Gshunt, Tini, Pgeni, genOn, resistance, reactance,
    charging, transTap, transShift, branchOn = read_dcsystem(system)

    main = fill(0.0, num.Nbus, 6)
    flow = fill(0.0, num.Nbranch, 5)
    gene = fill(0.0, num.Ngen, 2)
    Va, Pinj, Pbus, Pij, Pji, Pgen = write_dcsystem(system, num, main, flow, gene)

  algtime = @elapsed begin
    ########## Convert in integers ##########
    geni = convert(Array{Int64,1}, system.generator[:, 1])
    fromi = convert(Array{Int64,1}, system.branch[:, 2])
    toi = convert(Array{Int64,1}, system.branch[:, 3])

    ########## Numbering and slack bus ##########
    numbering = false
    bus = collect(1:num.Nbus)
    slack = 0
    for i = 1:num.Nbus
        if bus[i] != busi[i]
            numbering = true
        end
        if type[i] == 3
            slack = bus[i]
        end
    end
    if numbering
        println("The new bus numbering is running.")
    end
    if slack == 0
        slack = 1
        println("The slack bus is not found. Slack bus is the first bus.")
    end

    gen_bus = renumber(geni, busi, bus, numbering)
    from = renumber(fromi, busi, bus, numbering)
    to = renumber(toi, busi, bus, numbering)

    ########## Generation ##########
    for (k, i) in enumerate(gen_bus)
        if genOn[k] == 1
            Pbus[i] += Pgeni[k] / system.basePower
            Pgen[k] = Pgeni[k]
        end
    end

    ########## Ybus matrix ##########
    Ybus, admitance, Pshift = ybusdc(system, num, bus, from, to, branchOn, transTap, reactance, transShift)

    ######### Remove row/column of the slack bus, but keep column rank ##########
    rowSlack = Ybus[:, slack]
    for i in Ybus.colptr[slack]:(Ybus.colptr[slack + 1] - 1)
        row = Ybus.rowval[i]
        Ybus[row, slack] = 0.0; Ybus[slack, row] = 0.0
    end
    Ybus[slack, slack] = 1.0

    ########## Solve the system ##########
    b = Pbus - Pshift - (Pload + Gshunt) ./ system.basePower
    Ti = ls(Ybus, b, settings.solve)

    Ti[slack] = 0.0
    Ti = (pi / 180) * Tini[slack] .+ Ti

    ########## Post-processing ##########
    for i = 1:num.Nbranch
        if branchOn[i] == 1
            Pij[i] = admitance[i] * (Ti[from[i]] - Ti[to[i]] - (pi / 180) * transShift[i]) * system.basePower
            Pji[i] = -Pij[i]
        end
    end

    for i = 1:num.Nbus
        Va[i] = (180 / pi) * Ti[i]
        Pbus[i] = Pbus[i] * system.basePower
        Pinj[i] = Pbus[i] - Pload[i]
        if i == slack
            I = 0.0
            for j in Ybus.colptr[i]:(Ybus.colptr[i + 1] - 1)
                row = Ybus.rowval[j]
                I += rowSlack[row] * Ti[row]
            end
            Pinj[slack] = (I + Pshift[slack]) * system.basePower + Gshunt[slack]
            Pbus[slack] = Pinj[slack] + Pload[slack]
        end
    end

    flag = true
    tempslack = 0
    for i = 1:num.Ngen
        if genOn[i] == 1
            if gen_bus[i] == slack && flag == false
                Pgen[tempslack] -= Pgen[i]
            end
            if gen_bus[i] == slack && flag == true
                Pgen[i] = Pbus[slack]
                tempslack = i
                flag = false
            end
        end
    end
 end # algtime

    ########## Results ##########
    results = PowerFlowDC(main, flow, gene)
    header, group = results_flowdc(system, num, settings, results, slack, algtime)
    if !isempty(settings.save)
        savedata(results, system; info = info, group = group, header = header, path = settings.save)
    end

    return results
end

### Read data
function read_dcsystem(system)
    busi = @view(system.bus[:, 1])
    type = @view(system.bus[:, 2])
    Pload = @view(system.bus[:, 3])
    Gshunt = @view(system.bus[:, 5])
    Tini = @view(system.bus[:, 9])

    Pgeni = @view(system.generator[:, 2])
    genOn = @view(system.generator[:, 8])

    resistance = @view(system.branch[:, 4])
    reactance = @view(system.branch[:, 5])
    charging = @view(system.branch[:, 6])
    transTap = @view(system.branch[:, 10])
    transShift = @view(system.branch[:, 11])
    branchOn = @view(system.branch[:, 12])

    return busi, type, Pload, Gshunt, Tini, Pgeni, genOn,
    resistance, reactance, charging, transTap, transShift, branchOn
end

### Write data
function write_dcsystem(system, num, main, flow, gene)
    for i = 1:num.Nbus
        main[i, 1] = system.bus[i, 1]
        main[i, 5] = system.bus[i, 3]
        main[i, 6] = system.bus[i, 5]
    end
    Va = @view(main[:, 2])
    Pinj = @view(main[:, 3])
    Pbus = @view(main[:, 4])

    for i = 1:num.Nbranch
        flow[i, 1] = system.branch[i, 1]
        flow[i, 2] = system.branch[i, 2]
        flow[i, 3] = system.branch[i, 3]
    end
    Pij = @view(flow[:, 4])
    Pji = @view(flow[:, 5])

    for i = 1:num.Ngen
        gene[i, 1] = system.generator[i, 1]
    end
    Pgen = @view(gene[:,2])

    return Va, Pinj, Pbus, Pij, Pji, Pgen
end


# Implements Bayesian networks

module BayesianNetworks

using SPNetworks
import SPNetworks: Node, SumNode, ProductNode, LeafNode, IndicatorFunction

"Tree-shaped Bayesian network. A tree is conneted digraph where each node has at most one parent."
abstract type BayesianTree end
struct BTRoot <: BayesianTree # root node
    variable::Int
    factor::Vector{Float64} # array of unconditional probabilities
    children::Vector{BayesianTree}
end
struct BTNode <: BayesianTree # inner node
    variable::Int
    factor::Matrix{Float64} # square matrix where columns are conditional probability distributions given each value of the parent
    children::Vector{BayesianTree}
end
struct BTLeaf <: BayesianTree # leaf node
    variable::Int
    factor::Matrix{Float64}
end

"Compile Bayesian Tree into SPN."
function compile(root::BTRoot)
    # init cache of conditional SPNs
    cache = Dict{Tuple{Int,Int,Int},Int}() # (node.var,parent.var,value) -> node id
    # init cache of indicators
    icache = Dict{Tuple{Int,Int},Int}()    # (node.var,value) -> node id
    # root node
    sumnode = SumNode(Vector{UInt}(undef,size(root.factor,1)),root.factor)
    # list of nodes
    nodelist = Node[ sumnode ]
    # apply Shannon-like decomposition recursively
    for value=1:size(root.factor,1)
        # Decompose on root.variable = value
        prodnode = ProductNode([])
        push!(nodelist, prodnode)
        sumnode.children[value] = length(nodelist) # set child id
        push!(nodelist, IndicatorFunction(root.variable,value))
        push!(prodnode.children,length(nodelist))
        for child in root.children
            # recur on child
            nodeid = compile!(cache,icache,nodelist,child,root,value)
            push!(prodnode.children,nodeid)
        end
    end
    # return compiled SPN
    spn = SumProductNetwork(nodelist)
    # node ids are not topologically orders - sort in-place nodes in bfs-order
    SPNetworks.sort!(spn)
    # return sorted network
    spn
end
"
    compile!(cache::Dict{Tuple{Int,Int,Int},Int},icache::Dict{Tuple{Int,Int}},nodelist::Vector{Node},node::BayesianTree,parent,pavalue)

Compile subtree rooted at node and adds respective nodes to nodelist. Returns id of root node of subSPN.
Caches the node id given the parent id and value to avoid duplication.
"
function compile!(cache::Dict{Tuple{Int,Int,Int},Int},icache::Dict{Tuple{Int,Int}},nodelist::Vector{Node},node::BTLeaf,parent,pavalue)
    if haskey(cache,(node.variable,parent.variable,pavalue))
        return cache[(node.variable,parent.variable,pavalue)]
    end
    sumnode = SumNode(Vector{UInt}(undef,size(node.factor,1)),node.factor[:,pavalue])
    push!(nodelist, sumnode)
    cache[(node.variable,parent.variable,pavalue)] = length(nodelist)
    for value = 1:size(node.factor,1)
        push!(nodelist,IndicatorFunction(node.variable,value))
        sumnode.children[value] = length(nodelist) # set child id
    end
    # return root node id
    cache[(node.variable,parent.variable,pavalue)]
end
function compile!(cache::Dict{Tuple{Int,Int,Int},Int},icache::Dict{Tuple{Int,Int}},nodelist::Vector{Node},node::BTNode,parent,pavalue)
    if haskey(cache,(node.variable,parent.variable,pavalue))
        return cache[(node.variable,parent.variable,pavalue)]
    end
    sumnode = SumNode(Vector{UInt}(undef,size(node.factor,1)),node.factor[:,pavalue])
    push!(nodelist, sumnode)
    cache[(node.variable,parent.variable,pavalue)] = length(nodelist)
    for value=1:size(node.factor,1)
        prod = ProductNode([])
        push!(nodelist, prod)
        sumnode.children[value] = length(nodelist) # set child id
        if haskey(icache,(node.variable,value)) # recover indicator if already created
            push!(prod.children, icache[(node.variable,value)])
        else # new indicator
            push!(nodelist, IndicatorFunction(node.variable,value))
            icache[(node.variable,value)] = length(nodelist)
            push!(prod.children, length(nodelist))
        end
        for child in node.children
            # recur
            nodeid = compile!(cache,icache,nodelist,child,node,value)
            push!(prod.children, nodeid)
        end
    end
    # return root node id
    cache[(node.variable,parent.variable,pavalue)]
end

end


export project_annulus!

function project_annulus!(
                     x     ::Union{Vector{TF},Vector{Complex{TF}}},
                     sigma_min ::TF,         #minimum 2-norm ||x||_2,
                     sigma_max ::TF          #maximum 2-norm ||x||_2
                     ) where {TF<:Real}

nl2 = norm(x,2)
if sigma_min <= nl2 <= sigma_max
  return x
elseif nl2 > sigma_max
  rmul!(x,sigma_max/nl2)
elseif nl2 < sigma_min && nl2>0
  rmul!(x,sigma_min/nl2)
elseif nl2 < sigma_min && nl2==0
  copy!(x,ones(TF,length(x)) .* (sigma_min ./ sqrt(length(x))))
end

return x
end


"""
    minimumdistance(myModel::CPLEX.Model, timeStart::UInt, timeLim::Float64,
        xTilde::Vector{Float64}, indices::Vector{Int})

Replace the objective function to minimize the L1-distance to xTilde. Then
optimize to return xOverline.
"""
function minimumdistance(
        myModel::CPLEX.Model,
        timeStart::UInt,
        timeLim::Float64,
        xTilde::Vector{Float64},
        indices::Vector{Int},
        opts...
        ;
        compile::Bool = false
    )
    if compile
        return [NaN]
    end

    nVars = length(xTilde)
    objectiveFunction = zeros(Float64, nVars)
    for i in indices
        if xTilde[i] > 0.5
            objectiveFunction[i] = -1.0
        else
            objectiveFunction[i] = 1.0
        end
    end
    CPLEX.set_obj!(myModel, objectiveFunction)

    CPLEX.set_param!(myModel.env, "CPX_PARAM_TILIM", maximum([0, timeLim - (time_ns() - timeStart) / 1.0e9]))
    CPLEX.optimize!(myModel)

    xOverline = zeros(Float64, nVars)
    if CPLEX.get_status(myModel) == :CPX_STAT_OPTIMAL
        xOverline = CPLEX.get_solution(myModel)
    end

    return xOverline
end

"""
    minimumdeltaalpha(myModel::CPLEX.Model, timeStart::UInt, timeLim::Float64,
        xTilde::Vector{Float64}, indices::Vector{Int},
        initObjectiveFunction::Vector{Float64}, alpha::Float64, coef::Float64)

Replace the objective function to minimize the L1-distance to xTilde taking into
account the initial objective function. Then optimize to return xOverline.
"""
function minimumdeltaalpha(
        myModel::CPLEX.Model,
        timeStart::UInt,
        timeLim::Float64,
        xTilde::Vector{Float64},
        indices::Vector{Int},
        initObjectiveFunction::Vector{Float64},
        alpha::Float64,
        coef::Float64,
        opts...
        ;
        compile::Bool = false
    )
    if compile
        return [NaN]
    end

    nVars = length(xTilde)

    # objectiveFunction minimumdistance
    objectiveFunction = zeros(Float64, nVars)
    for i in indices
        if xTilde[i] > 0.5
            objectiveFunction[i] = -1.0
        else
            objectiveFunction[i] = 1.0
        end
    end

    # Add objective function and alpha to it
    objectiveFunction .= (objectiveFunction .* (1-alpha)) .+ ((alpha*coef) .* initObjectiveFunction)
    CPLEX.set_obj!(myModel, objectiveFunction)

    CPLEX.set_param!(myModel.env, "CPX_PARAM_TILIM", maximum([0, timeLim - (time_ns() - timeStart) / 1.0e9]))
    CPLEX.optimize!(myModel)

    xOverline = zeros(Float64, nVars)
    if CPLEX.get_status(myModel) == :CPX_STAT_OPTIMAL
        xOverline = CPLEX.get_solution(myModel)
    end

    return xOverline
end

"""
    minimumdistance_reduce(myModel::CPLEX.Model, timeStart::UInt,
        timeLim::Float64, xTilde::Vector{Float64}, indices::Vector{Int})

Replace the objective function to minimize the L1-distance to xTilde. Multiply
the new objective function by the non zeros reduced costs. Then optimize to
return xOverline.
"""
function minimumdistance_reduce(
        myModel::CPLEX.Model,
        timeStart::UInt,
        timeLim::Float64,
        xTilde::Vector{Float64},
        indices::Vector{Int},
        opts...
        ;
        compile::Bool = false
    )
    if compile
        return [NaN]
    end

    nVars = length(xTilde)
    reducedCosts = Vector{Float64}()
    try
        reducedCosts = CPLEX.get_reduced_costs(myModel)
    catch
        reducedCosts = zeros(nVars)
    end
    objectiveFunction = zeros(Float64, nVars)
    for i in indices
        if xTilde[i] > 0.5
            objectiveFunction[i] = -1.0
        else
            objectiveFunction[i] = 1.0
        end
        if abs(reducedCosts[i]) > 1e-8
            objectiveFunction[i] *= abs(reducedCosts[i])
        end
    end
    CPLEX.set_obj!(myModel, objectiveFunction)

    CPLEX.set_param!(myModel.env, "CPX_PARAM_TILIM", maximum([0, timeLim - (time_ns() - timeStart) / 1.0e9]))
    CPLEX.optimize!(myModel)

    xOverline = zeros(Float64, nVars)
    if CPLEX.get_status(myModel) == :CPX_STAT_OPTIMAL
        xOverline = CPLEX.get_solution(myModel)
    end

    return xOverline
end


mutable struct KetState{T<:RLorCX}
    lx :: Int
    d  :: Int
    v  :: Vector{T}
end

function randket(T::Type, rng::AbstractRNG, lx::Int, d::Int)# where {T<:RLorCX}
    v = randn(rng, T, d^lx)
    KetState{T}(lx, d, v)
end

randket(rng::AbstractRNG, lx::Int, d::Int) = randket(Float64, rng, lx, d)

normalize!(v::KetState{T}) where {T<:RLorCX} = normalize!(v.v)

@inline opextend(op, lx::Int, l::Int) = eye(2^(l-1)) ⊗ op ⊗ eye(2^(lx-l))

function apply1site(v::KetState{T}, l::Int, op::SparseMatrixCSC{T,Int}) where {T<:RLorCX}
    size(op) != (v.d, v.d) && error("Operator size not correct!")
    (0 < l <= v.lx) || error("position l is not within the lattice")

    KetState(v.lx, v.d, opextend(op, v.lx, l) * v.v)
end

apply1site(v::KetState{ComplexF64}, l::Int, op::SparseMatrixCSC{Float64, Int}) =
    apply1site(v,l, convert(SparseMatrixCSC{ComplexF64,Int}, op))

function measure1point(v::KetState{T}, op::SparseMatrixCSC{T,Int}) where {T<:RLorCX}
    result = Vector{T}(undef, v.lx)
    for l in 1:v.lx
        result[l] = dot(v.v, opextend(op, v.lx, l) * v.v)
    end
    result
end

measure1point(v::KetState{ComplexF64}, op::SparseMatrixCSC{Float64, Int}) =
    measure1point(v, convert(SparseMatrixCSC{ComplexF64,Int}, op))


using NetworkEpidemics
using LightGraphs
using Statistics
using Plots
using ColorSchemes

CorrectedMetapopulation(χ::Real, mp::Metapopulation{SI}) = Metapopulation(mp.h, mp.D, SI(χ*mp.dynamics.β))

CorrectedMetapopulation(χ::Real, mp::Metapopulation{SIS}) = Metapopulation(mp.h, mp.D, SIS(χ*mp.dynamics.β, mp.dynamics.γ))

CorrectedMetapopulation(χ::Real, mp::Metapopulation{SIR}) = Metapopulation(mp.h, mp.D, SIR(χ*mp.dynamics.β, mp.dynamics.δ))

N = 1000
M = 10

h = complete_graph(M)
g = erdos_renyi(N, 0.3)
g = random_regular_graph(N, 50)
g = static_scale_free(N, Int(20*N), 2)
g = star_graph(N)
g = watts_strogatz(N, 100, 0.1)
g = cycle_graph(N)

is_connected(g)

k = degree(g)

χ₁ = mean(closeness_centrality(g))
χ₂ = mean(k.^2)/(mean(k)*N)
χ₃ = mean(k)/N #ne(g)/(n*(n-1)/2)
χ₄ = global_clustering_coefficient(g)

β = 0.1
γ = 0.2
D = 0.1

mp = Metapopulation(h, D, SIR(β,γ))
mp_1 = CorrectedMetapopulation(χ₁, mp)
mp_2 = CorrectedMetapopulation(χ₂, mp)
mp_3 = CorrectedMetapopulation(χ₃, mp)
mp_4 = CorrectedMetapopulation(χ₄, mp)

mpx = Metaplex(g, h, D, SIR(β,γ))

x0_i = fill(1, N)
x0_i[1:10] .= 2
x0_μ = [div(i-1,div(N,M))+1 for i in 1:N]

x0_mp = Array{Int,2}(undef, M,3)
x0_mp[2:M,:] .= [100 0 0]
x0_mp[1, :] .= [90, 10, 0]

x0_i = fill(1, N)
x0_i[1:10] .= 2
x0_μ = rand(1:M, N)
x0_mp = zeros(Int, M, 3)
for i in 1:N
    x0_mp[x0_μ[i], x0_i[i]] += 1
end

x0_mp

tmax = 50.0
nmax = 1000000
nsims = 200
nbins = 200

ts, output = gillespie(mp, x0_mp, tmax=tmax, nmax=nmax)
ts_mpx, output_mpx = gillespie(mpx, [x0_i,x0_μ], tmax=tmax, nmax=nmax)

plot(ts, output[2], label="")
plot(ts_mpx, output_mpx[2], label="")

ts_av, u_av = average(mp, x0_mp, tmax=tmax, nmax=nmax, nbins=nbins, nsims=nsims)
ts_av_1, u_av_1 = average(mp_1, x0_mp, tmax=tmax, nmax=nmax, nbins=nbins, nsims=nsims)
ts_av_2, u_av_2 = average(mp_2, x0_mp, tmax=tmax, nmax=nmax, nbins=nbins, nsims=nsims)
ts_av_3, u_av_3 = average(mp_3, x0_mp, tmax=tmax, nmax=nmax, nbins=nbins, nsims=nsims)
ts_av_4, u_av_4 = average(mp_4, x0_mp, tmax=tmax, nmax=nmax, nbins=nbins, nsims=nsims)
ts_av_mpx, u_av_mpx = average(mpx, [x0_i,x0_μ], tmax=tmax, nmax=nmax, nbins=nbins, nsims=nsims)

ts_mf, u_mf = meanfield(mp, x0_mp, tmax=tmax, saveat=ts_av)
ts_mf_1, u_mf_1 = meanfield(mp_1, x0_mp, tmax=tmax, saveat=ts_av_1)
ts_mf_2, u_mf_2 = meanfield(mp_2, x0_mp, tmax=tmax, saveat=ts_av_2)
ts_mf_3, u_mf_3 = meanfield(mp_3, x0_mp, tmax=tmax, saveat=ts_av_3)
ts_mf_4, u_mf_4 = meanfield(mp_4, x0_mp, tmax=tmax, saveat=ts_av_4)
ts_mf_mpx, u_mf_mpx = meanfield(mpx, [x0_i,x0_μ], tmax=tmax, saveat=ts_av_mpx)

colors = ColorSchemes.tab10;

plot(ts_av, sum(u_av[2], dims=2)/N,
    label="Metapopulation",
    xlabel="Time",
    ylabel="Fraction of infected individuals",
    linestyle=:dash,
    legend=:bottomright,
    fontfamily="Deja Vu",
    color = colors[1]
    )
    plot!(ts_av_mpx, sum(u_av_mpx[2], dims=2)/N,
    label="Metaplex",
    linestyle=:dash,
    color = colors[2]
    )
    plot!(ts_av_1, sum(u_av_1[2], dims=2)/N,
    label="Closeness Centrality",
    linestyle=:dash,
    color = colors[3]
    )
    plot!(ts_av_2, sum(u_av_2[2], dims=2)/N,
    label="Second Moment",
    linestyle=:dash,
    color = colors[4]
    )
    plot!(ts_av_3, sum(u_av_3[2], dims=2)/N,
    label="Mean Degree",
    linestyle=:dash,
    color = colors[5]
    )
    plot!(ts_av_4, sum(u_av_4[2], dims=2)/N,
    label="Clustering Coefficient",
    linestyle=:dash,
    color = colors[6]
    )
    plot!(ts_mf, sum(u_mf[2], dims=2)/N,
    #label="Meanfield (No correction)",
    label = "",
#    xlabel="Time",
#    ylabel="Fraction of infected individuals",
    #legend=:bottomright,
    color = colors[1]
    )
    plot!(ts_mf_mpx, sum(u_mf_mpx[2], dims=2)/N,
#    label="Meanfield (Metaplex)",
    label = "",
    color = colors[2]
    )
    plot!(ts_mf_1, sum(u_mf_1[2], dims=2)/N,
#    label="Meanfield (Closeness Centrality)",
    label = "",
    color = colors[3]
    )
    plot!(ts_mf_2, sum(u_mf_2[2], dims=2)/N,
#    label="Meanfield (Second Moment)",
    label = "",
    color = colors[4]
    )
    plot!(ts_mf_3, sum(u_mf_3[2], dims=2)/N,
#    label="Meanfield (Mean degree)",
    label = "",
    color = colors[5]
    )
    plot!(ts_mf_4, sum(u_mf_4[2], dims=2)/N,
#    label="Meanfield (Clustering Coefficient)",
    label = "",
    color = colors[6]
    )

cols = map(x->[x.r, x.g, x.b], colors.colors)

using MATLAB

mat"""
figure
xlabel('Time')
ylabel('Fraction of Infected Individuals')
hold on
plot($ts_av, $(sum(u_av[2], dims=2)/N), 'LineStyle', '--', 'Color', $(cols[1]))
plot($ts_av_mpx, $(sum(u_av_mpx[2], dims=2)/N), 'LineStyle', '--', 'Color', $(cols[2]))
plot($ts_av_1, $(sum(u_av_1[2], dims=2)/N), 'LineStyle', '--', 'Color', $(cols[3]))
plot($ts_av_2, $(sum(u_av_2[2], dims=2)/N), 'LineStyle', '--', 'Color', $(cols[4]))
plot($ts_av_3, $(sum(u_av_3[2], dims=2)/N), 'LineStyle', '--', 'Color', $(cols[5]))
plot($ts_av_4, $(sum(u_av_4[2], dims=2)/N), 'LineStyle', '--', 'Color', $(cols[6]))

plot($ts_mf, $(sum(u_mf[2], dims=2)/N), 'Color', $(cols[1]))
plot($ts_mf_mpx, $(sum(u_mf_mpx[2], dims=2)/N), 'Color', $(cols[2]))
plot($ts_mf_1, $(sum(u_mf_1[2], dims=2)/N), 'Color', $(cols[3]))
plot($ts_mf_2, $(sum(u_mf_2[2], dims=2)/N), 'Color', $(cols[4]))
plot($ts_mf_3, $(sum(u_mf_3[2], dims=2)/N), 'Color', $(cols[5]))
plot($ts_mf_4, $(sum(u_mf_4[2], dims=2)/N), 'Color', $(cols[6]))
hold off
legend('Metapopulation', 'Metaplex', 'Closeness Centrality', 'Second Moment', 'Mean Degree', 'Clustering Coefficient')
"""


@symbol_func function K_kappa(cur_reactor::AbstractReactor)
  cur_nu_n = cur_reactor.nu_n

  cur_nu_T = cur_reactor.nu_T

  cur_K = 0.4744

  cur_K *= ( 1 + cur_reactor.f_D )

  cur_K *= ( 1 + cur_nu_n )

  cur_K *= ( 1 + cur_nu_T )

  cur_K /= 1 + cur_nu_n + cur_nu_T

  cur_K *= cur_reactor.epsilon ^ 2

  cur_K *= cur_reactor.kappa_95

  cur_K *= g(cur_reactor)

  cur_K
end


function add_triangle!(point::Vector{SVector{3,Float64}}, i::Vector{SVector{3,Int64}}, a::SVector{3,Float64},
        b::SVector{3,Float64}, c::SVector{3,Float64})

    push!(i, SVector{3,Int64}(1, 2, 3) .+ length(point))
    push!(point, a, b, c)
    return nothing
end

function add_rectangle!(point::Vector{SVector{3,Float64}}, i::Vector{SVector{3,Int64}}, a⁻::SVector{3,Float64},
        a⁺::SVector{3,Float64}, b⁻::SVector{3,Float64}, b⁺::SVector{3,Float64})

    add_triangle!(point, i, a⁻, a⁺, b⁺)
    add_triangle!(point, i, a⁻, b⁺, b⁻)
end

function add_slice(point::Vector{SVector{3,Float64}}, i::Vector{SVector{3,Int64}}, v::NTuple{N,SVector{3,Float64}},
        θ⁰::Float64, θ¹::Float64) where {N}
    # adds a rotationally symmetric slice of point sequence v to the vector of points (point) and vector of indices i

    function add_rot!(point::Vector{SVector{3,Float64}}, i::Vector{SVector{3,Int64}}, a::SVector{3,Float64},
            b::SVector{3,Float64}, RZ_0::RotZ{Float64}, RZ_1::RotZ{Float64})

        a⁻ = RZ_0 * a
        a⁺ = RZ_1 * a
        b⁻ = RZ_0 * b
        b⁺ = RZ_1 * b

        # TODO: is this necessary given mesh repair_mesh?
        if (a[1] == 0.0)  # && (a[2] == 0.0)  # point has no xy extent
            add_triangle!(point, i, a, b⁺, b⁻)
        elseif (b[1] == 0.0)  # && (b[2] == 0.0)
            add_triangle!(point, i, a⁻, a⁺, b)
        else
            add_rectangle!(point, i, a⁻, a⁺, b⁻, b⁺)
        end
    end

    RZ_0 = RotZ(θ⁰)
    RZ_1 = RotZ(θ¹)
    for k = 1:(N - 1)
        add_rot!(point, i, v[k], v[k + 1], RZ_0, RZ_1)
    end
end

function obj_from_point_sequence(point_vec_2D::Vector{SVector{2,Float64}}, n_theta::Int64=10)
    # creates a rotationally symmetric object by rotating a vector of 2D points (x,z) about the z axis to create a
    # vector of 3D points (x,y,z)

    ### FIXES degenerate points near the axis of rotation
    for k = 1:length(point_vec_2D)
        item_1, item_2 = point_vec_2D[k]
        tol = 1.0e-12
        if item_1 <= -tol
            error("negative extent")
        # elseif item_1 == 0.0
        #     #
        # elseif item_1 <= tol
        elseif 0.0 < item_1 <= tol
            @warn("point near rotation axis set to 0.0")
            point_vec_2D[k] = SVector{2,Float64}(0.0, item_2)
        end
    end

    point = Vector{SVector{3,Float64}}()
    i = Vector{SVector{3,Int64}}()
    point_vec = [SVector{3,Float64}(k[1], 0.0, k[2]) for k = point_vec_2D]

    theta = collect(LinRange{Float64}(0, 2*pi, n_theta + 1)) .+ (pi/2)
    for k = 1:n_theta
        add_slice(point, i, Tuple(point_vec), theta[k], theta[mod1(k + 1, n_theta)])
    end

    hex_mesh = eMesh(point, i, nothing, nothing)
    mesh_repair!(hex_mesh)
    return hex_mesh
end


using OrdinaryDiffEq, StaticArrays, Test

function time_derivative(du,u,p,t)
  du[1] = -t
end
function time_derivative_static(u,p,t)
  SVector(-t)
end
function time_derivative_analytic(u0,p,t)
  u0 .- t.^2 ./ 2
end

const CACHE_TEST_ALGS = [Euler(),Midpoint(),RK4(),SSPRK22(),SSPRK33(),SSPRK53(),
  SSPRK63(),SSPRK73(),SSPRK83(),SSPRK43(),SSPRK432(),SSPRK932(),SSPRK54(),SSPRK104(),CarpenterKennedy2N54(),
  BS3(),BS5(),DP5(),DP8(),Feagin10(),Feagin12(),Feagin14(),TanYam7(),
  Tsit5(),TsitPap8(),Vern6(),Vern7(),Vern8(),Vern9(),OwrenZen3(),OwrenZen4(),OwrenZen5()]

tspan = (0.0,1.0)

for (ff_time_derivative, u0) in
  ((ODEFunction(time_derivative, analytic=time_derivative_analytic), [1.0]),
   (ODEFunction(time_derivative_static, analytic=time_derivative_analytic), SVector(1.0)))

  @info "StaticArrays?: $(u0 isa StaticArray)"

  prob = ODEProblem(ff_time_derivative,u0,tspan)

  for _autodiff in (true, false)
    @info "autodiff=$(_autodiff)"

    @show Rosenbrock23
    sol = solve(prob,Rosenbrock32(autodiff=_autodiff),reltol=1e-9,abstol=1e-9)
    @test sol.errors[:final] < 1e-5*10^(!_autodiff)
    sol = solve(prob,Rosenbrock23(autodiff=_autodiff),reltol=1e-9,abstol=1e-9)
    @test sol.errors[:final] < 1e-10*10_000^(!_autodiff)

    @show Rodas4
    sol = solve(prob,Rodas4(autodiff=_autodiff),reltol=1e-9,abstol=1e-9)
    @test sol.errors[:final] < 1e-10*100_000^(!_autodiff)

    @show Rodas5
    sol = solve(prob,Rodas5(autodiff=_autodiff),reltol=1e-9,abstol=1e-9)
    @test sol.errors[:final] < 1e-10*1_000_000_000^(!_autodiff)

    @show Veldd4
    sol = solve(prob,Veldd4(autodiff=_autodiff),reltol=1e-9,abstol=1e-9)
    @test sol.errors[:final] < 1e-10*100_000^(!_autodiff)

    @show KenCarp3
    sol = solve(prob,KenCarp3(autodiff=_autodiff),reltol=1e-12,abstol=1e-12)
    @test length(sol) > 2
    @test sol.errors[:final] < 1e-10

    @show KenCarp4
    sol = solve(prob,KenCarp4(autodiff=_autodiff),reltol=1e-12,abstol=1e-12)
    @test length(sol) > 2
    @test sol.errors[:final] < 1e-10

    @show KenCarp47
    sol = solve(prob,KenCarp47(autodiff=_autodiff),reltol=1e-12,abstol=1e-12)
    @test length(sol) > 2
    @test sol.errors[:final] < 1e-10

    @show KenCarp5
    sol = solve(prob,KenCarp5(autodiff=_autodiff),reltol=1e-12,abstol=1e-12)
    @test length(sol) > 2
    @test sol.errors[:final] < 1e-10

    @show KenCarp58
    sol = solve(prob,KenCarp58(autodiff=_autodiff),reltol=1e-12,abstol=1e-12)
    @test length(sol) > 2
    @test sol.errors[:final] < 1e-10

    @show TRBDF2
    sol = solve(prob,TRBDF2(autodiff=_autodiff),reltol=1e-9,abstol=1e-9)
    @test sol.errors[:final] < 1e-10

    @show ImplicitEuler
    sol = solve(prob,ImplicitEuler(autodiff=_autodiff),dt=1/10)
    @test sol.errors[:final] < 1e-1

    @show Trapezoid
    sol = solve(prob,Trapezoid(autodiff=_autodiff),dt=1/10)
    @test sol.errors[:final] < 1e-12
  end

  @show Euler
  sol = solve(prob,Euler(),dt=1/100)
  @test sol.errors[:final] < 6e-3

  for alg in CACHE_TEST_ALGS
    @show alg
    sol = solve(prob,alg,dt=1/10)
    if !(typeof(alg) <: Euler)
      @test sol.errors[:final] < 4e-14
    end
  end
end


module AccurateArithmetic

include("EFT.jl")
using .EFT
export two_sum, two_prod

include("Summation.jl")
using .Summation
export sum_naive, sum_kbn, sum_oro, sum_mixed
export dot_naive, dot_oro, dot_mixed

include("Test.jl")

end # module


export TR_generic_ls
"""
A generic one dimensionnal Trust Region algorithm adapted for line search.
In this generic version we assume a second order polynomial is used to
approximate our h function. A third degree approximation is used in TR_Cub_ls
We use q(d) = h(θ) + h'(θ)⋆d + 0.5 * h''(θ) ⋆ d². Also h''(θ) is sometimes
approximated by a secant (Sec) or an improved secant (SecA). That approximation
is decided with hyper parameter "direction"

Some specific hyper parameter to this line search:
symmetrical :: The method is designed to used an asymmetrical  trust region
to avoid having a negative step size. If symmetrical true then the trust region
becomes symmetrical

Some parameters are used for TR and ARC methods
eps1 and eps2 :: treshold to determine if q is a bad or a good++ approximation
of h
red and aug :: Controls the speed at which we reduce or augment the size of
the trust region depending on the previously mentionned treshold.

All the parameters mentionned above can be found in the LS_Function_Meta
"""
function TR_generic_ls(h :: LineModel,  stop_ls :: LS_Stopping;
                       f_meta = LS_Function_Meta(),
                       φ_dφ        :: Function = (x, y) -> phi_dphi(x, y),
                       verboseLS   :: Bool = false, symmetrical :: Bool = false,
                       kwargs...)

  state = stop_ls.current_state

  tmp = obj(h, 1.0); tmpg = grad(h, 1.0)
  # # Check if 1.0 is an admissible step size.
  OK = update_and_start!(stop_ls, ht = tmp, gt = tmpg, tmps = time())

  # If 1.0 isn't an admissible step size we start our line search algorithm
  iter = 0

  # If 1.0 is a good step size we don't want to compute  unnecessary function
  # evaluations
  H = NaN; φt = NaN; dφt = NaN                   # only for the verbose

  #if OK == false
    φt, dφt = φ_dφ(h, state) #Tangi: Ici on recalcule la fct objectif ?

    tprec = NaN; φtestTR = NaN; dφtestTR = NaN; φtprec = NaN; dφtprec = NaN;

    # H will denote the approximation to φ'' hereafter
    if f_meta.dir == :Nwt
      H = hess(h, state.x)
    elseif f_meta.dir == :Sec || f_meta.dir == :SecA
      H = 1.0
    end
  #end # if OK == false

  # Quadratic approximation of h
  # q(d) = φt + dφt * d + 0.5 * H * d^2

  verboseLS &&
    @printf("  iter   t       φt        dφt        ddφt        Δn         Δp")
  verboseLS &&
    @printf("   Successful        dN        ratio\n")
  verboseLS &&
    @printf("%4d %9.2e  %9.2e %9.2e %9.2e %9.2e %9.2e\n", iter, state.x, φt, dφt, H,
                                                          f_meta.Δn, f_meta.Δp);

  while !OK #admissible: Armijo & Wolfe, tired: iterations
      # We select our descent direction. We compute the classical Newton
      # direction and then we check we the bounds of our trust region
      dN = -dφt / H
      d = TR_ls_step_computation(H, dφt, dN, f_meta.Δn, f_meta.Δp)

      # We see where the direction d gets us
      candidate_state = copy(state)
      update!(candidate_state, x = state.x + d)
      φtestTR, dφtestTR = φ_dφ(h, candidate_state; kwargs...)

      # depending on the approximation of the second derivate we need different
      # information
      if f_meta.dir == :Sec || f_meta.dir == :SecA
        tprec = state.x
        dφtprec = dφt
      end
      if f_meta.dir == :SecA φtprec = φt end

      # We compute the predicted and actual reduction as well as the ratio
      # between the two.
      (pred, ared, ratio) = pred_ared_computation(dφt, φt, H, d, φtestTR,
                                                  dφtestTR)
      # mettre le if qui suit dans une fonction pour aléger le code.
      if ratio < f_meta.eps1  # Unsuccessful approximation => reduce interval
        f_meta.Δp = f_meta.red * f_meta.Δp
        f_meta.Δn = f_meta.red * f_meta.Δn
        iter += 1
        verboseLS && @printf("%4d %9.2e %9.2e  %9.2e %9.2e %9.2e %9.2e %9e %9.2e %9e\n",
                              iter, state.x, φt, dφt, H, f_meta.Δn, f_meta.Δp, 0, dN, ratio);
      else             # Successful approximation => we move towards an
                       # admissible step size
        stop_ls.current_state = copy(candidate_state)
        state = stop_ls.current_state
        φt, dφt = φtestTR, dφtestTR
        H = update_H(f_meta.dir, h, state.x, tprec, φt, dφt, φtprec, dφtprec)

        if symmetrical        # We adjust our interval if we want it symmetrical
                              # or not
          if ratio > f_meta.eps2 # Very Successful iteration => augment size of
                                 # trust region
            f_meta.Δp = f_meta.aug * f_meta.Δp
            f_meta.Δn = f_meta.aug * f_meta.Δn
          end
        else
          if ratio > f_meta.eps2  # Very Successful iteration=> augment size of
                                  # trust region
            f_meta.Δp = f_meta.aug * f_meta.Δp
            f_meta.Δn = min(-state.x, f_meta.Δn)
          else
            f_meta.Δn = min(-state.x, f_meta.Δn)
          end
        end # à mettre dans une fonction

          iter += 1 # à mettre dans ls_at_t a un certain point

          verboseLS &&
            @printf("%4d %9.2e %9.2e  %9.2e %9.2e %9.2e %9.2e %9e %9.2e  %9e\n",
                    iter, state.x, φt, dφt, H, f_meta.Δn, f_meta.Δp, 1, dN, ratio);
      end;
      OK = stop!(stop_ls)
  end;

  return (state, stop_ls.meta.optimal)

end # function


using BlackBoxOptim

const opt_methods = (:adaptive_de_rand_1_bin, :adaptive_de_rand_1_bin_radiuslimited, :separable_nes, :xnes, :de_rand_1_bin, :de_rand_2_bin, :de_rand_1_bin_radiuslimited, :de_rand_2_bin_radiuslimited, :random_search, :generating_set_search, :probabilistic_descent)

# opt_fields = [(:r,(0.01,2.0)),(:volfrac,(0.,0.14)),(:ρ,(0.0,100.))]
# fix_fields = [(:c,[1.0+0.0im,1.0+0.0im])]
# optf = f_slow1
# medium = Medium{Float64}(1.0,1.)
# ωs = 0.01:0.01:1.0
# num_species=2
# MaxTime=5
# method = :adaptive_de_rand_1_bin

function optimal_species(optf, medium, ωs;
                opt_fields = [(:r,(0.1,2.0)), (:volfrac,(0.01,0.1))],
                fix_fields = [(:ρ,[0.0,Inf]), (:c,[1.0+0.0im,1.0+0.0im])],
                num_species=2, MaxTime=100., method=:choose_method,
                opt_kws...
)

  function F(; ρ=[0.,Inf], r=[0.5,0.5], c=[1.0+0.0im,1.0+0.0im], volfrac=[0.1,0.1])
    N = num_species
    sps = [Specie(ρ[i], r[i], c[i]; volfrac=volfrac[i]) for i=1:N]
    kTs = wavenumber_low_volfrac(ωs, medium, sps)
    # kTs = [wavenumber_low_volfrac(ω, medium, sps) for ω in ωs]
    optf(sps, medium, ωs, kTs)
  end
  function optF(x)
    var_fields = map(1:length(opt_fields)) do n
      (opt_fields[n][1], x[(1+(n-1)*num_species):(n*num_species)])
    end
    F(; var_fields..., fix_fields...)
  end
  if method == :choose_method
    fits =[
      best_fitness(bboptimize(optF; MaxTime = min(100.0,MaxTime), Method = m,
        SearchRange =  repeat([o[2] for o in opt_fields], inner=[num_species])))
    for m in opt_methods]
    method = opt_methods[findmin(fits)[2]]
    println("For MaxTime=$MaxTime, the optimal method was = $method.")
  end

  res = bboptimize(optF; MaxTime = MaxTime, Method=method,
    SearchRange =  repeat([o[2] for o in opt_fields], inner=[num_species]))
  parameters = reshape(best_candidate(res), num_species, length(opt_fields))
  # include the fixed paramters
  parameters = hcat(parameters, hcat([f[2] for f in fix_fields]...))
  fields = [f[1] for f in (opt_fields..., fix_fields...)]
  [ Specie(Float64; zip(fields, parameters[n,:])...) for n =1:num_species]
end


import Pkg; Pkg.add("Scanf")
using Scanf

### Part 1
N = 50
S = 2 * N + 1
grid = zeros(Bool, S, S, S)

function updateInit(v, xs)
  cut(r) = intersect(r, -N:N) .+ (N + 1)
  xl, xr, yl, yr, zl, zr = xs
  grid[cut(xl:xr), cut(yl:yr), cut(zl:zr)] .= v
end

### Part 2
concatMap(f, xs) = collect(Iterators.flatten(map(f, xs)))

# from p, remove q
function cut1D(p, q)
  q = intersect(p, q)
  if (length(q) == 0) return [p] end
  if (p == q) return [] end
  l, r = extrema(p)
  ll, rr = extrema(q)
  return filter(s -> length(s) != 0, [l:ll-1, rr+1:r])
end

# from c1, cut out c2
function cutOverlap(c1, c2)
  x1, y1, z1, v = c1
  x2, y2, z2, _ = c2
  res = []
  for xx in cut1D(x1, x2)
    push!(res, (xx, y1, z1, v))
  end
  x1 = intersect(x1, x2)
  if (length(x1) == 0) return res end
  for yy in cut1D(y1, y2)
    push!(res, (x1, yy, z1, v))
  end
  y1 = intersect(y1, y2)
  if (length(y1) == 0) return res end
  for zz in cut1D(z1, z2)
    push!(res, (x1, y1, zz, v))
  end
  return res
end

cubes = []
function update(v, xs)
  xl, xr, yl, yr, zl, zr = xs
  cur = (xl:xr, yl:yr, zl:zr, v)
  global cubes = concatMap(c -> cutOverlap(c, cur), cubes)
  push!(cubes, cur)
end

function volume(c)
  x, y, z, v = c
  return if (v) length(x) * length(y) * length(z) else 0 end
end

### Compute
for line in eachline()
  _, state, xs... = @scanf(line, "%s x=%d..%d,y=%d..%d,z=%d..%d",
                           String, Int, Int, Int, Int, Int, Int)
  updateInit(state == "on", xs)
  update(state == "on", xs)
end

println(sum(grid))
println(sum(map(volume, cubes)))