function load_Ham_medium(; inf_medium = false)
    ## ---------------------------------------------------------------
    # Ham's F-12
    # but mediam componets from Human1 Zenodo https://zenodo.org/record/3583004#.Xu8KdmpKiCh
    # task (Growth in Ham's medium)
    # Final concentrations in mM

    # Nutrient Mixture F-12 Ham N6760
    # Source concentration where are g/L, from https://www.sigmaaldrich.com/life-science/cell-culture/learning-center/media-formulations/f-12-ham.html

    Ham_medium = Dict()

    ## ---------------------------------------------------------------
    # Inorganic Salts

    # Calcium Chloride (CaCl2)   0.0333 g/L
    if inf_medium
        Ham_medium["Ca2+[s]"] = INF
        Ham_medium["chloride[s]"] = INF
    else
        Ham_medium["Ca2+[s]"] = 0.0333 * 1e3 / 110.98 # mM
        Ham_medium["chloride[s]"] = 2 * 0.0333 * 1e3 / 110.98 # mM
    end

    # Cupric Sulfate • 5H2O (CuSO4 + 5H2O)   0.0000025 g/L
    if inf_medium
        Ham_medium["Cu2+[s]"] = INF
        Ham_medium["sulfate[s]"] = INF
    else
        Ham_medium["Cu2+[s]"] = 0.0000025 * 1e3 / 249.685 # mM
        Ham_medium["sulfate[s]"] = 0.0000025 * 1e3 / 249.685 # mM
    end

    # Ferrous Sulfate • 7H2O (FeSO4.7H2O)   0.000834 g/L
    if inf_medium
        Ham_medium["Fe2+[s]"] = INF
    else
        Ham_medium["Fe2+[s]"] = 0.000834 * 1e3 / 151.91 # mM
        Ham_medium["sulfate[s]"] += 0.000834 * 1e3 / 151.91 # mM
    end

    # Magnesium Chloride (MgCl2)   0.0576 g/L
    if inf_medium
        Ham_medium["Mg2+[s]"] = INF
    else
        Ham_medium["Mg2+[s]"] = 0.0576 * 1e3 / 95.211 # mM
        Ham_medium["chloride[s]"] += 0.0576 * 1e3 / 95.211 # mM
    end

    # Potassium Chloride (KCl)  0.224 g/L
    if inf_medium
        Ham_medium["K+[s]"] = INF
    else
        Ham_medium["K+[s]"] = 0.224 * 1e3 / 74.5513 # mM
        Ham_medium["chloride[s]"] += 0.224 * 1e3 / 74.5513 # mM
    end

    # Sodium Bicarbonate (NaHCO3)  1.176 g/L
    if inf_medium
        Ham_medium["Na+[s]"] = INF
        Ham_medium["HCO3-[s]"] = INF
    else
        Ham_medium["Na+[s]"] = 1.176 * 1e3 / 84.007 # mM
        Ham_medium["HCO3-[s]"] = 1.176 * 1e3 / 84.007 # mM
    end

    # Sodium Chloride (NaCl)  7.599 g/L
    if !inf_medium
        Ham_medium["Na+[s]"] += 7.599 * 1e3 / 58.44 # mM
        Ham_medium["chloride[s]"] += 7.599 * 1e3 / 58.44 # mM
    end

    # Sodium Phosphate Dibasic (anhydrous) (Na2HPO4)  0.14204 g/L
    if inf_medium
        Ham_medium["Pi[s]"] = INF
    else
        Ham_medium["Pi[s]"] = 0.14204 * 1e3 / 141.96 # mM
        Ham_medium["Na+[s]"] += 2 * 0.14204 * 1e3 / 141.96 # mM
    end

    # Zinc Sulfate • 7H2O (ZnSO4. 7H2O)  0.000863 g/L
    if inf_medium
        Ham_medium["zinc[s]"] = INF
    else
        Ham_medium["zinc[s]"] = 0.000863 * 1e3 / 287.6 # mM
        Ham_medium["sulfate[s]"] += 0.000863 * 1e3 / 287.6 # mM
    end

    # Sodium Bicarbonate  1.176 g/L
    if !inf_medium
        Ham_medium["Na+[s]"] += 1.176 * 1e3 / 58.44 # mM
        Ham_medium["HCO3-[s]"] += 1.176 * 1e3 / 84.007 # mM
    end

    ## ---------------------------------------------------------------
    # Vitamins

    # D-Biotin   0.0000073 g/L
    if inf_medium
        Ham_medium["biotin[s]"] = INF
    else
        Ham_medium["biotin[s]"] = 0.0000073 * 1e3 / 244.31 # mM
    end

    # Choline Chloride (C5H14NO.Cl) 0.01396 g/L
    if inf_medium
        Ham_medium["choline[s]"] = INF
    else
        Ham_medium["choline[s]"] = 0.01396 * 1e3 / 139.62 # mM
        Ham_medium["chloride[s]"] += 0.01396 * 1e3 / 139.62 # mM
    end

    # Folic Acid   0.00132 g/L
    if inf_medium
        Ham_medium["folate[s]"] = INF
    else
        Ham_medium["folate[s]"] = 0.00132 * 1e3 / 441.4 # mM
    end

    # myo-Inositol   0.018 g/L
    if inf_medium
        Ham_medium["inositol[s]"] = INF
    else
        Ham_medium["inositol[s]"] = 0.018 * 1e3 / 180.16 # mM
    end

    # Niacinamide   0.000037 g/L
    if inf_medium
        Ham_medium["nicotinamide[s]"] = INF
    else
        Ham_medium["nicotinamide[s]"] = 0.000037 * 1e3 / 122.12 # mM
    end

    # D-Pantothenic Acid (hemicalcium)   0.00048 g/L
    if inf_medium
        Ham_medium["pantothenate[s]"] = INF
    else
        Ham_medium["pantothenate[s]"] = 0.00048 * 1e3 / 219.23 # mM
    end

    # Pyridoxine • HCl   0.000062 g/L
    if inf_medium
        Ham_medium["pyridoxine[s]"] = INF
    else
        Ham_medium["pyridoxine[s]"] = 0.000062 * 1e3 / 169.18 # mM
        Ham_medium["chloride[s]"] += 0.000062 * 1e3 / 169.18 # mM
    end

    # Riboflavin   0.000038 g/L
    if inf_medium
        Ham_medium["riboflavin[s]"] = INF
    else
        Ham_medium["riboflavin[s]"] = 0.000038 * 1e3 / 376.36 # mM
    end

    # Thiamine • HCl   0.00034 g/L
    if inf_medium
        Ham_medium["thiamin[s]"] = INF
    else
        Ham_medium["thiamin[s]"] = 0.00034 * 1e3 / 265.355 # mM
        Ham_medium["chloride[s]"] += 0.00034 * 1e3 / 265.355 # mM
    end

    # Vitamin B12   0.00136 g/L
    if inf_medium
        Ham_medium["cobamide-coenzyme[s]"] = INF
    else
        Ham_medium["cobamide-coenzyme[s]"] = 0.00136 * 1e3 / 1355.365 # mM
    end

    ## ---------------------------------------------------------------
    # Other
    # D-Glucose   1.802  g/L
    Ham_medium["glucose[s]"] = 1.802 * 1e3 / 180.156 # mM

    # Hypoxanthine   0.00408 g/L
    if inf_medium
        Ham_medium["hypoxanthine[s]"] = INF
    else
        Ham_medium["hypoxanthine[s]"] = 0.00408 * 1e3 / 136.1115 # mM
    end

    # Linoleic Acid   0.000084 g/L
    if inf_medium
        Ham_medium["linolenate[s]"] = INF
    else
        Ham_medium["linolenate[s]"] = 0.000084 * 1e3 / 280.4472 # mM
    end

    # Phenol Red • Na   0.0013 g/L # PH Indicator

    # Putrescine • HCl   0.000161 g/L
    if inf_medium
        Ham_medium["putrescine[s]"] = INF
    else
        Ham_medium["putrescine[s]"] = 0.000161 * 1e3 / 88.15 # mM
        Ham_medium["chloride[s]"] += 0.000161 * 1e3 / 88.15 # mM
    end

    # Pyruvic Acid • Na   0.11 g/L # Carbon source, not included

    # Thioctic Acid   0.00021 g/L
    if inf_medium
        Ham_medium["lipoic acid[s]"] = INF
    else
        Ham_medium["lipoic acid[s]"] = 0.00021 * 1e3 / 206.33 # mM
    end

    # Thymidine   0.00073   g/L
    if inf_medium
        Ham_medium["thymidine[s]"] = INF
    else
        Ham_medium["thymidine[s]"] = 0.00073 * 1e3 / 242.2286 # mM
    end

    ## ---------------------------------------------------------------
    # Add
    # L-Glutamine   0.146 g/L
    Ham_medium["glutamine[s]"] = 0.146 * 1e3 / 146.14 # mM

    ## ---------------------------------------------------------------
    # Amino Acids

    # L-Alanine   0.009 g/L
    Ham_medium["alanine[s]"] = 0.009 * 1e3 / 89.09 # mM

    # L-Arginine • HCl   0.211 g/L
    Ham_medium["arginine[s]"] = 0.211 * 1e3 / 174.2 # mM
    Ham_medium["chloride[s]"] += 0.211 * 1e3 / 174.2 # mM

    # L-Asparagine • H2O   0.01501 g/L
    Ham_medium["asparagine[s]"] = 0.01501 * 1e3 / 132.12 # mM

    # L-Aspartic Acid   0.0133 g/L
    Ham_medium["aspartate[s]"] = 0.0133 * 1e3 / 133.11 # mM

    # L-Cysteine • HCl • H2O   0.035 g/L
    Ham_medium["cysteine[s]"] = 0.035 * 1e3 / 121.16 # mM
    Ham_medium["chloride[s]"] += 0.035 * 1e3 / 121.16  # mM

    # L-Glutamic Acid   0.0147 g/L
    Ham_medium["glutamate[s]"] = 0.0147 * 1e3 / 147.13 # mM

    # L-Glutamine   0.146 g/L
    Ham_medium["glutamine[s]"] += 0.146 * 1e3 / 146.14 # mM

    # Glycine   0.00751 g/L
    Ham_medium["glycine[s]"] = 0.00751 * 1e3 / 75.07 # mM

    # L-Histidine • 3HCl • H2O   0.02096 g/L
    Ham_medium["histidine[s]"] = 0.02096 * 1e3 / 155.1546 # mM
    Ham_medium["chloride[s]"] += 3 * 0.02096 * 1e3 / 155.1546 # mM

    # L-Isoleucine   0.00394 g/L
    Ham_medium["isoleucine[s]"] = 0.00394 * 1e3 / 131.17 # mM

    # L-Leucine   0.0131 g/L
    Ham_medium["leucine[s]"] = 0.0131 * 1e3 / 131.17 # mM

    # L-Lysine • HCl   0.0365 g/L
    Ham_medium["lysine[s]"] = 0.0365 * 1e3 / 146.19 # mM
    Ham_medium["chloride[s]"] += 0.0365 * 1e3 / 146.19 # mM

    # L-Methionine   0.00448 g/L
    Ham_medium["methionine[s]"] = 0.00448 * 1e3 / 149.21 # mM

    # L-Phenylalanine   0.00496 g/L
    Ham_medium["phenylalanine[s]"] = 0.00496 * 1e3 / 165.19 # mM

    # L-Proline   0.0345 g/L
    Ham_medium["proline[s]"] = 0.0345 * 1e3 / 115.13 # mM

    # L-Serine   0.0105 g/L
    Ham_medium["serine[s]"] = 0.0105 * 1e3 / 105.09 # mM

    # L-Threonine   0.0119 g/L
    Ham_medium["threonine[s]"] = 0.0119 * 1e3 / 119.1192 # mM

    # L-Tryptophan   0.00204 g/L
    Ham_medium["tryptophan[s]"] = 0.00204 * 1e3 / 204.23 # mM

    # L-Tyrosine • 2Na • 2H2O   0.00778 g/L
    Ham_medium["tyrosine[s]"] =  0.00778 * 1e3 / 181.19 # mM
    Ham_medium["Na+[s]"] += 2 * 0.00778 * 1e3 / 181.19 # mM

    # L-Valine   0.0117 g/L
    Ham_medium["valine[s]"] = 0.0117 * 1e3 / 117.151 # mM
    # -

    # ## Extras

    Ham_medium["O2[s]"] = INF
    Ham_medium["H2O[s]"] = INF
    # Not found in concentration source
    # TODO search real conc for this mets
    # The concentration of this components was selected to ensure
    # suficient growth rate compared to the experimental values
    Ham_medium["alpha-tocopherol[s]"] = INF
    Ham_medium["gamma-tocopherol[s]"] = INF
    Ham_medium["aquacob(III)alamin[s]"] = INF
    Ham_medium["retinoate[s]"] = INF

    # Testing
    All_components = Any["valine[s]", "putrescine[s]", "H2O[s]", "Fe2+[s]", "arginine[s]", "tryptophan[s]",
                        "asparagine[s]", "glutamine[s]", "serine[s]", "linolenate[s]", "cysteine[s]",
                        "HCO3-[s]", "Ca2+[s]", "gamma-tocopherol[s]", "chloride[s]", "histidine[s]",
                        "hypoxanthine[s]", "pyridoxine[s]", "Na+[s]", "thiamin[s]", "phenylalanine[s]",
                        "riboflavin[s]", "Cu2+[s]", "K+[s]", "lipoic acid[s]", "alanine[s]", "Mg2+[s]",
                        "zinc[s]", "sulfate[s]", "lysine[s]", "glycine[s]", "leucine[s]", "threonine[s]",
                        "tyrosine[s]", "aspartate[s]", "inositol[s]", "thymidine[s]", "cobamide-coenzyme[s]",
                        "choline[s]", "proline[s]", "retinoate[s]", "glutamate[s]", "pantothenate[s]", "folate[s]",
                        "O2[s]", "alpha-tocopherol[s]", "Pi[s]", "isoleucine[s]", "aquacob(III)alamin[s]",
                        "glucose[s]", "nicotinamide[s]", "methionine[s]", "biotin[s]"] |> sort
    @assert all(Ham_medium |> keys |> collect |> sort .== All_components)
    @assert all(c >= 0.0 for (met, c) in Ham_medium)

    return Ham_medium
end


using AstroTime: Epoch
using ..Bodies:
    CelestialBody,
    NAIFId,
    SolarSystemBarycenter,
    naifid,
    path_ids,
    ssb
import ..Interfaces:
    AbstractEphemeris,
    position,
    position!,
    state,
    state!,
    velocity,
    velocity!

for f in (:position, :velocity)
    fmut = Symbol(f, "!")
    @eval begin
        function $fmut(arr,
                       eph::AbstractEphemeris,
                       ep::Epoch,
                       from::T,
                       to::T;
                       kwargs...
                      ) where T<:CelestialBody
            arr .+= 0.0
        end

        function $fmut(arr,
                       eph::AbstractEphemeris,
                       ep::Epoch,
                       from::CelestialBody,
                       to::CelestialBody;
                       kwargs...
                      )
            path = path_ids(from, to)
            for (origin, target) in zip(path[1:end-1], path[2:end])
                $fmut(arr, eph, ep, origin, target; kwargs...)
            end
            arr
        end

        function $f(eph::AbstractEphemeris,
                    ep::Epoch,
                    from::CelestialBody,
                    to::CelestialBody;
                    kwargs...
                   )
            $fmut(zeros(3), eph, ep, from, to; kwargs...)
        end

        function $fmut(arr, eph::AbstractEphemeris, ep::Epoch, to::CelestialBody; kwargs...)
            $fmut(arr, eph, ep, ssb, to; kwargs...)
        end

        function $f(eph::AbstractEphemeris, ep::Epoch, to::CelestialBody; kwargs...)
            $fmut(zeros(3), eph, ep, ssb, to; kwargs...)
        end
    end
end

function state!(pos, vel, eph::AbstractEphemeris, ep::Epoch, from::NAIFId, to::NAIFId; kwargs...)
    position!(pos, eph, ep, from, to; kwargs...), velocity!(vel, eph, ep, from, to; kwargs...)
end

function state!(pos,
                vel,
                eph::AbstractEphemeris,
                ep::Epoch,
                from::CelestialBody,
                to::CelestialBody;
                kwargs...)
    path = path_ids(from, to)
    for (origin, target) in zip(path[1:end-1], path[2:end])
        state!(pos, vel, eph, ep, origin, target; kwargs...)
    end
    pos, vel
end

state!(pos,
       vel,
       ::AbstractEphemeris,
       ::Epoch,
       ::T, ::T; kwargs...) where {T<:CelestialBody} = pos, vel

function state(eph::AbstractEphemeris, ep::Epoch, from::CelestialBody, to::CelestialBody; kwargs...)
    state!(zeros(3), zeros(3), eph, ep, from, to; kwargs...)
end

function state!(pos, vel, eph::AbstractEphemeris, ep::Epoch, to::CelestialBody; kwargs...)
    state!(pos, vel, eph, ep, ssb, to; kwargs...)
end

function state(eph::AbstractEphemeris, ep::Epoch, to::CelestialBody; kwargs...)
    state!(zeros(3), zeros(3), eph, ep, ssb, to; kwargs...)
end


"""
    Optimizer(; <keyword arguments>)

Return a progressive-hedging optimizer.

...
# Arguments
- `subproblem_optimizer::AbstractOptimizer`: MathOptInterface solver capable of solving quadratic programs.
- `penalty::AbstractPenalizer = Fixed()`: Specify penalty update procedure (Fixed, Adaptive)
- `execution::AbstractExecuter = Serial`: Specify how algorithm should be executed (Serial, Synchronous, Asynchronous). Distributed variants requires worker cores.
- `penaltyterm::PenaltyTerm = Quadratic`: Specify penaltyterm variant ([`Quadratic`](@ref), [`Linearized`](@ref), [`InfNorm`](@ref), [`ManhattanNorm`][@ref])
- <keyword arguments>: Algorithm specific parameters, consult individual docstrings (see above list) for list of possible arguments and default values.
...
"""
mutable struct Optimizer <: AbstractStructuredOptimizer
    subproblem_optimizer
    sub_params::Dict{MOI.AbstractOptimizerAttribute, Any}
    execution::AbstractExecution
    penalizer::AbstractPenalizer
    penaltyterm::AbstractPenaltyterm
    parameters::ProgressiveHedgingParameters{Float64}

    status::MOI.TerminationStatusCode
    solve_time::Float64
    progressivehedging::Union{AbstractProgressiveHedging, Nothing}

    function Optimizer(; subproblem_optimizer = nothing,
                       execution::AbstractExecution = nworkers() == 1 ? Serial() : Synchronous(),
                       penalty::AbstractPenalizer = Fixed(),
                       penaltyterm::AbstractPenaltyterm = Quadratic(),
                       kw...)
        return new(subproblem_optimizer,
                   Dict{MOI.AbstractOptimizerAttribute, Any}(),
                   execution,
                   penalty,
                   penaltyterm,
                   ProgressiveHedgingParameters{Float64}(; kw...),
                   MOI.OPTIMIZE_NOT_CALLED,
                   NaN,
                   nothing)
    end
end

# Interface #
# ========================== #
function supports_structure(optimizer::Optimizer, ::HorizontalStructure)
    return true
end

function default_structure(::UnspecifiedInstantiation, optimizer::Optimizer)
    if optimizer.execution isa Serial && nworkers() == 1
        return Horizontal()
    else
        return DistributedHorizontal()
    end
end

function check_loadable(optimizer::Optimizer, ::HorizontalStructure)
    if optimizer.subproblem_optimizer === nothing
        msg = "Subproblem optimizer not set. Consider setting `SubproblemOptimizer` attribute."
        throw(UnloadableStructure{Optimizer, HorizontalStructure}(msg))
    end
    return nothing
end

function ensure_compatible_execution!(optimizer::Optimizer, ::HorizontalStructure{2, 1, <:Tuple{ScenarioProblems}})
    if !(optimizer.execution isa Serial)
        @warn "Distributed execution policies are not compatible with a single-core horizontal structure. Switching to `Serial` execution by default."
        MOI.set(optimizer, Execution(), Serial())
    end
    return nothing
end

function ensure_compatible_execution!(optimizer::Optimizer, ::HorizontalStructure{2, 1, <:Tuple{DistributedScenarioProblems}})
    if optimizer.execution isa Serial
        @warn "Serial execution not compatible with distributed horizontal structure. Switching to `Synchronous` execution by default."
        MOI.set(optimizer, Execution(), Synchronous())
    end
    return nothing
end

function load_structure!(optimizer::Optimizer, structure::HorizontalStructure, x₀::AbstractVector)
    # Sanity check
    check_loadable(optimizer, structure)
    # Restore structure if optimization has been run before
    restore_structure!(optimizer)
    # Ensure that execution policy is compatible
    ensure_compatible_execution!(optimizer, structure)
    # Create new progressive-hedging algorithm
    optimizer.progressivehedging = ProgressiveHedgingAlgorithm(structure,
                                                               x₀,
                                                               optimizer.execution,
                                                               optimizer.penalizer,
                                                               optimizer.penaltyterm;
                                                               type2dict(optimizer.parameters)...)
    for (attr, value) in optimizer.sub_params
        MOI.set(scenarioproblems(optimizer.progressivehedging.structure), attr, value)
    end
    return nothing
end

function restore_structure!(optimizer::Optimizer)
    if optimizer.progressivehedging !== nothing
        restore_subproblems!(optimizer.progressivehedging)
    end
    return nothing
end

function MOI.optimize!(optimizer::Optimizer)
    if optimizer.progressivehedging === nothing
        throw(StochasticProgram.UnloadedStructure{Optimizer}())
    end
    start_time = time()
    optimizer.status = optimizer.progressivehedging()
    optimizer.solve_time = time() - start_time
    return nothing
end

function termination_status(optimizer::Optimizer)
    return optimizer.status
end

function optimizer_name(optimizer::Optimizer)
    return "$(str(optimizer.execution))Progressive-hedging with $(str(optimizer.penalizer))"
end

# MOI #
# ========================== #
function MOI.get(optimizer::Optimizer, ::MOI.Silent)
    return !MOI.get(optimizer, MOI.RawParameter("log"))
end

function MOI.set(optimizer::Optimizer, attr::MOI.Silent, flag::Bool)
    MOI.set(optimizer, MOI.RawParameter("log"), !flag)
    optimizer.sub_params[attr] = flag
    return nothing
end

function MOI.get(optimizer::Optimizer, param::MOI.RawParameter)
    name = Symbol(param.name)
    if !(name in fieldnames(ProgressiveHedgingParameters))
        error("Unrecognized parameter name: $(name).")
    end
    return getfield(optimizer.parameters, name)
end

function MOI.set(optimizer::Optimizer, param::MOI.RawParameter, value)
    name = Symbol(param.name)
    if !(name in fieldnames(ProgressiveHedgingParameters))
        error("Unrecognized parameter name: $(name).")
    end
    setfield!(optimizer.parameters, name, value)
    return nothing
end

function MOI.get(optimizer::Optimizer, ::MOI.TimeLimitSec)
    limit = MOI.get(optimizer, MOI.RawParameter("time_limit"))
    return isinf(limit) ? nothing : limit
end

function MOI.set(optimizer::Optimizer, ::MOI.TimeLimitSec, limit::Union{Real, Nothing})
    limit = limit === nothing ? Inf : limit
    MOI.set(optimizer, MOI.RawParameter("time_limit"), limit)
    return
end

function MOI.get(optimizer::Optimizer, ::PrimalTolerance)
    return MOI.get(optimizer, MOI.RawParameter("ϵ₁"))
end

function MOI.set(optimizer::Optimizer, ::PrimalTolerance, limit::Real)
    MOI.set(optimizer, MOI.RawParameter("ϵ₁"), limit)
    return nothing
end

function MOI.get(optimizer::Optimizer, ::DualTolerance)
    return MOI.get(optimizer, MOI.RawParameter("ϵ₂"))
end

function MOI.set(optimizer::Optimizer, ::DualTolerance, limit::Real)
    MOI.set(optimizer, MOI.RawParameter("ϵ₂"), limit)
    return nothing
end

function MOI.get(optimizer::Optimizer, ::MasterOptimizer)
    return MOI.get(optimizer, SubproblemOptimizer())
end

function MOI.get(optimizer::Optimizer, ::SubproblemOptimizer)
    if optimizer.subproblem_optimizer === nothing
        return MOI.get(optimizer, MasterOptimizer())
    end
    return () -> begin
        opt = optimizer.subproblem_optimizer()
        for (attr, value) in optimizer.sub_params
            MOI.set(opt, attr, value)
        end
        return opt
    end
end

function MOI.set(optimizer::Optimizer, ::SubproblemOptimizer, optimizer_constructor)
    optimizer.subproblem_optimizer = optimizer_constructor
    return nothing
end

function MOI.get(optimizer::Optimizer, ::SubproblemOptimizerAttribute, attr::MOI.AbstractOptimizerAttribute)
    if !haskey(optimizer.sub_params, attr)
        error("Subproblem optimizer attribute $(attr) has not been set.")
    end
    return optimizer.sub_params[attr]
end

function MOI.set(optimizer::Optimizer, ::SubproblemOptimizerAttribute, attr::MOI.AbstractOptimizerAttribute, value)
    optimizer.sub_params[attr] = value
    return nothing
end

function MOI.get(optimizer::Optimizer, param::RawSubproblemOptimizerParameter)
    moi_param = MOI.RawParameter(param.name)
    if !haskey(optimizer.sub_params, moi_param)
        error("Subproblem optimizer attribute $(param.name) has not been set.")
    end
    return optimizer.sub_params[moi_param]
end

function MOI.set(optimizer::Optimizer, param::RawSubproblemOptimizerParameter, value)
    moi_param = MOI.RawParameter(param.name)
    optimizer.sub_params[moi_param] = value
    return nothing
end

function MOI.get(optimizer::Optimizer, ::Execution)
    return optimizer.execution
end

function MOI.set(optimizer::Optimizer, ::Execution, execution::AbstractExecution)
    optimizer.execution = execution
    return nothing
end

function MOI.get(optimizer::Optimizer, ::Penalizer)
    return optimizer.penalizer
end

function MOI.set(optimizer::Optimizer, ::Penalizer, penalizer::AbstractPenalizer)
    optimizer.penalizer = penalizer
    return nothing
end

function MOI.get(optimizer::Optimizer, ::Penaltyterm)
    return optimizer.penaltyterm
end

function MOI.set(optimizer::Optimizer, ::Penaltyterm, penaltyterm::AbstractPenaltyterm)
    optimizer.penaltyterm = penaltyterm
    return nothing
end

function MOI.get(optimizer::Optimizer, param::ExecutionParameter)
    return MOI.get(optimizer.execution, param)
end

function MOI.set(optimizer::Optimizer, param::ExecutionParameter, value)
    MOI.set(optimizer.execution, param, value)
    return nothing
end

function MOI.get(optimizer::Optimizer, param::PenalizationParameter)
    return MOI.get(optimizer.penalizer, param)
end

function MOI.set(optimizer::Optimizer, param::PenalizationParameter, value)
    MOI.set(optimizer.penalizer, param, value)
    return nothing
end

function MOI.get(optimizer::Optimizer, ::MOI.TerminationStatus)
    return optimizer.status
end

function MOI.get(optimizer::Optimizer, attr::MOI.ListOfVariableIndices)
    if optimizer.progressivehedging === nothing
        throw(UnloadedStructure{Optimizer}())
    end
    list = MOI.get(optimizer.progressivehedging.structure.proxy, attr)
    return list
end

function MOI.get(optimizer::Optimizer, ::MOI.VariablePrimal, index::MOI.VariableIndex)
    if optimizer.progressivehedging === nothing
        throw(StochasticProgram.UnloadedStructure{Optimizer}())
    end
    return decision(optimizer.progressivehedging, index)
end

function MOI.get(optimizer::Optimizer, ::MOI.ObjectiveValue)
    if optimizer.progressivehedging === nothing
        throw(StochasticProgram.UnloadedStructure{Optimizer}())
    end
    return objective_value(optimizer.progressivehedging)
end

function MOI.get(optimizer::Optimizer, ::MOI.SolveTime)
    return optimizer.solve_time
end

function MOI.get(optimizer::Optimizer, attr::Union{MOI.AbstractOptimizerAttribute, MOI.AbstractModelAttribute})
    # Catch-all in case the proxy model supports it
    if optimizer.progressivehedging === nothing
        throw(UnloadedStructure{Optimizer}())
    end
    return MOI.get(optimizer.progressivehedging.structure.proxy, attr)
end

function MOI.is_empty(optimizer::Optimizer)
    return optimizer.progressivehedging === nothing
end

MOI.supports(::Optimizer, ::MOI.Silent) = true
MOI.supports(::Optimizer, ::MOI.TimeLimitSec) = true
MOI.supports(::Optimizer, ::MOI.RawParameter) = true
MOI.supports(::Optimizer, ::AbstractStructuredOptimizerAttribute) = true
MOI.supports(::Optimizer, ::RelativeTolerance) = false
MOI.supports(::Optimizer, ::RawInstanceOptimizerParameter) = true
MOI.supports(::Optimizer, ::AbstractProgressiveHedgingAttribute) = true

# High-level attribute setting #
# ========================== #
"""
    get_penalization_attribute(stochasticprogram::StochasticProgram, name::String)

Return the value associated with the penalization-specific attribute named `name` in `stochasticprogram`.

See also: [`set_penalization_attribute`](@ref), [`set_penalization_attributes`](@ref).
"""
function get_penalization_attribute(stochasticprogram::StochasticProgram, name::String)
    return return MOI.get(optimizer(stochasticprogram), RawPenalizationParameter(name))
end
"""
    set_penalization_attribute(stochasticprogram::StochasticProgram, name::Union{Symbol, String}, value)

Sets the penalization-specific attribute identified by `name` to `value`.

"""
function set_penalization_attribute(stochasticprogram::StochasticProgram, name::Union{Symbol, String}, value)
    return set_optimizer_attribute(stochasticprogram, RawPenalizationParameter(String(name)), value)
end
"""
    set_penalization_attributes(stochasticprogram::StochasticProgram, pairs::Pair...)

Given a list of `attribute => value` pairs or a collection of keyword arguments, calls
`set_penalization_attribute(stochasticprogram, attribute, value)` for each pair.

"""
function set_penalization_attributes(stochasticprogram::StochasticProgram, pairs::Pair...)
    for (name, value) in pairs
        set_penalization_attribute(stochasticprogram, name, value)
    end
end
function set_penalization_attributes(stochasticprogram::StochasticProgram; kw...)
    for (name, value) in kw
        set_penalization_attribute(stochasticprogram, name, value)
    end
end


function gen(data,mva)
    data["pmax"]=mva
    data["pmin"]=mva
    return data
end

function converterdc_ne(conv,nde,mbase)
    #NOTE change
    conv["cost"]=nde["xfm_off"].costs.cpx_p+nde["xfm_off"].costs.cpx_i+nde["conv_off"].costs.cpx+nde["plat"].costs.cpx+nde["cableWF"].costs.cpx_p+nde["cableWF"].costs.cpx_i;
    conv["Pacmax"]=nde["conv_off"].mva/mbase
    conv["Pacmin"]=-1*nde["conv_off"].mva/mbase
    conv["Qacmax"]=nde["conv_off"].mva/mbase
    conv["Qacmin"]=-1*nde["conv_off"].mva/mbase
    return conv
end

function convdc_ne(conv,nde,mbase)
    #NOTE change
    conv["cost"]=nde["dc"]["xfm"].costs.cpx_p+nde["dc"]["xfm"].costs.cpx_i+nde["dc"]["conv"].costs.cpx
    #conv["cost"]=0
    if (haskey(nde["dc"],"plat")); conv["cost"]=conv["cost"]+nde["dc"]["plat"].costs.cpx;end
    #if (haskey(nde["dc"],"plat")); conv["cost"]=0;end
    #########
    conv["Pacmax"]=nde["mva"]/mbase
    conv["Pacmin"]=-1*nde["mva"]/mbase
    conv["Qacmax"]=nde["mva"]/mbase
    conv["Qacmin"]=-1*nde["mva"]/mbase
    return conv
end

function branchdc_on(brc,dcss,mbase)
    brc["cost"]=brc["cost"]+dcss["xfm_on"].costs.cpx_p+dcss["xfm_on"].costs.cpx_i+dcss["conv_on"].costs.cpx
    return brc
end

function converterdc(conv,nde,mbase)
    conv["Pacmax"]=nde.mva/mbase
    conv["Pacmin"]=-1*nde.mva/mbase
    conv["Qacmax"]=nde.mva/mbase
    conv["Qacmin"]=-1*nde.mva/mbase
    return conv
end

function branchdc_ne(b_ne,dc_can,mbase)
    b_ne["rateA"]=(dc_can.num*dc_can.elec.mva)/mbase
    b_ne["rateB"]=(dc_can.num*dc_can.elec.mva)/mbase
    b_ne["rateC"]=(dc_can.num*dc_can.elec.mva)/mbase
    b_ne["r"]=dc_can.elec.ohm

    #NOTE change
    b_ne["cost"]=dc_can.costs.cpx_p+dc_can.costs.cpx_i
    #b_ne["cost"]=0
    #######
    return b_ne
end

function ne_branch(ne_b,ac_can,mbase)
    ne_b["mva"]=ac_can.num*ac_can.elec.mva
    ne_b["rate_a"]=ac_can.num*ac_can.elec.mva/mbase
    ne_b["rate_b"]=ac_can.num*ac_can.elec.mva/mbase
    ne_b["rate_c"]=ac_can.num*ac_can.elec.mva/mbase
    ne_b["br_r"]=ac_can.elec.ohm
    ne_b["br_x"]=ac_can.elec.xl
    #data["ne_branch"][datai]["construction_cost"]=hoa["candidate_lines"]["ac"][hoai].costs.cpx_p+hoa["candidate_lines"]["ac"][hoai].costs.cpx_i+hoa["candidate_lines"]["ac"][hoai].costs.qc+hoa["candidate_lines"]["ac"][hoai].costs.sg
    #ne_b["construction_cost"]=(ac_can.costs.cpx_p+ac_can.costs.cpx_i)/mbase

    #NOTE change
    ne_b["construction_cost"]=ac_can.costs.cpx_p+ac_can.costs.cpx_i
    #ne_b["construction_cost"]=0
    ##########
    return ne_b
end

function branch_xfo(ne_b,ne_xfo,mbase)
    #ne_b["construction_cost"]=ne_b["construction_cost"]+(ne_xfo.costs.cpx_p+ne_xfo.costs.cpx_i)/mbase

    #NOTE change
    ne_b["construction_cost"]=ne_b["construction_cost"]+ne_xfo.costs.cpx_p+ne_xfo.costs.cpx_i
    #ne_b["construction_cost"]=ne_b["construction_cost"]+0
    ##########
    return ne_b
end

function branch_plat(ne_b,ne_plat,mbase)
    #ne_b["construction_cost"]=ne_b["construction_cost"]+(ne_plat.costs.cpx)/mbase

    #NOTE change
    ne_b["construction_cost"]=ne_b["construction_cost"]+ne_plat.costs.cpx
    #ne_b["construction_cost"]=ne_b["construction_cost"]+0
    ##########
    return ne_b
end

function pm_dict_candidateICs(data,hoa,candidates)
    #WF AC candidate lines
    for (i,k) in enumerate(vcat(candidates[2],candidates[2]))
        if (i<=length(candidates[2]))
            data["ne_branch"][string(i)]=ne_branch(data["ne_branch"][string(i)], hoa["candidates"]["wf"]["ac"][string(k)]["cableBE"], data["gen"]["1"]["mbase"])
            data["ne_branch"][string(i)]=branch_xfo(data["ne_branch"][string(i)],hoa["candidates"]["wf"]["ac"][string(k)]["xfm_on"], data["gen"]["1"]["mbase"])
            data["ne_branch"][string(i)]=branch_xfo(data["ne_branch"][string(i)],hoa["candidates"]["wf"]["ac"][string(k)]["xfm_off"], data["gen"]["1"]["mbase"])
            data["ne_branch"][string(i)]=branch_plat(data["ne_branch"][string(i)], hoa["candidates"]["wf"]["ac"][string(k)]["plat"], data["gen"]["1"]["mbase"])
        else
            data["ne_branch"][string(i)]=ne_branch(data["ne_branch"][string(i)], hoa["candidates"]["wf"]["ac"][string(k)]["cableUK"], data["gen"]["1"]["mbase"])
            data["ne_branch"][string(i)]=branch_xfo(data["ne_branch"][string(i)],hoa["candidates"]["wf"]["ac"][string(k)]["xfm_on"], data["gen"]["1"]["mbase"])
            data["ne_branch"][string(i)]=branch_xfo(data["ne_branch"][string(i)],hoa["candidates"]["wf"]["ac"][string(k)]["xfm_off"], data["gen"]["1"]["mbase"])
            data["ne_branch"][string(i)]=branch_plat(data["ne_branch"][string(i)], hoa["candidates"]["wf"]["ac"][string(k)]["plat"], data["gen"]["1"]["mbase"])
        end
    end

    for (i,k) in enumerate(vcat(candidates[1],candidates[1]))
        if (i<=length(candidates[2]))
            data["branchdc_ne"][string(i)]=branchdc_ne(data["branchdc_ne"][string(i)],hoa["candidates"]["ic"][string(k)]["cableBE"],data["gen"]["1"]["mbase"])
            data["branchdc_ne"][string(i)]=branchdc_on(data["branchdc_ne"][string(i)],hoa["candidates"]["ic"][string(k)],data["gen"]["1"]["mbase"])
        else
            data["branchdc_ne"][string(i)]=branchdc_ne(data["branchdc_ne"][string(i)],hoa["candidates"]["ic"][string(k)]["cableUK"],data["gen"]["1"]["mbase"])
            data["branchdc_ne"][string(i)]=branchdc_on(data["branchdc_ne"][string(i)],hoa["candidates"]["ic"][string(k)],data["gen"]["1"]["mbase"])
        end
    end

    for (i,k) in enumerate(candidates[2])
        data["convdc_ne"][string(i)]=converterdc_ne(data["convdc_ne"][string(i)],hoa["candidates"]["wf"]["dc"][string(k)],data["gen"]["1"]["mbase"])
    end
    size_set=[];for (k,v) in hoa["candidates"]["ic"]; push!(size_set,parse(Float64,k));end
    mx_key=string(maximum(size_set))
    data["convdc"]["1"]=converterdc(data["convdc"]["1"],hoa["candidates"]["ic"][mx_key]["conv_on"],data["gen"]["1"]["mbase"])
    data["convdc"]["2"]=converterdc(data["convdc"]["2"],hoa["candidates"]["ic"][mx_key]["conv_on"],data["gen"]["1"]["mbase"])

    return data
end

function additional_candidates(data,candidates)
    #AC
    c2be=deepcopy(data["ne_branch"]["1"]);c2uk=deepcopy(data["ne_branch"]["2"])
    data["ne_branch"]=Dict{String,Any}()
    #WF
    for i=1:1:length(candidates[2]); c2be["source_id"][2]=i;c2be["index"]=i; push!(data["ne_branch"],string(i)=>deepcopy(c2be)); end
    for i=length(candidates[2])+1:1:length(candidates[2])*2; c2uk["source_id"][2]=i;c2uk["index"]=i; push!(data["ne_branch"],string(i)=>deepcopy(c2uk)); end
    #DC
    #WF
    convwf=deepcopy(data["convdc_ne"]["1"])
    data["convdc_ne"]=Dict{String,Any}()
    for i=1:1:length(candidates[2]); convwf["source_id"][2]=i;convwf["index"]=i; push!(data["convdc_ne"],string(i)=>deepcopy(convwf)); end
    #IC
    c2be=deepcopy(data["branchdc_ne"]["1"]);c2uk=deepcopy(data["branchdc_ne"]["2"])
    data["branchdc_ne"]=Dict{String,Any}()
    for i=1:1:length(candidates[1]); c2be["source_id"][2]=i;c2be["index"]=i; push!(data["branchdc_ne"],string(i)=>deepcopy(c2be)); end
    for i=length(candidates[1])+1:1:length(candidates[1])*2; c2uk["source_id"][2]=i;c2uk["index"]=i; push!(data["branchdc_ne"],string(i)=>deepcopy(c2uk)); end
    return data
end

#calculates candidate equipment and sorts in struct
function hoa_datastruct_candidateICs(ic_mva,owpp_mva,ic_length,owpp_km,candidates)
    #location
    uk_pcc=xy();uk_pcc.x=ic_length;uk_pcc.y=0;
    be_pcc=xy();be_pcc.x=0;be_pcc.y=0;
    owpp_xy=xy();owpp_xy.x=owpp_km;owpp_xy.y=0
    #datastructure
    hoa=Dict{String,Any}();
    push!(hoa,"nodes"=>Dict{String,Any}());
    push!(hoa["nodes"],"ac"=>Dict{String,Any}());
    push!(hoa["nodes"],"dc"=>Dict{String,Any}());

    [push!(hoa["nodes"]["ac"],n=>Dict{String,Any}()) for n in ["1","2","3"]]
    [push!(hoa["nodes"]["dc"],n=>Dict{String,Any}()) for n in ["1","2","3","4"]]
    #position of nodes
    push!(hoa["nodes"]["ac"]["1"],"xy"=>be_pcc);
    push!(hoa["nodes"]["ac"]["2"],"xy"=>uk_pcc);
    push!(hoa["nodes"]["ac"]["3"],"xy"=>owpp_xy);
    #position of nodes
    push!(hoa["nodes"]["dc"]["1"],"xy"=>be_pcc);
    push!(hoa["nodes"]["dc"]["2"],"xy"=>uk_pcc);
    push!(hoa["nodes"]["dc"]["3"],"xy"=>owpp_xy);
    push!(hoa["nodes"]["dc"]["4"],"xy"=>owpp_xy);
    #rating of nodes
    push!(hoa["nodes"]["ac"]["1"],"mva"=>ic_mva);
    push!(hoa["nodes"]["ac"]["2"],"mva"=>ic_mva);
    push!(hoa["nodes"]["ac"]["3"],"mva"=>owpp_mva);
    #rating of nodes
    push!(hoa["nodes"]["dc"]["1"],"mva"=>ic_mva);
    push!(hoa["nodes"]["dc"]["2"],"mva"=>ic_mva);
    push!(hoa["nodes"]["dc"]["3"],"mva"=>owpp_mva);
    push!(hoa["nodes"]["dc"]["4"],"mva"=>owpp_mva);
    #lines
    push!(hoa,"candidates"=>Dict{String,Any}());
    push!(hoa["candidates"],"wf"=>Dict{String,Any}());
    push!(hoa["candidates"],"ic"=>Dict{String,Any}());
    push!(hoa["candidates"]["wf"],"ac"=>Dict{String,Any}());
    push!(hoa["candidates"]["wf"],"dc"=>Dict{String,Any}());

    [push!(hoa["candidates"]["wf"]["ac"],string(p)=>Dict{String,Any}()) for p in candidates[2]]
    [push!(hoa["candidates"]["wf"]["dc"],string(p)=>Dict{String,Any}()) for p in candidates[2]]
    [push!(hoa["candidates"]["ic"],string(p)=>Dict{String,Any}()) for p in candidates[1]]

    #wf-ac
    [push!(hoa["candidates"]["wf"]["ac"][k],"xfm_on"=>on_xfm(owpp_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["wf"]["ac"]]
    [push!(hoa["candidates"]["wf"]["ac"][k],"xfm_off"=>off_xfm(owpp_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["wf"]["ac"]]
    [push!(hoa["candidates"]["wf"]["ac"][k],"plat"=>AC_plat(owpp_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["wf"]["ac"]]
    [push!(hoa["candidates"]["wf"]["ac"][k],"xfm"=>on_xfm(owpp_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["wf"]["ac"]]
    [push!(hoa["candidates"]["wf"]["ac"][k],"cableBE"=>AC_cbl(owpp_mva*parse(Float64,k), owpp_km)) for (k,p) in hoa["candidates"]["wf"]["ac"]]
    [push!(hoa["candidates"]["wf"]["ac"][k],"cableUK"=>AC_cbl(owpp_mva*parse(Float64,k), ic_length-owpp_km)) for (k,p) in hoa["candidates"]["wf"]["ac"]]
    #wf-dc
    [push!(hoa["candidates"]["wf"]["dc"][k],"xfm_off"=>off_xfm(owpp_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["wf"]["dc"]]
    [push!(hoa["candidates"]["wf"]["dc"][k],"conv_off"=>off_conv(owpp_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["wf"]["dc"]]
    [push!(hoa["candidates"]["wf"]["dc"][k],"plat"=>DC_plat(owpp_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["wf"]["dc"]]
    [push!(hoa["candidates"]["wf"]["dc"][k],"cableWF"=>DC_cbl(owpp_mva/2*parse(Float64,k), 1)) for (k,p) in hoa["candidates"]["wf"]["dc"]]
    #ic dc
    [push!(hoa["candidates"]["ic"][k],"xfm_on"=>on_xfm(ic_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["ic"]]
    [push!(hoa["candidates"]["ic"][k],"conv_on"=>on_conv(ic_mva*parse(Float64,k))) for (k,p) in hoa["candidates"]["ic"]]
    [push!(hoa["candidates"]["ic"][k],"cableBE"=>DC_cbl(ic_mva/2*parse(Float64,k), owpp_km)) for (k,p) in hoa["candidates"]["ic"]]
    [push!(hoa["candidates"]["ic"][k],"cableUK"=>DC_cbl(ic_mva/2*parse(Float64,k), ic_length-owpp_km)) for (k,p) in hoa["candidates"]["ic"]]

    return hoa
end


SEED_Char(io::IO, BUF::SeisIOBuf, nb::UInt16) = replace(String(fastread(io, nb)),
                                            ['\r', '\0'] =>"")

function SEED_Unenc!(io::IO, S::GphysData, c::Int64, xi::Int64, nb::UInt16, nx::UInt16)
  buf = getfield(BUF, :buf)
  checkbuf_8!(buf, xi)
  x = getindex(getfield(S, :x), c)
  fast_readbytes!(io, buf, nb)
  T::Type = (if BUF.fmt == 0x01
      Int16
    elseif BUF.fmt == 0x03
      Int32
    elseif BUF.fmt == 0x04
      Float32
    else
      Float64
    end)
  xr = reinterpret(T, buf)
  if BUF.swap
    x[xi+1:xi+nx] .= bswap.(xr[1:nx])
  else
    copyto!(x, xi+1, xr, 1, nx)
  end
  setfield!(BUF, :k, Int64(nx))
  return nothing
end

function SEED_Geoscope!(io::IO, BUF::SeisIOBuf)
  mm = 0x0fff
  gm = BUF.fmt == 0x0d ? 0x7000 : 0xf000
  for i = 0x0001:BUF.n
    x = BUF.swap ? bswap(fastread(io, UInt16)) : fastread(io, UInt16)
    m = Int32(x & mm)
    g = Int32((x & gm) >> 12)
    ex = -1*g
    setindex!(BUF.x, ldexp(Float64(m-2048), ex), i)
  end
  BUF.k = BUF.n
  return nothing
end

function SEED_CDSN!(io::IO, BUF::SeisIOBuf)
  for i = 0x0001:BUF.n
    x = BUF.swap ? bswap(fastread(io, UInt16)) : fastread(io, UInt16)
    m = Int32(x & 0x3fff)
    g = Int32((x & 0xc000) >> 14)
    mult = 4^g * g==3 ? 2 : 1
    m -= 0x1fff
    setindex!(BUF.x, m*mult, i)
  end
  BUF.k = BUF.n
  return nothing
end

function SEED_SRO!(io::IO, BUF::SeisIOBuf)
  for i = 0x0001:BUF.n
    x = BUF.swap ? bswap(fastread(io, UInt16)) : fastread(io, UInt16)
    m = Int32(x & 0x0fff)
    g = Int32((x & 0xf000) >> 12)
    if m > 0x07ff
      m -= 0x1000
    end
    ex = -1*g + 10
    setindex!(BUF.x, ldexp(Float64(m), ex), i)
  end
  BUF.k = BUF.n
  return nothing
end

function SEED_DWWSSN!(io::IO, BUF::SeisIOBuf)
  for i = 0x0001:BUF.n
    x = signed(UInt32(BUF.swap ? bswap(fastread(io, UInt16)) : fastread(io, UInt16)))
    BUF.x[i] = x > 32767 ? x - 65536 : x
  end
  BUF.k = BUF.n
  return nothing
end

# Steim1 or Steim2
function SEED_Steim!(io::IO, BUF::SeisIOBuf, nb::UInt16)
  x = getfield(BUF, :x)
  buf = getfield(BUF, :buf)
  ff = getfield(BUF, :uint32_buf)
  nc = Int64(div(nb, 0x0040))
  ni = div(nb, 0x0004)
  fast_readbytes!(io, buf, nb)
  (ni > lastindex(ff)) && resize!(ff, ni)
  fillx_u32_be!(ff, buf, ni, 0)
  k = zero(Int64)       # number of values read
  x0 = zero(Float32)    # first data value
  xn = zero(Float32)    # last data value
  a = zero(UInt8)       # byte offset to first data in each UInt32
  b = zero(UInt8)       # number of reads in each UInt32
  c = zero(UInt8)       # length (in bits) of each read in each UInt32
  d = zero(UInt8)       # amount of right bitshift
  fq = zero(Float32)    # Float32 data placeholder
  m = zero(UInt8)       # counter to reads in each UInt32
  dnib = zero(UInt8)    # dnib; two-bit secondary encoding flag
  q = zero(Int32)       # Int32 data placeholder
  u = zero(UInt32)      # placeholder for bit-shifted UInt32
  ck = zero(UInt8)      # two-bit primary encoding flag
  z = zero(UInt32)      # UInt32 containing nibbles
  χ = zero(UInt32)      # packed UInt32
  r = zero(Int64)       # "row" index to "matrix" of UInt32s
  for i = 1:nc
    z = getindex(ff, 1+r)
    for j = 1:16
      χ = getindex(ff, j+r)
      ck = UInt8((z >> steim[j]) & 0x03)

      # Steim1 and Steim2 are the same here
      if ck == 0x01
        a = 0x00
        b = 0x08
        c = 0x04

      # Steim1 for ck > 0x01
      elseif BUF.fmt == 0x0a
        a = 0x00
        if ck == 0x02
          b = 0x10
          c = 0x02
        elseif ck == 0x03
          b = 0x20
          c = 0x01
        end

      # Steim2
      else
        dnib = UInt8(χ >> 0x0000001e)
        if ck == 0x02
          a = 0x02
          if dnib == 0x01
            b = 0x1e
            c = 0x01
          elseif dnib == 0x02
            b = 0x0f
            c = 0x02
          elseif dnib == 0x03
            b = 0x0a
            c = 0x03
          end
        elseif ck == 0x03
          if dnib == 0x00
            a = 0x02
            b = 0x06
            c = 0x05
          elseif dnib == 0x01
            a = 0x02
            b = 0x05
            c = 0x06
          else
            a = 0x04
            b = 0x04
            c = 0x07
          end
        end
      end
      if ck != 0x00
        u = χ << a
        m = zero(UInt8)
        d = 0x20 - b
        while m < c
          k = k + 1
          q = signed(u)
          q >>= d
          fq = Float32(q)
          setindex!(x, fq, k)
          m = m + 0x01
          u <<= b
        end
      end
      if i == 1
        if j == 2
          x0 = Float32(signed(χ))
        elseif j == 3
          xn = Float32(signed(χ))
        end
      end
    end
    r = r+16
  end

  # Cumsum by hand
  setindex!(x, x0, 1)
  xa = copy(x0)
  @inbounds for i1 = 2:k
    xa = xa + getindex(x, i1)
    setindex!(x, xa, i1)
  end

  # Check data values
  if isapprox(getindex(x, k), xn) == false
    println(stdout, string("RDMSEED: data integrity -- Steim-",
                            getfield(BUF, :fmt) - 0x09, " sequence #",
                            String(copy(getfield(BUF, :seq))),
                            " integrity check failed, last_data=",
                            getindex(getfield(BUF, :x), k),
                            ", should be xn=", xn))
  end
  setfield!(BUF, :k, k)
  return nothing
end


############################################################################################
### Types
############################################################################################
"""
Empty placeholder for models that don't need an imply part.

# Constructor

    ImplyEmpty(;specification, kwargs...)

# Arguments
- `specification`: either a `RAMMatrices` or `ParameterTable` object

# Extended help

## Interfaces
- `identifier(::RAMSymbolic) `-> Dict containing the parameter labels and their position
- `n_par(::RAMSymbolic)` -> Number of parameters

## Implementation
Subtype of `SemImply`.
"""
struct ImplyEmpty{V, V2} <: SemImply
    identifier::V2
    n_par::V
end

############################################################################################
### Constructors
############################################################################################

function ImplyEmpty(;
        specification,
        kwargs...)

        ram_matrices = RAMMatrices(specification)
        identifier = StructuralEquationModels.identifier(ram_matrices)

        n_par = length(ram_matrices.parameters)

        return ImplyEmpty(identifier, n_par)
end

############################################################################################
### methods
############################################################################################

objective!(imply::ImplyEmpty, par, model) = nothing
gradient!(imply::ImplyEmpty, par, model) = nothing
hessian!(imply::ImplyEmpty, par, model) = nothing

############################################################################################
### Recommended methods
############################################################################################

identifier(imply::ImplyEmpty) = imply.identifier
n_par(imply::ImplyEmpty) = imply.n_par

update_observed(imply::ImplyEmpty, observed::SemObserved; kwargs...) = imply


using StructArrays
using ChainRulesCore: NoTangent

struct ∇getindex{T,S}
    xs::T
    i::S
end

function (g::∇getindex)(Δ)
    Δ′ = zero(g.xs)
    Δ′[g.i...] = Δ
    (ChainRulesCore.NoTangent(), Δ′, map(_ -> nothing, g.i)...)
end

function ChainRulesCore.rrule(g::∇getindex, Δ)
    g(Δ), Δ′′->(nothing, Δ′′[1][g.i...])
end

function ChainRulesCore.rrule(::typeof(getindex), xs::Array, i...)
    xs[i...], ∇getindex(xs, i)
end

function reversediff(f, xs...)
    y, f☆ = ∂⃖(f, xs...)
    return tuple(y, tail(f☆(dx(y)))...)
end

function reversediff_array(f, xs::Vector...)
    fieldarrays(StructArray(reversediff(f, x...) for x in zip(xs...)))
end

function reversediff_array(f, xs::Vector)
    fieldarrays(StructArray(reversediff(f, x) for x in xs))
end

function assert_gf(f)
    @assert sizeof(sin) == 0
end

function ChainRulesCore.rrule(::typeof(assert_gf), f)
    assert_gf(f), Δ->begin
        (NoTangent(), NoTangent())
    end
end

#=
function ChainRulesCore.rrule(::typeof(map), f, xs::Vector...)
    assert_gf(f)
    primal, dual = reversediff_array(f, xs...)
    primal, Δ->begin
        (NoTangent(), NoTangent(), ntuple(i->map(*, getfield(dual, i), Δ), length(dual))...)
    end
end
=#

# Disable thunk versions of ChainRules, which interfere with higher order AD

function rrule_times(::typeof(*), A::AbstractVecOrMat, B::AbstractVecOrMat)
    function times_pullback(Ȳ)
        return (NoTangent(), Ȳ * Base.adjoint(B), Base.adjoint(A) * Ȳ)
    end
    return A * B, times_pullback
end

function rrule_times(::typeof(*), A::AbstractVector{<:ChainRules.CommutativeMulNumber}, B::AbstractMatrix{<:ChainRules.CommutativeMulNumber})
    function times_pullback(Ȳ)
        return (NoTangent(), Ȳ * Base.adjoint(B), Base.adjoint(A) * Ȳ)
    end
    return A * B, times_pullback
end

rrule_times(::typeof(*), args...) = rrule(*, args...)

function (::∂⃖{N})(f::typeof(*), args...) where {N}
    if N == 1
        z = rrule_times(f, args...)
        if z === nothing
            return ∂⃖recurse{1}()(f, args...)
        end
        return z
    else
        ∂⃖p = ∂⃖{minus1(N)}()
        @destruct z, z̄ = ∂⃖p(rrule_times, f, args...)
        if z === nothing
            return ∂⃖recurse{N}()(f, args...)
        else
            return ∂⃖rrule{N}()(z, z̄)
        end
    end
end

function ChainRulesCore.frule((_, ∂A, ∂B), ::typeof(*), A::AbstractMatrix{<:Real}, B::AbstractVector{<:Real})
    return (A * B, ∂A * B + A * ∂B)
end

#=
function ChainRulesCore.rrule(::typeof(map), f, xs::Vector)
    assert_gf(f)
    arrs = reversediff_array(f, xs)
    primal = getfield(arrs, 1)
    primal, let dual = getfield(arrs, 2)
        Δ->(NoTangent(), NoTangent(), map(*, dual, unthunk(Δ)))
    end
end
=#

#=
function ChainRulesCore.rrule(::typeof(map), f, xs::Vector, ys::Vector)
    assert_gf(f)
    arrs = reversediff_array(f, xs, ys)
    primal = getfield(arrs, 1)
    primal, let dual = tail(arrs)
        Δ->(NoTangent(), NoTangent(), map(*, getfield(dual, 1), Δ), map(*, getfield(dual, 2), Δ))
    end
end
=#

xsum(x::Vector) = sum(x)
function ChainRulesCore.rrule(::typeof(xsum), x::Vector)
    xsum(x), let xdims=size(x)
        Δ->(NoTangent(), xfill(Δ, xdims...))
    end
end

xfill(x, dims...) = fill(x, dims...)
function ChainRulesCore.rrule(::typeof(xfill), x, dim)
    xfill(x, dim), Δ->(NoTangent(), xsum(Δ), NoTangent())
end

struct NonDiffEven{N, O, P}; end
struct NonDiffOdd{N, O, P}; end

(::NonDiffOdd{N, O, P})(Δ) where {N, O, P} = (ntuple(_->ZeroTangent(), N), NonDiffEven{N, plus1(O), P}())
(::NonDiffEven{N, O, P})(Δ...) where {N, O, P} = (ZeroTangent(), NonDiffOdd{N, plus1(O), P}())
(::NonDiffOdd{N, O, O})(Δ) where {N, O} = ntuple(_->ZeroTangent(), N)

# This should not happen
(::NonDiffEven{N, O, O})(Δ...) where {N, O} = error()

@Base.pure function ChainRulesCore.rrule(::typeof(Core.apply_type), head, args...)
    Core.apply_type(head, args...), NonDiffOdd{plus1(plus1(length(args))), 1, 1}()
end

function ChainRulesCore.rrule(::typeof(Core.tuple), args...)
    Core.tuple(args...), Δ->Core.tuple(NoTangent(), Δ...)
end

# TODO: What to do about these integer rules
@ChainRulesCore.non_differentiable Base.rem(a::Integer, b::Type)

ChainRulesCore.canonicalize(::ChainRulesCore.ZeroTangent) = ChainRulesCore.ZeroTangent()

# Skip AD'ing through the axis computation
function ChainRules.rrule(::typeof(Base.Broadcast.instantiate), bc::Base.Broadcast.Broadcasted)
    return Base.Broadcast.instantiate(bc), Δ->begin
        Core.tuple(NoTangent(), Δ)
    end
end

using StaticArrays

# Force the static arrays constructor to use a vector representation of
# the cotangent space.

struct to_tuple{N}; end
@generated function (::to_tuple{N})(Δ) where {N}
    :( (NoTangent(), Core.tuple( $( ( :(Δ[$i]) for i = 1:N )...) )) )
end
(::to_tuple)(Δ::SArray) = getfield(Δ, :data)

function ChainRules.rrule(::Type{SArray{S, T, N, L}}, x::NTuple{L,T}) where {S, T, N, L}
    SArray{S, T, N, L}(x), to_tuple{L}()
end

function ChainRules.rrule(::Type{SArray{S, T, N, L}}, x::NTuple{L,Any}) where {S, T, N, L}
    SArray{S, T, N, L}(x), to_tuple{L}()
end

function ChainRules.frule((_, ∂x), ::Type{SArray{S, T, N, L}}, x::NTuple{L,T}) where {S, T, N, L}
    SArray{S, T, N, L}(x), SArray{S, T, N, L}(∂x)
end

function ChainRules.frule((_, ∂x), ::Type{SArray{S, T, N, L}}, x::NTuple{L,Any}) where {S, T, N, L}
    SArray{S, T, N, L}(x), SArray{S}(∂x)
end

@ChainRulesCore.non_differentiable StaticArrays.promote_tuple_eltype(T)

function ChainRules.frule((_, ∂A), ::typeof(getindex), A::AbstractArray, args...)
    getindex(A, args...), getindex(∂A, args...)
end

function ChainRules.rrule(::typeof(map), ::typeof(+), A::AbstractArray, B::AbstractArray)
    map(+, A, B), Δ->(NoTangent(), NoTangent(), Δ, Δ)
end

function ChainRules.rrule(::typeof(map), ::typeof(+), A::AbstractVector, B::AbstractVector)
    map(+, A, B), Δ->(NoTangent(), NoTangent(), Δ, Δ)
end

function ChainRules.rrule(AT::Type{<:Array{T,N}}, x::AbstractArray{S,N}) where {T,S,N}
    # We're leaving these in the eltype that the cotangent vector already has.
    # There isn't really a good reason to believe we should convert to the
    # original array type, so don't unless explicitly requested.
    AT(x), Δ->(NoTangent(), Δ)
end

function ChainRules.rrule(AT::Type{<:Array}, undef::UndefInitializer, args...)
    # We're leaving these in the eltype that the cotangent vector already has.
    # There isn't really a good reason to believe we should convert to the
    # original array type, so don't unless explicitly requested.
    AT(undef, args...), Δ->(NoTangent(), NoTangent(), ntuple(_->NoTangent(), length(args))...)
end

function unzip_tuple(t::Tuple)
    map(x->x[1], t), map(x->x[2], t)
end

function ChainRules.rrule(::typeof(unzip_tuple), args::Tuple)
    unzip_tuple(args), Δ->(NoTangent(), map((x,y)->(x,y), Δ...))
end

struct BackMap{T}
    f::T
end
(f::BackMap{N})(args...) where {N} = ∂⃖¹(getfield(f, :f), args...)
back_apply(x, y) = x(y)
back_apply_zero(x) = x(Zero())

function ChainRules.rrule(::typeof(map), f, args::Tuple)
    a, b = unzip_tuple(map(BackMap(f), args))
    function back(Δ)
        (fs, xs) = unzip_tuple(map(back_apply, b, Δ))
        (NoTangent(), sum(fs), xs)
    end
    function back(Δ::ZeroTangent)
        (fs, xs) = unzip_tuple(map(back_apply_zero, b))
        (NoTangent(), sum(fs), xs)
    end
    a, back
end

function ChainRules.rrule(::typeof(Base.ntuple), f, n)
    a, b = unzip_tuple(ntuple(BackMap(f), n))
    a, function (Δ)
        (NoTangent(), sum(map(back_apply, b, Δ)), NoTangent())
    end
end

function ChainRules.frule(_, ::Type{Vector{T}}, undef::UndefInitializer, dims::Int...) where {T}
    Vector{T}(undef, dims...), zeros(T, dims...)
end

@ChainRules.non_differentiable Base.:(|)(a::Integer, b::Integer)
@ChainRules.non_differentiable Base.throw(err)
@ChainRules.non_differentiable Core.Compiler.return_type(args...)
ChainRulesCore.canonicalize(::NoTangent) = NoTangent()

# Disable thunking at higher order (TODO: These should go into ChainRulesCore)
function ChainRulesCore.rrule(::Type{Thunk}, thnk)
    z, ∂z = ∂⃖¹(thnk)
    z, Δ->(NoTangent(), ∂z(Δ)...)
end

function ChainRulesCore.rrule(::Type{InplaceableThunk}, add!!, val)
    val, Δ->(NoTangent(), NoTangent(), Δ)
end


# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
    ZerosBridge{T} <: Bridges.Variable.AbstractBridge

`ZerosBridge` implements the following reformulation:

* ``x \\in \\{0\\}`` into the substitution rule ``x = 0``,

where `T` is the coefficient type of `0`.

## Source node

`ZerosBridge` supports:

 * [`MOI.VectorOfVariables`](@ref) in [`MOI.Zeros`](@ref)

## Target nodes

`ZerosBridge` does not create target nodes. It replaces all instances of `x`
with `0` via substitution. This means that no variables are created in the
underlying model.

## Caveats

The bridged variables are similar to parameters with zero values. Parameters
with non-zero values can be created with constrained variables in
[`MOI.EqualTo`](@ref) by combining a [`VectorizeBridge`](@ref) and this bridge.

However, functions modified by `ZerosBridge` cannot be unbridged. That is, for a
given function, we cannot determine if the bridged variables were used.

A related implication is that this bridge does not support
[`MOI.ConstraintDual`](@ref). However, if a [`MOI.Utilities.CachingOptimizer`](@ref)
is used, the dual can be determined by the bridged optimizer using
[`MOI.Utilities.get_fallback`](@ref) because the caching optimizer records the
unbridged function.
"""
struct ZerosBridge{T} <: AbstractBridge
    n::Int # Number of variables
end

const Zeros{T,OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T},OT}

function bridge_constrained_variable(
    ::Type{ZerosBridge{T}},
    model::MOI.ModelLike,
    set::MOI.Zeros,
) where {T}
    return ZerosBridge{T}(MOI.dimension(set))
end

function supports_constrained_variable(::Type{<:ZerosBridge}, ::Type{MOI.Zeros})
    return true
end

function MOI.Bridges.added_constrained_variable_types(::Type{<:ZerosBridge})
    return Tuple{Type}[]
end

function MOI.Bridges.added_constraint_types(::Type{<:ZerosBridge})
    return Tuple{Type,Type}[]
end

# Attributes, Bridge acting as a model
MOI.get(::ZerosBridge, ::MOI.NumberOfVariables)::Int64 = 0

MOI.get(::ZerosBridge, ::MOI.ListOfVariableIndices) = MOI.VariableIndex[]

# References
MOI.delete(::MOI.ModelLike, ::ZerosBridge) = nothing

# Attributes, Bridge acting as a constraint

function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::ZerosBridge)
    return MOI.Zeros(bridge.n)
end

function MOI.get(
    ::MOI.ModelLike,
    ::MOI.ConstraintPrimal,
    bridge::ZerosBridge{T},
) where {T}
    return zeros(T, bridge.n)
end

function MOI.get(
    ::MOI.ModelLike,
    ::MOI.ConstraintDual,
    ::ZerosBridge{T},
) where {T}
    return error(
        "Unable to query the dual of a variable bound that was reformulated " *
        "using `ZerosBridge`. This usually arises in conic solvers when a " *
        "variable is fixed to a value. As a work-around, instead of creating " *
        "a fixed variable using variable bounds like `p == 1`, add an affine " *
        "equality constraint like `1 * p == 1` (or `[1 * p - 1,] in Zeros(1)`).",
    )
end

function MOI.get(
    ::MOI.ModelLike,
    ::MOI.VariablePrimal,
    ::ZerosBridge{T},
    ::MOI.Bridges.IndexInVector,
) where {T}
    return zero(T)
end

function MOI.Bridges.bridged_function(
    ::ZerosBridge{T},
    ::MOI.Bridges.IndexInVector,
) where {T}
    return zero(MOI.ScalarAffineFunction{T})
end

function unbridged_map(
    ::ZerosBridge,
    ::MOI.VariableIndex,
    ::MOI.Bridges.IndexInVector,
)
    # The transformation is not recoverable, so we explicitly return `nothing`
    # here. See the docstring of `unbridged_map` for details.
    return nothing
end


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENSE in the project root.
# ------------------------------------------------------------------

module ImageInpainting

using ImageMorphology
using ImageFiltering
using FFTW: set_num_threads
using CpuId: cpucores

include("utils.jl")
include("pointgradients.jl")
include("inpaint.jl")

export
  # main function
  inpaint,

  # algorithms
  Criminisi

end


# vim: set et ts=2 sw=2;

# Non-parametric optimizers
# ## Uses
using Test
using Random
using Parameters
using ResumableFunctions

# ## Config
@with_kw mutable struct It
  data = (file="auto93.csv", dir="data",some=128,best=.75)
  char = (skip='?',less='<',more='>',num=':',klass='!')
  str  = (skip="?")
  some = (max=64,bins=.5, cohen=0.3, trivial=1.05)
  divs = (few=126)
  seed = 1
end

# ## Globals
it=It()
Random.seed!(it.seed)
no = nothing

# -------------------------------------------------------------------
# ## Columns
# Count all the symbols. Keep a sample of the numbers.
@with_kw mutable struct Some pos=0;txt="";w=1;n=0;_all=[];ok=true end
@with_kw mutable struct Sym  pos=0;txt="";w=1;n=0;seen=Dict() end
@with_kw mutable struct Skip pos=0;txt="";w=1;n=0 end

function inc!(i,x::Array) for y in x inc!(i,y) end; i end
function inc!(i,x)
  inc1!(i::Skip, x) = i
  inc1!(i::Sym,  x) = begin
    new = i.seen[x] = 1 + get(i.seen,x,0)
    if new > i.n i.n, i.mode = now, x end end
  inc1!(i::Some, x) = begin
    m = length(i._all)
    if m < it.some.max
      i.ok=false; push!(i._all, x);
    elseif rand() < m/i.n
      i.ok=false; i._all[int(m*rand())+1]=x end
  end
  x==it.char.skip ? x : begin i.n += 1; inc1!(i,x); x end end

function mid(i::Sym) i.mode end
function var(i::Sym) sum(-v/i.n*log(2,v/i.n) for (_,v) in i.seen) end

function mid( i::Some)   a=all(i); per(a,.5) end
function var( i::Some)   a=all(i); (per(a,.9) - per(a,.1)) / 2.56 end
function norm(i::Some,x) a=all(i);
  x==it.char.skip ? x : (x-a[1])/(a[end]-a[1]+1E-32) end

function all(i::Some)
  i._all = i.ok ? i._all : sort(i._all)
  i.ok = true
  i._all end

function combine(i::Some,j::Some, small,lo)
  mi, mj = mid(i), mid(j)
  if (abs(mi) - abs(mj) < small) || (mi < lo && mi < lo)
    inc!(Some(), [i._all;j._all]) end end

# -------------------------------------------------------------------
# ## Table
# Load rows, Summarize the columns.
@with_kw mutable struct Table ys=[]; xs=[]; rows=[]; cols=[] end
@with_kw mutable struct Row   has=[]; gt=0; tag=no end

function data(file; t=Table())
  col(;txt="", pos=0, c=it.char) = begin
    what = c.less in txt||c.more in txt||c.num in txt ? Some : Sym
    what = c.skip in txt ? Skip : what
    now = what(txt=txt, pos=pos, w= c.less in txt ? -1 : 1)
    at  = c.less in txt||c.more in txt||c.klass in txt ? t.ys : t.xs
    push!(at, now)
    now
  end
  cols(a)  = [col(txt=txt, pos=pos) for (pos,txt) in enumerate(a)]
  cells(a) = Row(has= [inc!(c, a[c.pos]) for c in t.cols])
  for a in csv(it.data.dir * "/" * file)
    length(t.cols)==0 ? t.cols=cols(a) : push!(t.rows, cells(a)) end
  t end

# -------------------------------------------------------------------
# ## Classify
# Score rows by how they are better than others.
# Tag the `best` percent rows.
function tag(t::Table)
  function gt(row1, row2)
    "Zitler's continous domination predicate (from IBEA, 2005)."
    s1, s2, n = 0, 0, length(t.ys)
    for col in t.ysz
      a, b = row1.has[col.pos], row2.has[col.pos]
      a, b = norm(col, a), norm(col, b)
      s1  -= ℯ ^ (col.w * (a - b) / n)
      s2  -= ℯ ^ (col.w * (b - a) / n) end
    s1/n < s2/n
  end
  n = it.data.some
  print(":::",any(t.rows))
  for row in t.rows
    row.gt = sum(gt(row, any(t.rows)) for _ in 1:n)/n  end
  for (n, row) in enumerate(sort(t.rows, by = x -> x.gt))
    row.tag = n > length(t.rows) * it.data.best end
  t end

# -------------------------------------------------------------------
# ## Discretize
# Score rows by how they are better than others.
# Tag the `best` percent rows.
@with_kw mutable struct Span
   lo=typemax(Int); hi=typemin(Int); _has=[] end

function discretize(x::Some, t:Table)
  function div(xsmall,ysmall,ymin,xy)
    n = length(xy) * the data.xchop
    while n < 4 && n <= length(xy)/2 n+= 1.2 end
    n, now, tmp = int(n), int(n), []
    b4, span    = 1, Span(lo=xy[1][1])
    while now < length(xy) - n
      x=xy[now][1]
      span.hi = x
      now += 1
      if (now - b4 => n    && now < len(xy) - 2 &&
          x != xy[now][1]  && span.hi - span.lo > xsmall)
        span._has= [z[2] for z in xy[b4:now]]
        tmp += [span]
        span = Span(lo=xy[now][1])
        b4   = now
        now += n end end
     tmp += [Span(lo=xy[b4][1],hi=xy[end][1],
                  _has=[z[2] for z in xy[b4:]])]
     merge(tmp,ysmall,ymin) end

  function merge(b4, ysmall, ymin)
    j, now = 0, []
    while j < length(b4)
      a=b4[j]
      if j < length(b4)
        b = b4[j+1]
        c  = combine(a._has,b._has, ysmall, ymin)
        if c != nothing
          now += [Span(lo=a.lo,hi=
    end
    length(now) == length(b4) ? b4 : merge(now,ymin,small)

  for row in rows:
    inc!(y, row.gt)
    push!(xy, (row.has[x.pos], row.gt)) end
  tmp = div(var(x)*it.some.xsmall,
            var(y)*it.some.ysmall,
            per(all(y), it.data.best),
            sort(xy))
  tmp[1].lo = typemin(Int)
  tmp[end].hi = typemax(Int)
  tmp end

 # -------------------------------------------------------------------
# ## Misc Utils
# ### One-liners.
same(s)  = s                                  #noop
int(x)   = floor(Int,x)                       #round
per(a,n) = a[int(length(a)*n)+1]                #percentile
thing(x) = try parse(Float64,x) catch _ x end #coerce
say(i)   = println(o(i))                      #print+nl
any(a)   = a[ int(length(a) * rand()) + 1 ]   #pick any one
few(a,n=it.divs.few) =                        #pick many
  length(a)<n ? a : [any(a) for _ in 1:n]

# ### How to print a struct
# Skips any fields starting with "`_`".
o(i::String)     = i
o(i::SubString)  = i
o(i::Char)       = string(i)
o(i::Number)     = string(i)
o(i::Array)      = "["*join(map(o,i),", ")*"]"
o(i::NamedTuple) = "("*join(map(o,i),", ")*")"
o(i::Dict)       = "{"*join(["$k="*o(v) for (k,v) in i],", ")*"}"
o(i::Span)       = begin
  if i.lo <= typemin(Int) return "<= $(i.hi)"
  if i.hi <= typemax(Int) return ">= $(i.lp)"
  "[$(i.lo)..$(i.hi)]" end
o(i) = begin
  s, pre="$(typeof(i)){", ""
  for f in sort([x for x in fieldnames(typeof(i))
                  if !("$x"[1] == '_')])
    s = s * pre * "$f=$(o(getfield(i,f)))"
    pre=", " end
  return s * "}" end

# ### How to read a CSV File
# Skip blank lines. Coerce numeric strings to numbers.
@resumable function csv(file;zap=r"(\s+|#.*)") #iterate on file
  b4=""
  for line in eachline(file)
    line = replace(line,zap =>"")
    if length(line) != 0
      if line[end] == ',' # if line ends with ",",
        b4 = b4 * line    # join it to next
      else
        @yield [thing(x) for x in split(b4*line,",")]
        b4 = "" end end end end

# ## Tests
go()  = include("jump.jl")
function run()
  t=tag(data("auto.csv"))
  for r in sort(t.rows, by = x -> x.gt)
    print(r.tag) end end

# ## Command line


module TalusPlotScript

using SynapseClient
using DISSEQT
using DISSEQT.Plots
using DISSEQT.AnnotatedArrays
using DISSEQT.SynapseTools
using Statistics
using DataFrames
using CSV
using JLD
using Gadfly
using Cairo
using Fontconfig

function cachemetadata(syn,alignmentFolder,metadataID)
    metadataCacheFilename = "metadatacache.csv"
    metadata = DataFrame()
    if isfile(metadataCacheFilename)
        metadata = CSV.read(metadataCacheFilename; missingstrings=["","NA"])
    else
        # Get metadata (metadataID can be the synapse ID of a .csv file or of a folder containing .csv files).
        metadata = getsamplemetadata(syn, metadataID, :Rejected=>"No") # Always remove rejected samples

        # extend metadata
        appendswarmids!(syn,metadata,alignmentFolder)
        appendalignedids!(syn,metadata,alignmentFolder)

        # download swarms and consensus sequences
        downloadswarms!(syn,metadata,cacheDir="MutantSwarms")
        downloadconsensuses!(syn,metadata,cacheDir="MutantSwarms")

        CSV.write(metadataCacheFilename, metadata; missingstring="NA")
    end
    metadata
end

function main()
    syn = SynapseClient.login()

    # projectFolder should point to MyProject folder (containing subfolders Metadata and Analysis)
    projectFolder   = "syn11639899" # VignuzziLabPublic/Projects/FitnessLandscapes
    analysisFolder  = getchildbyname(syn, projectFolder, "Analysis")
    alignmentFolder = getchildbyname(syn, analysisFolder, "Alignment")
    referenceFolder = getchildbyname(syn, alignmentFolder, "ReferenceGenomes")

    isdir("MutantSwarms") || mkdir("MutantSwarms")
    isdir("plots") || mkdir("plots")

    # set to true to upload files
    doUpload = true
    uploadName = "fitness landscapes invitro"

    metadataID = getchildbyname(syn, projectFolder, "Metadata", "fitness landscapes invitro.csv")
    metadata = cachemetadata(syn, alignmentFolder, metadataID)

    # Get Reference Genomes
    referenceNames = ["WT", "Less", "More", "Stop"]
    referenceIDs, referenceGenomes = getreferencegenomes(syn, referenceFolder, referenceNames)

    # get CVB3 feature list
    featureFileID = getchildbyname(syn,referenceFolder,"CVB3_features.csv")
    features = CSV.read(localpath(syn, featureFileID); missingstrings=["","NA"])

    positions = featurepositions(features, "ORF") # coding positions

    # load swarms
    swarms  = loadswarm(metadata[:SampleID],metadata[:SwarmPath],metadata[:ConsensusPath])

    # filter by positions
    swarms = swarms[:,:,:,inset(:position,positions),:]

    coverage = dropdims(swarms[:coverage]; dims=(1,2,3))
    meanCoverage = mean(coverage; dims=1)[:]

    # filter by mean read coverage at coding sites
    sampleMask = meanCoverage .>= 1000
    println("Removed ", count(.~sampleMask), " samples due to low coverage.")
    swarms  = swarms[:,:,:,:,sampleMask]
    metadata = metadata[sampleMask,:]

    # remove mixed samples
    mixes = estimatemix(swarms, referenceGenomes)
    mixMask = maximum(mixes;dims=2)[:] .>= 0.98
    println("Removed ", count(.~mixMask), " mixed samples.")
    swarms  = swarms[:,:,:,:,mixMask]
    metadata = metadata[mixMask,:]

    println("Total number of samples used: ", size(swarms,5))

    # load limit of detection
    limitOfDetectionID = getchildbyname(syn,analysisFolder,"LimitOfDetection","fitness landscapes invitro","limitofdetection.jld")
    limitOfDetection = convert(AnnotatedArray,load(localpath(syn,limitOfDetectionID,downloadLocation="MutantSwarms")))
    limitOfDetection = limitOfDetection[:,:,:,inset(:position,positions)] # filter by positions
    @assert isequal(swarms[:position][:], limitOfDetection[:position][:]) # the positions must be the same

    # get rid of annotations to simplify math stuff
    # arrays are 4x4x4xPxN - nuc3 x nuc2 x nuc1 x positions x samples
    s = convert(Array, swarms)
    α = convert(Array, limitOfDetection)

    # Put a lower limit on the limit of detection
    α = max.(α,1e-3)

    # apply transformation
    X = log2.(s.+α)

    # center variables
    X = X .- mean(X;dims=5) # remove mean over samples

    # reshape to VxN - variables x samples
    X = reshape(X, 64*size(X,4), size(X,5))

    # Non-intrusive prefiltering of variables that will never contribute
    σThreshold = 1e-2 # same as the lower limit we use for projection score
    σ = dropdims(std(X;dims=2);dims=2)
    σ = σ/maximum(σ) # normalize
    X = X[σ.>=σThreshold,:]

    uploads = UploadList()
    dependencies = unique(vcat(featureFileID, referenceIDs, limitOfDetectionID, metadata[:SwarmID], metadata[:MetadataID]))

    nbrDims = 40

    pl = talusplot(X, 1:nbrDims, Guide.title("Talus plot"))
    markforupload!(uploads, "plots", saveplot(pl, [:png,:svg,:pdf], "plots/talus", width=29.7cm/2, height=21cm/2), dependencies)

    doUpload && uploadfiles(syn, createchildfolder(syn, analysisFolder, "Dimensionality", uploadName), uploads, @__FILE__, "Talus plot")

    nothing
end

end
TalusPlotScript.main();


## Issue examples ####

# Dimensions should be 330 x 570
cd("assets/")

wcpath = joinpath(ENV["SDMLAYERS_PATH"], "WorldClim", "BioClim", "10", "wc2.1_10m_bio_1.tif")
tmpfile = tempname()

query = `gdalwarp -te -145.0 20.0 -50.0 75.0 $(wcpath) $(tmpfile)`
run(query)

using ArchGDAL
d = ArchGDAL.read(tmpfile)
ArchGDAL.height(d) # 330
ArchGDAL.width(d) # 570

# hcat/vcat are wrong
l1 = SimpleSDMPredictor(WorldClim, BioClim, 1; left=0.0, right=10.0, bottom=0.0, top=10.0);
l2 = SimpleSDMPredictor(WorldClim, BioClim, 1; left=0.0, right=10.0, bottom=10.0, top=20.0);
l3 = SimpleSDMPredictor(WorldClim, BioClim, 1; left=10.0, right=20.0, bottom=0.0, top=10.0);

vl1 = vcat(l1, l2) # latitudes span makes no sense
ml1 = hcat(l1, l3) # things look fine here, but are they?

latitudes(vl1)
vl1.bottom < vl1.top # false

latitudes(ml1)
ml1.bottom < ml1.top # false

# Inversed bounds
l1 = SimpleSDMPredictor(WorldClim, BioClim, 1)
l2 = SimpleSDMPredictor(copy(l1.grid), 180.0, -180.0, 90.0, -90.0) # looks the same, but bounds really are inversed

l1.grid == l2.grid # same grid
extrema(longitudes(l1)) == extrema(longitudes(l2)) # same longitude extremas (displayed by show method)
extrema(latitudes(l1)) == extrema(latitudes(l2)) # same latitude extremas too

longitudes(l1) == longitudes(l2) # false, inversed
latitudes(l1) == latitudes(l2) # false, inversed

# Geotiff writing
l1 = SimpleSDMPredictor(WorldClim, BioClim, 1)
length(l1)
geotiff(tempname(), l1)
length(l1)


# This file is a part of SimilaritySearch.jl

import Base: push!
export ParallelExhaustiveSearch, search

struct ParallelExhaustiveSearch{DistanceType<:SemiMetric,DataType<:AbstractDatabase} <: AbstractSearchContext
    dist::DistanceType
    db::DataType
    lock::Threads.SpinLock
end

ParallelExhaustiveSearch(dist::SemiMetric, db::AbstractVecOrMat) = ParallelExhaustiveSearch(dist, convert(AbstractDatabase, db))
ParallelExhaustiveSearch(dist::SemiMetric, db::AbstractDatabase) = ParallelExhaustiveSearch(dist, db, Threads.SpinLock())

"""
    ParallelExhaustiveSearch(; dist=SqL2Distance(), db=VectorDatabase{Float32}())

Solves queries evaluating `dist` in parallel for the query and all elements in the dataset.
Note that this should not be used in conjunction with `searchbatch(...; parallel=true)` since they will compete for resources.
"""
function ParallelExhaustiveSearch(; dist=SqL2Distance(), db=VectorDatabase{Float32}())
    ParallelExhaustiveSearch(dist, db, Threads.SpinLock())
end

getpools(index::ParallelExhaustiveSearch) = nothing
Base.copy(ex::ParallelExhaustiveSearch; dist=ex.dist, db=ex.db) = ParallelExhaustiveSearch(dist, db, Threads.SpinLock())

"""
    search(ex::ParallelExhaustiveSearch, q, res::KnnResult; pools=nothing)

Solves the query evaluating all items in the given query.
"""
function search(ex::ParallelExhaustiveSearch, q, res::KnnResult; pools=nothing)
    dist = ex.dist
    elock = ex.lock
    Threads.@threads for i in eachindex(ex)
        d = evaluate(dist, ex[i], q)
        try
            lock(elock)
            push!(res, i, d)
        finally
            unlock(elock)
        end
    end

    (res=res, cost=length(ex))
end

function Base.push!(ex::ParallelExhaustiveSearch, u)
    push!(ex.db, u)
end


@testset "Redundancy removal" begin
    A = [9.69462e-5 9.69462e-5 9.69462e-5 9.69462e-5 0.0 0.0 0.0 0.0]
    B = [8.95335e-5 8.95335e-5 8.95335e-5 8.95335e-5 0.0 0.0 0.0 0.0]
    a = [1.]
    @test CutPruners.checkredundancy(A, a, B, a, true, true, 1e-5) == [1]
    @test CutPruners.checkredundancy(A, a, B, a, true, false, 1e-5) == [1]
    @test CutPruners.checkredundancy(A, a, B, a, true, true, 1e-6) == Int[]
    @test CutPruners.checkredundancy(A, a, B, a, true, false, 1e-6) == Int[]
    # A and B will be renormalized so A would be very close to B and even 1e-16 works
    @test CutPruners.checkredundancy(A, a, B, a, false, true, 1e-16) == Int[]
    @test CutPruners.checkredundancy(A, a, B, a, false, false, 1e-16) == [1]
end
@testset "Self redundancy" begin
    A = [9.69462e-5 9.69462e-5;
         8.95335e-5 8.95335e-5;
         0.0 0.0; 0.0 0.0]
    a = ones(Float64, 4)
    @test CutPruners.checkredundancy(A, a, true, true, 1e-5) == [2, 4]
    @test CutPruners.checkredundancy(A, a, true, false, 1e-5) == [2, 4]
    @test CutPruners.checkredundancy(A, a, true, true, 1e-6) == Int[4]
    @test CutPruners.checkredundancy(A, a, true, false, 1e-6) == Int[4]
    # A and B will be renormalized so A would be very close to B and even 1e-16 works
    @test CutPruners.checkredundancy(A, a, false, true, 1e-16) == Int[4]
    @test CutPruners.checkredundancy(A, a, false, false, 1e-6) == [2, 4]
end


"""

new optimazation idea  - try to put all data in boolean arrays in shared memory  when getting means
next we would need only to read shared memory - yet first one need to check wheather there would be enough shmem on device

calculating intercalss correlation
"""
module InterClassCorrKernel

using CUDA, ..CUDAGpuUtils ,..IterationUtils,..ReductionUtils , ..MemoryUtils,..CUDAAtomicUtils

export prepareInterClassCorrKernel,calculateInterclassCorr

"""
preparation for the interclass correlation kernel - we prepare the  GPU arrays - in this kernel they are small , calculate required loop iterations
and using occupancy API calculate optimal number of  threads and blocks for both kernels
also return arguments lists for both kernels
"""
function prepareInterClassCorrKernel()
  mainArrDims= (2,2,2)
  sumOfGold= CUDA.zeros(1);
  sumOfSegm= CUDA.zeros(1);
  sswTotal= CUDA.zeros(1);
  ssbTotal= CUDA.zeros(1);
  numberToLooFor= 1
  totalNumbOfVoxels= (mainArrDims[1]*mainArrDims[2]*mainArrDims[3])
  pixPerSlice = mainArrDims[1]*mainArrDims[2]
  iterLoop=5
  argsMain=(sumOfGold,sumOfSegm
     ,sswTotal
     ,ssbTotal
     ,iterLoop,pixPerSlice,totalNumbOfVoxels
     ,numberToLooFor )
  get_shmem(threads) = 4*33+3  #the same for both kernels
  threads,blocks =getThreadsAndBlocksNumbForKernel(get_shmem, kernel_InterClassCorr,(CUDA.zeros(2,2,2),CUDA.zeros(2,2,2),argsMain...))
  totalNumbOfVoxels= (mainArrDims[1]*mainArrDims[2]*mainArrDims[3])
  pixPerSlice= cld(totalNumbOfVoxels,blocks)
  iterLoop = UInt32(fld(pixPerSlice, threads[1]*threads[2]))

  argsMain=(sumOfGold,sumOfSegm
     ,sswTotal
     ,ssbTotal
     ,iterLoop,pixPerSlice,totalNumbOfVoxels
     ,numberToLooFor )

  return(argsMain, threads,blocks, totalNumbOfVoxels)
end

"""
calculates slicewise and global interclass correlation metric
"""
function calculateInterclassCorr(flatGold
                                ,flatSegm,threads,blocks
                                ,args,numberToLooFor)::Float64
 mainArrDims= size(flatGold)
 totalNumbOfVoxels= length(flatGold)
 pixPerSlice= cld(totalNumbOfVoxels,blocks)
 iterLoop = UInt32(fld(pixPerSlice, threads[1]*threads[2]))
 #resetting
 for i in  1:4
  CUDA.fill!(args[i],0)
 end
 argsMain=(args[1],args[2]
 ,args[3]
 ,args[4]
 ,iterLoop,pixPerSlice,totalNumbOfVoxels
 ,numberToLooFor )

@cuda threads=threads blocks=blocks cooperative = true kernel_InterClassCorr(flatGold,flatSegm,argsMain...)

  # println("grandMean[1] $(grandMean[1])  \n")
# @cuda threads=threads blocks=blocks  kernel_InterClassCorr(flatGold  ,flatSegm,args... )
     ssw = args[3][1]/args[7];
     ssb = args[4][1]/(args[7]-1) * 2;

  return (ssb - ssw)/(ssb + ssw);

end

"""
adapted from https://github.com/JuliaGPU/CUDA.jl/blob/afe81794038dddbda49639c8c26469496543d831/src/mapreduce.jl
goldBoolGPU - array holding data of gold standard bollean array
segmBoolGPU - boolean array with the data we want to compare with gold standard
ic - holding single value for global interclass correlation
intermediateRes- array holding slice wise results for ic
loopNumb - number of times the single lane needs to loop in order to get all needed data
sliceEdgeLength - length of edge of the slice we need to square this number to get number of pixels in a slice
amountOfWarps - how many warps we can stick in the block
"""

function kernel_InterClassCorr(flatGold  ,flatSegm,sumOfGold,sumOfSegm
       ,sswTotal
       ,ssbTotal
       ,iterLoop,pixPerSlice,totalNumbOfVoxels
       ,numberToLooFor )
   grid_handle = this_grid()

   #for storing results from warp reductions
   shmemSum = @cuStaticSharedMem(Float32, (32,2))   #thread local values that are meant to store some results - like means ...
   grandMeanShmem = @cuStaticSharedMem(Float32, (1))
   offsetIter = UInt8(1)

   locValA = Float32(0)
   locValB = Float32(0)
        #reset shared memory
        @ifY 1 shmemSum[threadIdxX(),1]=0 ;   @ifY 2 shmemSum[threadIdxX(),2]=0
        sync_threads()

        #first we add 1 for each spot we have true - so we will get sum  - and from sum we can get mean
        @iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,begin
            locValA += flatGold[i]==numberToLooFor
            locValB += flatSegm[i]==numberToLooFor
        end)#for
        @redWitAct(offsetIter,shmemSum,  locValA,+,     locValB,+  )
   sync_threads()
    @addAtomic(shmemSum, sumOfGold,sumOfSegm)
    ### sums should be in place
sync_grid(grid_handle)
grandMeanShmem[1]= ( (sumOfGold[1]/totalNumbOfVoxels) + (sumOfSegm[1]/totalNumbOfVoxels ))/2
locValA = 0
locValB = 0
@ifY 1 shmemSum[threadIdxX(),1]=0 ;
@ifY 2 shmemSum[threadIdxX(),2]=0 ;
sync_threads()

@iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,begin
m =  ((flatGold[i]==numberToLooFor) +(flatSegm[i]==numberToLooFor))/2
locValA += (((flatGold[i]==numberToLooFor)- m)^2) +(((flatSegm[i]==numberToLooFor)- m )^2)
locValB += ((m - grandMeanShmem[1])^2)
end)#for
#now we accumulated ssw and locValB - we need to reduce it
offsetIter = UInt8(1)
@redWitAct(offsetIter,shmemSum,  locValA,+,     locValB,+  )
sync_threads()
@addAtomic(shmemSum, sswTotal,ssbTotal)
return nothing

end

# function kernel_InterClassCorr(flatGold
#     ,flatSegm,
#     sumOfGold,sumOfSegm
#        ,sswTotal
#        ,ssbTotal
#        ,iterLoop,pixPerSlice,totalNumbOfVoxels
#        ,numberToLooFor,grandMean )

#     #for storing results from warp reductions
#     shmemSum = @cuStaticSharedMem(Float32, (33,2))   #thread local values that are meant to store some results - like means ...
#     @ifY 1 shmemSum[threadIdxX(),1]=0 ;
#     @ifY 2 shmemSum[threadIdxX(),2]=0 ;
#     sync_threads()
#     locValA = Float32(0.0)
#     locValB = Float32(0.0)

#     @iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,begin
#         m =  ((flatGold[i]==numberToLooFor) +(flatSegm[i]==numberToLooFor))/2
#         locValA += (((flatGold[i]==numberToLooFor)- m)^2) +(((flatSegm[i]==numberToLooFor)- m )^2)
#         locValB += ((m - grandMean[1])^2)
#     end)#for
#     #now we accumulated ssw and locValB - we need to reduce it
#     offsetIter = UInt8(1)
#     @redWitAct(offsetIter,shmemSum,  locValA,+,     locValB,+  )
#    sync_threads()
#     @addAtomic(shmemSum, sswTotal,ssbTotal)
#     return nothing

# end

# function kernel_InterClassCorr_means(goldlGPU,segmGPU
#      ,sumOfGold,sumOfSegm
#      ,sswTotal
#      ,ssbTotal
#      ,iterLoop,pixPerSlice,totalNumbOfVoxels
#      ,numberToLooFor )

# locValA = UInt32(0)
# locValB = UInt32(0)
# offsetIter= UInt8(1)
# shmemSum = @cuStaticSharedMem(UInt16, (33,2))   #thread local values that are meant to store some results - like means ...
# @ifY 1 shmemSum[threadIdxX(),1]= 0
# @ifY 2 shmemSum[threadIdxX(),2]= 0
# sync_threads()

# @iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,
# #inner expression
# begin
# #updating variables needed to calculate means
# locValA += goldlGPU[i]==numberToLooFor
# locValB += segmGPU[i]==numberToLooFor
# end)

# #tell what variables are to be reduced and by what operation
# @redWitAct(offsetIter,shmemSum,  locValA,+,     locValB,+  )
# sync_threads()
# @addAtomic(shmemSum, sumOfGold, sumOfSegm)

# return nothing
# end

# function kernel_InterClassCorr(goldlGPU,segmGPU
#   ,sumOfGold,sumOfSegm
#   ,sswTotal
#   ,ssbTotal
#   ,iterLoop,pixPerSlice,totalNumbOfVoxels
#   ,numberToLooFor )
# grandMean = @cuStaticSharedMem(Float32, (1))
# @ifXY 1 3 grandMean[1]= ( (sumOfGold[1]/totalNumbOfVoxels) + (sumOfSegm[1]/totalNumbOfVoxels ))/2
# ssw = Float32(0)
# ssb = Float32(0)
# m = Float32(0)
# offsetIter= UInt8(1)
# shmemSum = @cuStaticSharedMem(Float32, (33,2))   #thread local values that are meant to store some results - like means ...
# @ifY 1 shmemSum[threadIdxX(),1]= 0
# @ifY 2 shmemSum[threadIdxX(),2]= 0
# sync_threads()
# @iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,
#  #inner expression
#  begin
#   m =  ((goldlGPU[i]==numberToLooFor) +(segmGPU[i]==numberToLooFor))/2
#   ssw += (((goldlGPU[i]==numberToLooFor)- m)^2) +(((segmGPU[i]==numberToLooFor)- m )^2)
#   ssb += ((m - grandMean[1])^2)
#  end)

#  #tell what variables are to be reduced and by what operation
#  @redWitAct(offsetIter,shmemSum,  ssw,+,     ssb,+  )
# sync_threads()
# @addAtomic(shmemSum, sswTotal,ssbTotal)

#  return nothing

# end

# """
# calculates slicewise and global interclass correlation metric

# threadsForMeans, threadsForMain - tuples indicating dimensionality  of thread block
# bsically all needed arguments apart from gold standard and algorithm 3 dm arrays  and numberToLooFor should be given by prepareInterClassCorrKernel
# """
# function calculateInterclassCorr(goldGPU,segmGPU,argsMain, threadsMain,  blocksMain,threadsMean,blocksMean,argsMean, totalNumbOfVoxels)::Float64

#   ## resetting some entries to 0
# for i in 1:4
#   CUDA.fill!(argsMain[i],0)
# end

# @cuda threads=threadsMean  blocks=blocksMean  kernel_InterClassCorr_means(goldGPU,segmGPU,argsMean...)
# @cuda threads=threadsMain  blocks=blocksMain  kernel_InterClassCorr(goldGPU,segmGPU,argsMain...)

#   print("before correction ssw $(argsMain[4][1]) ssb $(argsMain[5][1])")

#      ssw = argsMain[4][1]/totalNumbOfVoxels;
#      ssb = (argsMain[5][1]/(totalNumbOfVoxels-1)) * 2;
#     print("ssw $(ssw) ssb $(ssb)")
# #  return (ssb - ssw)/(ssb + ssw);
#  return (ssb - ssw)/(ssb + ssw);

# end

# function kernel_InterClassCorr(goldlGPU,segmGPU
#      ,sumOfGold,sumOfSegm
#      ,sswTotal
#      ,ssbTotal
#      ,iterLoop,pixPerSlice,totalNumbOfVoxels
#      ,numberToLooFor )

# grid_handle = this_grid()

#  locValA = Float32(0)
#  locValB = Float32(0)
#   offsetIter= 1
#   grandMean = @cuStaticSharedMem(Float32, (1))
#   grandMeanSq = @cuStaticSharedMem(Float32, (1))
#   halfMingrandSq = @cuStaticSharedMem(Float32, (1))
#   oneMingrandSq = @cuStaticSharedMem(Float32, (1))

#   shmemSum = @cuStaticSharedMem(Float32, (33,2))   #thread local values that are meant to store some results - like means ...
#   @ifY 1 shmemSum[threadIdxX(),1]= Float32(0)
#   @ifY 2 shmemSum[threadIdxX(),2]= Float32(0)

#   sync_threads()
#   @iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,
#     #inner expression
#     begin
#         #updating variables needed to calculate means
#            locValA += goldlGPU[i]==numberToLooFor
#            locValB += segmGPU[i]==numberToLooFor
#     end)

#     #tell what variables are to be reduced and by what operation
#     @redWitAct(offsetIter,shmemSum,  locValA,+,     locValB,+  )
#   sync_threads()
#   @addAtomic(shmemSum, sumOfGold, sumOfSegm)

#   # @ifX 1  if(threadIdxY()>22 ) CUDA.@cuprint "aaa idY $(threadIdxY()) \n" end

#  sync_grid(grid_handle)
# #by now we should have all means
#  @ifXY 1 3 grandMean[1]= ( (sumOfGold[1]/totalNumbOfVoxels) + (sumOfSegm[1]/totalNumbOfVoxels ))/2
#  @ifXY 2 3 grandMeanSq[1]= ((( (sumOfGold[1]/totalNumbOfVoxels) + (sumOfSegm[1]/totalNumbOfVoxels ))/2))^2
#  @ifXY 3 3 halfMingrandSq[1]= (0.5- (( (sumOfGold[1]/totalNumbOfVoxels) + (sumOfSegm[1]/totalNumbOfVoxels ))/2))^2
#  @ifXY 4 3 oneMingrandSq[1]= (1- (( (sumOfGold[1]/totalNumbOfVoxels) + (sumOfSegm[1]/totalNumbOfVoxels ))/2))^2

#  @ifXY 1 1 CUDA.@cuprint "  "

#  @ifY 1 shmemSum[threadIdxX(),1]= Float32(0)
#  @ifY 2 shmemSum[threadIdxX(),2]= Float32(0)
#  sync_threads()
#  locValA = 0
#  locValB = 0
#   # m = Float32(0)
# sync_threads()

#   @iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,
#     begin
#       boolGold = segmGPU[i]==numberToLooFor
#       boolSegm = goldlGPU[i]==numberToLooFor
#       if(boolGold && boolSegm)
#         # CUDA.@cuprint "aa    "
#         locValB = locValB+oneMingrandSq[1]
#       elseif(boolGold ⊻ boolSegm)
#        locValA =locValA+ (0.25)
#        locValB += halfMingrandSq[1]#((0.5 - grandMean[1])*(0.5 - grandMean[1])   )
#       else
#         locValB =locValB+ grandMeanSq[1]
#       end

#       # elseif(boolGold || boolSegm)
#       #   m = 0.5#((boolGold ) +(boolSegm) )/2
#       #   locValA += 0.25
#       #   locValB += ((0.5 - grandMean[1])^2)
#       # else
#       #   locValB += grandMean[1]^2

#       # end
#     end )
#     #tell what variables are to be reduced and by what operation
#       offsetIter= 1
#       @redWitAct(offsetIter,shmemSum,  locValA,+,     locValB,+  )
#    sync_threads()
#     @addAtomic(shmemSum, sswTotal,ssbTotal)
#     #         #now we will have sum of all entries in given slice that comply to our predicate
#     #         #next we need to reduce values
#     #         while(offsetIter <32)
#     #             @inbounds locValA+=shfl_down_sync(FULL_MASK, locValA, offsetIter)
#     #             @inbounds locValB+=shfl_down_sync(FULL_MASK, locValB, offsetIter)
#     #             offsetIter<<= 1
#     #         end
#     #         # @ifX 1  if(threadIdxY()>22 ) CUDA.@cuprint "bbb idY $(threadIdxY()) \n" end
#     #         #@ifX 1  if(blockIdxX()==1 ) CUDA.@cuprint "bbb idY $(threadIdxY()) \n" end
#     #         #@ifY 1  if(blockIdxX()==1 ) CUDA.@cuprint "bbb idX $(threadIdxX()) \n" end

#     #         # @ifX 1  if(blockIdxX()==1 ) CUDA.@cuprint "bbb idY $(threadIdxY()) \n" end
#     #         #@ifY 1  if(blockIdxX()==1 ) CUDA.@cuprint "bbb idX $(threadIdxX()) \n" end

#     #  #   now we have sums in first threads of the warp we need to pass it to shared memory
#     #     if(threadIdxX()==1)
#     #       #TODO() try to do full reduction without those atomics
#     #         @inboundsCUDA.@atomic sswTotal[1]+=locValA
#     #         @inboundsCUDA.@atomic ssbTotal[1]+=locValB
#     #     end
#         # sync_threads()

#         # sync_warp(active_mask())

#         # #finally reduce from shared memory
#         # if(threadIdxY()==1)
#         #     offsetIter = UInt8(1)
#         #     while(offsetIter <32)
#         #         @inbounds shmemSum[threadIdxX(),1]+=shfl_down_sync(FULL_MASK, shmemSum[threadIdxX(),1], offsetIter)
#         #         @inbounds shmemSum[threadIdxX(),2]+=shfl_down_sync(FULL_MASK, shmemSum[threadIdxX(),2], offsetIter)
#         #     offsetIter<<= 1
#         #     end
#         # end

# # @ifXY 1 1 CUDA.@cuprint "shmemSum[1,1]  $(shmemSum[1,1])  \n "
#   #  sync_threads()
#   #  @addAtomic(shmemSum, sswTotal,ssbTotal)

#     return nothing

# end

end#InterClassCorrKernel

#     #offset for lloking for values in source arrays
#     offset = (pixelNumberPerSlice*(blockIdx().x-1))
#     #for storing results from warp reductions
#     shmemSum = @cuStaticSharedMem(Float32, (33,2))   #thread local values that are meant to store some results - like means ...
#     @ifY 1 shmemSum[threadIdxX(),1]=0 ;   @ifY 2 shmemSum[threadIdxX(),2]=0 ;@ifY 3 shmemSum[threadIdxX(),2]=0
#     sync_threads()
#     ssw::Float32 = Float32(0.0)
#     ssb::Float32 = Float32(0.0)

#     @unroll for k in UInt16(0):loopNumb
#         if(threadIdxX()+(threadIdxY()-1)*32+k*1024 <=pixelNumberPerSlice)
#         ind =offset+ threadIdxX()+(threadIdxY()-1)*32+k*1024
#         m =  ((flatGold[ind]==numberToLooFor) +(flatSegm[ind]==numberToLooFor))/2
#         ssw += (((flatGold[ind]==numberToLooFor)- m)^2) +(((flatSegm[ind]==numberToLooFor)- m )^2)
#         ssb += ((m - grandMean[1])^2)
#         end#if
#     end#for
#     #now we accumulated ssw and ssb - we need to reduce it
#     offsetIter = UInt8(1)
#     while(offsetIter <32)
#         @inbounds ssw+=shfl_down_sync(FULL_MASK, ssw, offsetIter)
#         @inbounds ssb+=shfl_down_sync(FULL_MASK, ssb, offsetIter)
#         offsetIter<<= 1
#     end
#         # now we have sums in first threads of the warp we need to pass it to shared memory
#         if(threadIdxX()==1)
#             @inbounds shmemSum[threadIdxY(),1]+=ssw
#             @inbounds shmemSum[threadIdxY(),2]+=ssb
#         end
#     sync_threads()
#     #finally reduce from shared memory
#         if(threadIdxY()==1)
#             offsetIter = UInt8(1)
#             while(offsetIter <32)
#             @inbounds shmemSum[threadIdxX(),1]+=shfl_down_sync(FULL_MASK, shmemSum[threadIdxX(),1], offsetIter)
#             @inbounds shmemSum[threadIdxX(),2]+=shfl_down_sync(FULL_MASK, shmemSum[threadIdxX(),2], offsetIter)
#             offsetIter<<= 1
#             end
#     end
#     sync_threads()sswTotal,ssbTotal
      #now in   shmemSum[1,1] we should have ssw and in  shmemSum[1,2] ssb
#       @ifXY 1 1  @inboundsCUDA.@atomic sswTotal[]+= shmemSum[1,1]
#       @ifXY 1 2  @inboundsCUDA.@atomic ssbTotal[]+= shmemSum[1,2]
#       @ifXY 1 3  begin
#         sswInner = shmemSum[1,2]/totalNumbOfVoxels;
#         ssbInner = shmemSum[1,1]/(totalNumbOfVoxels-1) * 2
#         @inbounds iccPerSlice[blockIdxX()]=(ssb - ssw)/(ssb + ssw)
#       end
    # # ####### now we have ssw and ssb calculated both global and per slice

# """
# adapted from https://github.com/JuliaGPU/CUDA.jl/blob/afe81794038dddbda49639c8c26469496543d831/src/mapreduce.jl
# goldGPU - array holding data of gold standard  array
# segmGPU -  array with the data we want to compare with gold standard
# ic - holding single value for global interclass correlation
# intermediateRes- array holding slice wise results for ic
# loopXdim,loopYdim,loopZdim - number of times we need to loop over each dimension in order to get all needed data
# sliceEdgeLength - length of edge of the slice we need to square this number to get number of pixels in a slice
# amountOfWarps - how many warps we can stick in the block
# """

# function kernel_InterClassCorr_means(goldlGPU
#                                ,segmGPU
#                                 ,iterLoop,pixPerSlice,totalNumbOfVoxels
#                                 ,sumOfGold
#                                 ,sumOfSegm
#                                 ,numberToLooFor )

#   locValA = UInt32(0)
#  locValB = UInt32(0)
#   offsetIter= UInt8(1)
#   shmemSum = @cuStaticSharedMem(UInt16, (33,2))   #thread local values that are meant to store some results - like means ...
#   @iterateLinearlyMultipleBlocks(iterLoop,pixPerSlice,totalNumbOfVoxels,
#     #inner expression
#     begin
#         #updating variables needed to calculate means
#            locValA += goldlGPU[i]==numberToLooFor
#            locValB += segmGPU[i]==numberToLooFor
#     end)

#     #tell what variables are to be reduced and by what operation
#     @redWitAct(offsetIter,shmemSum,  locValA,+,     locValB,+  )
#   sync_threads()
#   @addAtomic(shmemSum, sumOfGold, sumOfSegm)

# #     #offset for lloking for values in source arrays
# #     offset = (pixelNumberPerSlice*(blockIdx().x-1))
# #    #for storing results from warp reductions
# #    shmemSum = @cuStaticSharedMem(UInt16, (33,2))   #thread local values that are meant to store some results - like means ...
# #    offsetIter = UInt8(1)

# #    locValA = UInt32(0)
# #    locValB = UInt32(0)
# #         #reset shared memory
# #         @ifY 1 shmemSum[threadIdxX(),1]=0 ;   @ifY 2 shmemSum[threadIdxX(),2]=0
# #         sync_threads()

# #         #first we add 1 for each spot we have true - so we will get sum  - and from sum we can get mean
# #         @unroll for k in 0:loopNumb
# #             if(threadIdxX()+(threadIdxY()-1)*32+k*1024 <=pixelNumberPerSlice)
# #             ind =offset+ threadIdxX()+(threadIdxY()-1)*32+k*1024
# #             locValA += flatGold[ind]==numberToLooFor
# #             locValB += flatSegm[ind]==numberToLooFor
# #             end#if
# #         end#for

# #             #now we will have sum of all entries in given slice that comply to our predicate
# #             #next we need to reduce values
# #             offsetIter = UInt8(1)
# #             while(offsetIter <32)
# #                 @inbounds locValA+=shfl_down_sync(FULL_MASK, locValA, offsetIter)
# #                 @inbounds locValB+=shfl_down_sync(FULL_MASK, locValB, offsetIter)
# #                 offsetIter<<= 1
# #             end
# #         # now we have sums in first threads of the warp we need to pass it to shared memory
# #         if(threadIdxX()==1)

# #             @inbounds shmemSum[threadIdxY(),1]+=locValA
# #             @inbounds shmemSum[threadIdxY(),2]+=locValB
# #         end
# #         sync_threads()
# #         #finally reduce from shared memory
# #         if(threadIdxY()==1)
# #             offsetIter = UInt8(1)
# #             while(offsetIter <32)
# #                 @inbounds shmemSum[threadIdxX(),1]+=shfl_down_sync(FULL_MASK, shmemSum[threadIdxX(),1], offsetIter)
# #                 @inbounds shmemSum[threadIdxX(),2]+=shfl_down_sync(FULL_MASK, shmemSum[threadIdxX(),2], offsetIter)
# #             offsetIter<<= 1
# #             end
# #         end
# # sync_threads()

# #       #now in   shmemSum[1,1] we should have sum of values complying with our predicate in gold mask and in shmemSum[1,2] values of other mask
# #       #we need to add now those to the globals
# #       @ifXY 1 1  @inboundsCUDA.@atomic sumOfGold[]+= shmemSum[1,1]
# #       @ifXY 1 2  @inboundsCUDA.@atomic sumOfSegm[]+= shmemSum[1,2]

#     return nothing
# end