# Julia wrapper for header: cone.h
# Automatically generated using Clang.jl

function bring2cone(C, r, s)
    return ccall(
        (:bring2cone, ecos),
        Cvoid,
        (Ptr{cone}, Ptr{pfloat}, Ptr{pfloat}),
        C,
        r,
        s,
    )
end

function unitInitialization(C, s, z, scaling)
    return ccall(
        (:unitInitialization, ecos),
        Cvoid,
        (Ptr{cone}, Ptr{pfloat}, Ptr{pfloat}, pfloat),
        C,
        s,
        z,
        scaling,
    )
end

function updateScalings(C, s, z, lambda, mu)
    return ccall(
        (:updateScalings, ecos),
        idxint,
        (Ptr{cone}, Ptr{pfloat}, Ptr{pfloat}, Ptr{pfloat}, pfloat),
        C,
        s,
        z,
        lambda,
        mu,
    )
end

function evalSymmetricBarrierValue(siter, ziter, tauIter, kapIter, C, D)
    return ccall(
        (:evalSymmetricBarrierValue, ecos),
        pfloat,
        (Ptr{pfloat}, Ptr{pfloat}, pfloat, pfloat, Ptr{cone}, pfloat),
        siter,
        ziter,
        tauIter,
        kapIter,
        C,
        D,
    )
end

function scale(z, C, lambda)
    return ccall(
        (:scale, ecos),
        Cvoid,
        (Ptr{pfloat}, Ptr{cone}, Ptr{pfloat}),
        z,
        C,
        lambda,
    )
end

function scale2add(x, y, C)
    return ccall(
        (:scale2add, ecos),
        Cvoid,
        (Ptr{pfloat}, Ptr{pfloat}, Ptr{cone}),
        x,
        y,
        C,
    )
end

function unscale(lambda, C, z)
    return ccall(
        (:unscale, ecos),
        Cvoid,
        (Ptr{pfloat}, Ptr{cone}, Ptr{pfloat}),
        lambda,
        C,
        z,
    )
end

function conicProduct(u, v, C, w)
    return ccall(
        (:conicProduct, ecos),
        pfloat,
        (Ptr{pfloat}, Ptr{pfloat}, Ptr{cone}, Ptr{pfloat}),
        u,
        v,
        C,
        w,
    )
end

function conicDivision(u, v, C, w)
    return ccall(
        (:conicDivision, ecos),
        Cvoid,
        (Ptr{pfloat}, Ptr{pfloat}, Ptr{cone}, Ptr{pfloat}),
        u,
        v,
        C,
        w,
    )
end

function getSOCDetails(soc, conesize, eta_square, d1, u0, u1, v1, q)
    return ccall(
        (:getSOCDetails, ecos),
        Cvoid,
        (
            Ptr{socone},
            Ptr{idxint},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{Ptr{pfloat}},
        ),
        soc,
        conesize,
        eta_square,
        d1,
        u0,
        u1,
        v1,
        q,
    )
end

function unstretch(n, p, C, Pinv, Px, dx, dy, dz)
    return ccall(
        (:unstretch, ecos),
        Cvoid,
        (
            idxint,
            idxint,
            Ptr{cone},
            Ptr{idxint},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
        ),
        n,
        p,
        C,
        Pinv,
        Px,
        dx,
        dy,
        dz,
    )
end
# Julia wrapper for header: ctrlc.h
# Automatically generated using Clang.jl

# Julia wrapper for header: data.h
# Automatically generated using Clang.jl

# Julia wrapper for header: ecos.h
# Automatically generated using Clang.jl

function ECOS_setup(
    n,
    m,
    p,
    l,
    ncones,
    q,
    nex,
    Gpr,
    Gjc,
    Gir,
    Apr,
    Ajc,
    Air,
    c,
    h,
    b,
)
    return ccall(
        (:ECOS_setup, ecos),
        Ptr{pwork},
        (
            idxint,
            idxint,
            idxint,
            idxint,
            idxint,
            Ptr{idxint},
            idxint,
            Ptr{pfloat},
            Ptr{idxint},
            Ptr{idxint},
            Ptr{pfloat},
            Ptr{idxint},
            Ptr{idxint},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
        ),
        n,
        m,
        p,
        l,
        ncones,
        q,
        nex,
        Gpr,
        Gjc,
        Gir,
        Apr,
        Ajc,
        Air,
        c,
        h,
        b,
    )
end

function expConeLineSearch(w, dtau, dkappa, affine)
    return ccall(
        (:expConeLineSearch, ecos),
        pfloat,
        (Ptr{pwork}, pfloat, pfloat, idxint),
        w,
        dtau,
        dkappa,
        affine,
    )
end

function ECOS_solve(w)
    return ccall((:ECOS_solve, ecos), idxint, (Ptr{pwork},), w)
end

function ECOS_cleanup(w, keepvars)
    return ccall(
        (:ECOS_cleanup, ecos),
        Cvoid,
        (Ptr{pwork}, idxint),
        w,
        keepvars,
    )
end

function ECOS_ver()
    return ccall((:ECOS_ver, ecos), Cstring, ())
end

function ecos_updateDataEntry_h(w, idx, value)
    return ccall(
        (:ecos_updateDataEntry_h, ecos),
        Cvoid,
        (Ptr{pwork}, idxint, pfloat),
        w,
        idx,
        value,
    )
end

function ecos_updateDataEntry_c(w, idx, value)
    return ccall(
        (:ecos_updateDataEntry_c, ecos),
        Cvoid,
        (Ptr{pwork}, idxint, pfloat),
        w,
        idx,
        value,
    )
end

function ECOS_updateData(w, Gpr, Apr, c, h, b)
    return ccall(
        (:ECOS_updateData, ecos),
        Cvoid,
        (
            Ptr{pwork},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
        ),
        w,
        Gpr,
        Apr,
        c,
        h,
        b,
    )
end
# Julia wrapper for header: ecos_bb.h
# Automatically generated using Clang.jl

function ECOS_BB_setup(
    n,
    m,
    p,
    l,
    ncones,
    q,
    nex,
    Gpr,
    Gjc,
    Gir,
    Apr,
    Ajc,
    Air,
    c,
    h,
    b,
    num_bool_vars,
    bool_vars_idx,
    num_int_vars,
    int_vars_idx,
    stgs,
)
    return ccall(
        (:ECOS_BB_setup, ecos),
        Ptr{ecos_bb_pwork},
        (
            idxint,
            idxint,
            idxint,
            idxint,
            idxint,
            Ptr{idxint},
            idxint,
            Ptr{pfloat},
            Ptr{idxint},
            Ptr{idxint},
            Ptr{pfloat},
            Ptr{idxint},
            Ptr{idxint},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            idxint,
            Ptr{idxint},
            idxint,
            Ptr{idxint},
            Ptr{settings_bb},
        ),
        n,
        m,
        p,
        l,
        ncones,
        q,
        nex,
        Gpr,
        Gjc,
        Gir,
        Apr,
        Ajc,
        Air,
        c,
        h,
        b,
        num_bool_vars,
        bool_vars_idx,
        num_int_vars,
        int_vars_idx,
        stgs,
    )
end

function ECOS_BB_solve(prob)
    return ccall((:ECOS_BB_solve, ecos), idxint, (Ptr{ecos_bb_pwork},), prob)
end

function ECOS_BB_cleanup(prob, num_vars_keep)
    return ccall(
        (:ECOS_BB_cleanup, ecos),
        Cvoid,
        (Ptr{ecos_bb_pwork}, idxint),
        prob,
        num_vars_keep,
    )
end

function updateDataEntry_h(w, idx, value)
    return ccall(
        (:updateDataEntry_h, ecos),
        Cvoid,
        (Ptr{ecos_bb_pwork}, idxint, pfloat),
        w,
        idx,
        value,
    )
end

function updateDataEntry_c(w, idx, value)
    return ccall(
        (:updateDataEntry_c, ecos),
        Cvoid,
        (Ptr{ecos_bb_pwork}, idxint, pfloat),
        w,
        idx,
        value,
    )
end

function get_default_ECOS_BB_settings()
    return ccall((:get_default_ECOS_BB_settings, ecos), Ptr{settings_bb}, ())
end

function get_bool_node_id(idx, prob)
    return ccall(
        (:get_bool_node_id, ecos),
        Cstring,
        (idxint, Ptr{ecos_bb_pwork}),
        idx,
        prob,
    )
end

function get_int_node_id(idx, prob)
    return ccall(
        (:get_int_node_id, ecos),
        Ptr{pfloat},
        (idxint, Ptr{ecos_bb_pwork}),
        idx,
        prob,
    )
end

function abs_2(number)
    return ccall((:abs_2, ecos), pfloat, (pfloat,), number)
end

function pfloat_round(number)
    return ccall((:pfloat_round, ecos), pfloat, (pfloat,), number)
end

function pfloat_ceil(number, integer_tol)
    return ccall(
        (:pfloat_ceil, ecos),
        pfloat,
        (pfloat, pfloat),
        number,
        integer_tol,
    )
end

function pfloat_floor(number, integer_tol)
    return ccall(
        (:pfloat_floor, ecos),
        pfloat,
        (pfloat, pfloat),
        number,
        integer_tol,
    )
end

function float_eqls(a, b, integer_tol)
    return ccall(
        (:float_eqls, ecos),
        idxint,
        (pfloat, pfloat, pfloat),
        a,
        b,
        integer_tol,
    )
end
# Julia wrapper for header: equil.h
# Automatically generated using Clang.jl

function set_equilibration(w)
    return ccall((:set_equilibration, ecos), Cvoid, (Ptr{pwork},), w)
end

function unset_equilibration(w)
    return ccall((:unset_equilibration, ecos), Cvoid, (Ptr{pwork},), w)
end
# Julia wrapper for header: expcone.h
# Automatically generated using Clang.jl

function evalExpHessian(w, v, mu)
    return ccall(
        (:evalExpHessian, ecos),
        Cvoid,
        (Ptr{pfloat}, Ptr{pfloat}, pfloat),
        w,
        v,
        mu,
    )
end

function evalExpGradient(w, g)
    return ccall(
        (:evalExpGradient, ecos),
        Cvoid,
        (Ptr{pfloat}, Ptr{pfloat}),
        w,
        g,
    )
end

function evalBarrierValue(siter, ziter, fc, nexc)
    return ccall(
        (:evalBarrierValue, ecos),
        pfloat,
        (Ptr{pfloat}, Ptr{pfloat}, idxint, idxint),
        siter,
        ziter,
        fc,
        nexc,
    )
end

function scaleToAddExpcone(y, x, expcones, nexc, fc)
    return ccall(
        (:scaleToAddExpcone, ecos),
        Cvoid,
        (Ptr{pfloat}, Ptr{pfloat}, Ptr{expcone}, idxint, idxint),
        y,
        x,
        expcones,
        nexc,
        fc,
    )
end

function evalExpPrimalFeas(s, nexc)
    return ccall(
        (:evalExpPrimalFeas, ecos),
        idxint,
        (Ptr{pfloat}, idxint),
        s,
        nexc,
    )
end

function evalExpDualFeas(s, nexc)
    return ccall(
        (:evalExpDualFeas, ecos),
        idxint,
        (Ptr{pfloat}, idxint),
        s,
        nexc,
    )
end
# Julia wrapper for header: glblopts.h
# Automatically generated using Clang.jl

# Julia wrapper for header: kkt.h
# Automatically generated using Clang.jl

function kkt_factor(KKT, eps, delta)
    return ccall(
        (:kkt_factor, ecos),
        idxint,
        (Ptr{kkt}, pfloat, pfloat),
        KKT,
        eps,
        delta,
    )
end

function kkt_solve(KKT, A, G, Pb, dx, dy, dz, n, p, m, C, isinit, nitref)
    return ccall(
        (:kkt_solve, ecos),
        idxint,
        (
            Ptr{kkt},
            Ptr{spmat},
            Ptr{spmat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            Ptr{pfloat},
            idxint,
            idxint,
            idxint,
            Ptr{cone},
            idxint,
            idxint,
        ),
        KKT,
        A,
        G,
        Pb,
        dx,
        dy,
        dz,
        n,
        p,
        m,
        C,
        isinit,
        nitref,
    )
end

function kkt_update(PKP, P, C)
    return ccall(
        (:kkt_update, ecos),
        Cvoid,
        (Ptr{spmat}, Ptr{idxint}, Ptr{cone}),
        PKP,
        P,
        C,
    )
end

function kkt_init(PKP, P, C)
    return ccall(
        (:kkt_init, ecos),
        Cvoid,
        (Ptr{spmat}, Ptr{idxint}, Ptr{cone}),
        PKP,
        P,
        C,
    )
end
# Julia wrapper for header: spla.h
# Automatically generated using Clang.jl

function sparseMV(A, x, y, a, newVector)
    return ccall(
        (:sparseMV, ecos),
        Cvoid,
        (Ptr{spmat}, Ptr{pfloat}, Ptr{pfloat}, idxint, idxint),
        A,
        x,
        y,
        a,
        newVector,
    )
end

function sparseMtVm(A, x, y, newVector, skipDiagonal)
    return ccall(
        (:sparseMtVm, ecos),
        Cvoid,
        (Ptr{spmat}, Ptr{pfloat}, Ptr{pfloat}, idxint, idxint),
        A,
        x,
        y,
        newVector,
        skipDiagonal,
    )
end

function vadd(n, x, y)
    return ccall(
        (:vadd, ecos),
        Cvoid,
        (idxint, Ptr{pfloat}, Ptr{pfloat}),
        n,
        x,
        y,
    )
end

function vsubscale(n, a, x, y)
    return ccall(
        (:vsubscale, ecos),
        Cvoid,
        (idxint, pfloat, Ptr{pfloat}, Ptr{pfloat}),
        n,
        a,
        x,
        y,
    )
end

function norm2(v, n)
    return ccall((:norm2, ecos), pfloat, (Ptr{pfloat}, idxint), v, n)
end

function norminf(v, n)
    return ccall((:norminf, ecos), pfloat, (Ptr{pfloat}, idxint), v, n)
end

function eddot(n, x, y)
    return ccall(
        (:eddot, ecos),
        pfloat,
        (idxint, Ptr{pfloat}, Ptr{pfloat}),
        n,
        x,
        y,
    )
end
# Julia wrapper for header: splamm.h
# Automatically generated using Clang.jl

function ecoscreateSparseMatrix(m, n, nnz, jc, ir, pr)
    return ccall(
        (:ecoscreateSparseMatrix, ecos),
        Ptr{spmat},
        (idxint, idxint, idxint, Ptr{idxint}, Ptr{idxint}, Ptr{pfloat}),
        m,
        n,
        nnz,
        jc,
        ir,
        pr,
    )
end

function newSparseMatrix(m, n, nnz)
    return ccall(
        (:newSparseMatrix, ecos),
        Ptr{spmat},
        (idxint, idxint, idxint),
        m,
        n,
        nnz,
    )
end

function freeSparseMatrix(M)
    return ccall((:freeSparseMatrix, ecos), Cvoid, (Ptr{spmat},), M)
end

function transposeSparseMatrix(M, MtoMt)
    return ccall(
        (:transposeSparseMatrix, ecos),
        Ptr{spmat},
        (Ptr{spmat}, Ptr{idxint}),
        M,
        MtoMt,
    )
end

function permuteSparseSymmetricMatrix(A, pinv, C, PK)
    return ccall(
        (:permuteSparseSymmetricMatrix, ecos),
        Cvoid,
        (Ptr{spmat}, Ptr{idxint}, Ptr{spmat}, Ptr{idxint}),
        A,
        pinv,
        C,
        PK,
    )
end

function pinv(n, p, pinv)
    return ccall(
        (:pinv, ecos),
        Cvoid,
        (idxint, Ptr{idxint}, Ptr{idxint}),
        n,
        p,
        pinv,
    )
end

function copySparseMatrix(A)
    return ccall((:copySparseMatrix, ecos), Ptr{spmat}, (Ptr{spmat},), A)
end

function printDenseMatrix(M, dim1, dim2, name)
    return ccall(
        (:printDenseMatrix, ecos),
        Cvoid,
        (Ptr{pfloat}, idxint, idxint, Cstring),
        M,
        dim1,
        dim2,
        name,
    )
end

function printDenseMatrix_i(M, dim1, dim2, name)
    return ccall(
        (:printDenseMatrix_i, ecos),
        Cvoid,
        (Ptr{idxint}, idxint, idxint, Cstring),
        M,
        dim1,
        dim2,
        name,
    )
end

function printSparseMatrix(M)
    return ccall((:printSparseMatrix, ecos), Cvoid, (Ptr{spmat},), M)
end

function dumpSparseMatrix(M, fn)
    return ccall((:dumpSparseMatrix, ecos), Cvoid, (Ptr{spmat}, Cstring), M, fn)
end

function dumpDenseMatrix(M, dim1, dim2, fn)
    return ccall(
        (:dumpDenseMatrix, ecos),
        Cvoid,
        (Ptr{pfloat}, Cint, Cint, Cstring),
        M,
        dim1,
        dim2,
        fn,
    )
end

function dumpDenseMatrix_i(M, dim1, dim2, fn)
    return ccall(
        (:dumpDenseMatrix_i, ecos),
        Cvoid,
        (Ptr{idxint}, Cint, Cint, Cstring),
        M,
        dim1,
        dim2,
        fn,
    )
end
# Julia wrapper for header: wright_omega.h
# Automatically generated using Clang.jl

function wrightOmega(z)
    return ccall((:wrightOmega, ecos), pfloat, (pfloat,), z)
end


# This file is WIP while the interface for templates is finalized
@testset "Manual Operations Template" begin
    template = OperationsProblemTemplate(CopperPlatePowerModel)
    set_device_model!(template, PowerLoad, StaticPowerLoad)
    set_device_model!(template, ThermalStandard, ThermalStandardUnitCommitment)
    set_device_model!(template, Line, StaticBranchUnbounded)
    @test !isempty(template.devices)
    @test !isempty(template.branches)
    @test isempty(template.services)
end

@testset "Operations Template Overwrite" begin
    template = OperationsProblemTemplate(CopperPlatePowerModel)
    set_device_model!(template, PowerLoad, StaticPowerLoad)
    set_device_model!(template, ThermalStandard, ThermalStandardUnitCommitment)
    @test_logs (:info, "Overwriting ThermalStandard existing model") set_device_model!(
        template,
        DeviceModel(ThermalStandard, ThermalBasicUnitCommitment),
    )
    @test template.devices[:ThermalStandard].formulation == ThermalBasicUnitCommitment
end

@testset "Provided Templates Tests" begin
    uc_template = template_unit_commitment()
    @test !isempty(uc_template.devices)
    @test uc_template.devices[:ThermalStandard].formulation == ThermalBasicUnitCommitment
    uc_template = template_unit_commitment(network = DCPPowerModel)
    @test get_transmission_model(uc_template) == DCPPowerModel
    @test !isempty(uc_template.branches)
    @test !isempty(uc_template.services)

    ed_template = template_economic_dispatch()
    @test !isempty(ed_template.devices)
    @test ed_template.devices[:ThermalStandard].formulation == ThermalDispatch
    ed_template = template_economic_dispatch(network = ACPPowerModel)
    @test get_transmission_model(ed_template) == ACPPowerModel
    @test !isempty(ed_template.branches)
    @test !isempty(ed_template.services)
end


using GenderDemoInertia
using Test
include("conftest.jl")

@testset "Test column name imports from column_names.jl" begin
    @test :deptn ∈ GenderDemoInertia.INTEGER_COLUMNS
    @test :scen ∈ GenderDemoInertia.DECORATOR_COLUMNS
    @test :runn ∈ GenderDemoInertia.UNSUMMARIZED_COLUMNS
end

@testset "Test import of model_demographic_inertia_gender.jl" begin
    # @test GenderDemoInertia.probability(3,4) = 7
    @test TESTDATA["hiring_rate_women_1"] == 0.2059
    @test TESTDATA["attrition_rate_women_2"] == 0.00
    @test size(GenderDemoInertia.build_simulation_results_array(2, 10)) == (20,94)
    @test GenderDemoInertia.demographic_inertia_simulation(TESTDATA, 100, 100)[1, :r_fattr1] == 0.0615
    #@test GenderDemoInertia.run_gender_inertia_simulation(TESTDATA, 2, 2)
end


module Vmrs
using CUDA
using DocStringExtensions
using Adapt

export AbstractVmr, Vmr, VmrGM, init_vmr, get_vmr

"""
    AbstractVmr{FT}
"""
abstract type AbstractVmr{FT <: AbstractFloat} end
"""
    VmrGM{FT,FTA1D,FTA2D} <: AbstractVmr{FT}

Volume mixing ratios for various gases in the atmosphere. This struct can be used
when only H₂O and O₃ concentrations vary spatially and a global mean is used
for all other gases.

# Fields
$(DocStringExtensions.FIELDS)
"""
struct VmrGM{FT <: AbstractFloat, FTA1D <: AbstractArray{FT, 1}, FTA2D <: AbstractArray{FT, 2}} <: AbstractVmr{FT}
    "volume mixing ratio of H₂O"
    vmr_h2o::FTA2D
    "volume mixing ratio of Ozone"
    vmr_o3::FTA2D
    "volume mixing ratio of all other gases, which are independent of location and column"
    vmr::FTA1D
end
VmrGM(vmr_h2o, vmr_o3, vmr) = VmrGM{eltype(vmr_h2o), typeof(vmr), typeof(vmr_h2o)}(vmr_h2o, vmr_o3, vmr)
Adapt.@adapt_structure VmrGM

function VmrGM(
    ::Type{FT},
    ::Type{DA},
    vmr_h2o::FTA2D,
    vmr_o3::FTA2D,
    vmr::FTA1D,
) where {FT <: AbstractFloat, FTA1D <: AbstractArray{FT, 1}, FTA2D <: AbstractArray{FT, 2}, DA}
    return VmrGM{FT, typeof(vmr), typeof(vmr_h2o)}(vmr_h2o, vmr_o3, vmr)
end

"""
    Vmr{FT,FTA3D} <: AbstractVmr{FT}

Volume mixing ratios for various gases in the atmosphere. This struct can be used
when concentrations vary spatially for all gases.

# Fields
$(DocStringExtensions.FIELDS)
"""
struct Vmr{FT <: AbstractFloat, FTA3D <: AbstractArray{FT, 3}} <: AbstractVmr{FT}
    "volume mixing ratio of all gases as a function of location and column"
    vmr::FTA3D
end
Vmr(vmr) = Vmr{eltype(vmr), typeof(vmr)}(vmr)
Adapt.@adapt_structure Vmr
"""
    get_vmr(
        vmr::VmrGM{FT},
        ig::Int,
        ilay::Int,
        icol::Int,
    ) where {FT<:AbstractFloat}

Obtain volume mixing ratio of gas `ig` for layer `ilay` of column `icol`.
"""
@inline function get_vmr(vmr::VmrGM{FT}, ig::Int, ilay::Int, icol::Int) where {FT <: AbstractFloat}
    if ig == 0
        return FT(1)
    elseif ig == 1 # h2o / h2o_foreign / h2o_self-continua
        return @inbounds vmr.vmr_h2o[ilay, icol]
    elseif ig == 3 # ozone
        return @inbounds vmr.vmr_o3[ilay, icol]
    else # other gases
        return @inbounds vmr.vmr[ig]
    end
end

"""
    get_vmr(
        vmr::Vmr{FT},
        ig::Int,
        ilay::Int,
        icol::Int,
    ) where {FT<:AbstractFloat}

Obtain volume mixing ratio of gas `ig` for layer `ilay` of column `icol`.
"""
@inline function get_vmr(vmr::Vmr{FT}, ig::Int, ilay::Int, icol::Int) where {FT <: AbstractFloat}
    if ig == 0
        return FT(1)
    else
        return @inbounds vmr.vmr[ilay, icol, ig]
    end
end

"""
    init_vmr(
        ngas,
        nlay,
        ncol,
        ::Type{FT},
        ::Type{DA};
        gm::Bool = true,
    ) where {FT<:AbstractFloat,DA}

Initialize the Vmr struct.
"""
function init_vmr(
    ngas::Int,
    nlay::Int,
    ncol::Int,
    ::Type{FT},
    ::Type{DA};
    gm::Bool = true,
) where {FT <: AbstractFloat, DA}
    if gm
        FTA1D = DA{FT, 1}
        FTA2D = DA{FT, 2}
        return VmrGM{FT, FTA1D, FTA2D}(FTA2D(zeros(nlay, ncol)), FTA2D(zeros(nlay, ncol)), FTA1D(zeros(ngas)))
    else
        FTA3D = DA{FT, 3}
        return Vmr{FT, FTA3D}(FTA3D(zeros(nlay, ncol, ngas)))
    end
end

end


export apply!

using Flux
using Flux: throttle, logitcrossentropy, onecold, onehotbatch
using StatsBase: mean
using Parameters
using CUDA
using Distributions
using BSON
using Dates
using Zygote
using ArgParse

function parse_commandline()
    gpumem = CUDA.totalmem(collect(CUDA.devices())[1])/(1024^3)
    if gpumem < 2.0
        batchsize_ = 4
        num_cells_ = 4
        channels_ = 4
        trainval_fraction_ = 2f-3
    elseif gpumem < 12.0
        batchsize_ = 64
        num_cells_ = 8
        channels_ = 16
        trainval_fraction_ = 1f0
    elseif gpumem < 16.0
        batchsize_ = 64
        num_cells_ = 8
        channels_ = 16
        trainval_fraction_ = 1f0
    else
        batchsize_ = 128
        num_cells_ = 8
        channels_ = 16
        trainval_fraction_ = 1f0
    end
    global batchsize_
    global num_cells_
    global channels_
    global trainval_fraction_

    s = ArgParseSettings()

    @add_arg_table! s begin
        "--epochs"
            help = "number of epochs"
            arg_type = Int
            default = 50
        "--random_seed"
            help = "random seed (-1 for no random seed)"
            arg_type = Int
            default = 32
        "--checkpoint"
            help = "how often to save checkpoints (-1 for no checkpointing)"
            arg_type = Int
            default = -1
        "--batchsize"
            help = "batchsize"
            arg_type = Int
            default = batchsize_
        "--val_split"
            help = "fraction of train/val set to use as val set"
            arg_type = Float32
            default = 5f-1
        "--trainval_fraction"
            help = "total fraction of train/val set to use"
            arg_type = Float32
            default = trainval_fraction_
        "--test_fraction"
            help = "fraction of test set to use"
            arg_type = Float32
            default = 1f0
        "--num_cells"
            help = "number of cells in supernet"
            arg_type = Int
            default = num_cells_
        "--channels"
            help = "number of initial in supernet"
            arg_type = Int
            default = channels_
        "--rho"
            help = "admm parameter"
            arg_type = Float32
            default = 1f-3
    end

    return parse_args(s)
end

function parse_commandline_eval()
    gpumem = CUDA.totalmem(collect(CUDA.devices())[1])/(1024^3)
    if gpumem < 2.0
        batchsize_ = 1
        num_cells_ = 2
        channels_ = 18
        trainval_fraction_ = 4f-4
    elseif gpumem < 12.0
        batchsize_ = 96
        num_cells_ = 20
        channels_ = 36
        trainval_fraction_ = 1f0
    elseif gpumem < 16.0
        batchsize_ = 96
        num_cells_ = 20
        channels_ = 36
        trainval_fraction_ = 1f0
    else
        batchsize_ = 128
        num_cells_ = 20
        channels_ = 36
        trainval_fraction_ = 1f0
    end
    global batchsize_
    global num_cells_
    global channels_
    global trainval_fraction_

    s = ArgParseSettings()

    @add_arg_table! s begin
        "--epochs"
            help = "number of epochs"
            arg_type = Int
            default = 600
        "--random_seed"
            help = "random seed (-1 for no random seed)"
            arg_type = Int
            default = 32
        "--checkpoint"
            help = "how often to save checkpoints (-1 for no checkpointing)"
            arg_type = Int
            default = -1
        "--batchsize"
            help = "batchsize"
            arg_type = Int
            default = batchsize_
        "--test_batchsize"
            help = "testset batchsize"
            arg_type = Int
            default = 248
        "--val_split"
            help = "fraction of train/val set to use as val set"
            arg_type = Float32
            default = 0f0
        "--trainval_fraction"
            help = "total fraction of train/val set to use"
            arg_type = Float32
            default = trainval_fraction_
        "--test_fraction"
            help = "fraction of test set to use"
            arg_type = Float32
            default = 1f0
        "--num_cells"
            help = "number of cells in supernet"
            arg_type = Int
            default = num_cells_
        "--channels"
            help = "number of initial in supernet"
            arg_type = Int
            default = channels_
        "--droppath"
            help = "droppath probability"
            arg_type = Float32
            default = 2f-1
        "--aux"
            help = "weight of auxiliary loss"
            arg_type = Float32
            default = 4f-1
    end

    return parse_args(s)
end

@with_kw struct trial_params
    epochs::Int = 50
    batchsize::Int = batchsize_
    throttle_::Int = 20
    val_split::Float32 = 0.5
    trainval_fraction::Float32 = trainval_fraction_
    test_fraction::Float32 = 1.0
    num_cells::Int = num_cells_
    channels::Int = channels_
end

function loss(m, x, y)
    out = m(x)
    loss = logitcrossentropy(squeeze(out), y)
    return loss
end

acccb() = @show(accuracy_batched(m, val))
function accuracy(m, x, y)
    out = mean(onecold(m(x), 1:10) .== onecold(y, 1:10))
end
function accuracy(m, x, y, pert)
    out = mean(onecold(m(x, pert), 1:10) .== onecold(y, 1:10))
end
function accuracy_batched(m, xy)
    CUDA.reclaim()
    GC.gc()
    score = 0f0
    count = 0
    for batch in CuIterator(xy)
        @show acc = accuracy(m, batch...)
        score += acc*length(batch)
        count += length(batch)
        CUDA.reclaim()
        GC.gc()
    end
    display(score / count)
    score / count
end
function accuracy_batched(m, xy, pert)
    CUDA.reclaim()
    GC.gc()
    score = 0f0
    count = 0
    for batch in CuIterator(xy)
        @show acc = accuracy(m, batch..., pert)
        score += acc*length(batch)
        count += length(batch)
        CUDA.reclaim()
        GC.gc()
    end
    display(score / count)
    score / count
end
function accuracy_unbatched(m, xy; pert = [])
    CUDA.reclaim()
    GC.gc()
    xy = xy | gpu
    acc = accuracy(m, xy..., pert = pert)
    foreach(CUDA.unsafe_free!, xy)
    CUDA.reclaim()
    GC.gc()
    acc
end
Base.@kwdef mutable struct histories
    normal_αs::Vector{Vector{Array{Float32, 1}}}
    reduce_αs::Vector{Vector{Array{Float32, 1}}}
    activations::Vector{Dict}
    accuracies::Vector{Float32}
end

histories() = histories([],[],[],[])

function (hist::histories)()
    push!(hist.normal_αs, deepcopy(m.normal_αs) |> cpu)
    push!(hist.reduce_αs, deepcopy(m.reduce_αs) |> cpu)
    #push!(hist.activations, m.activations |> cpu)
    #push!(hist.accuracies, accuracy_batched(m, val |> gpu))
end

Base.@kwdef mutable struct historiessm
    normal_αs_sm::Vector{Vector{Array{Float32, 1}}}
    reduce_αs_sm::Vector{Vector{Array{Float32, 1}}}
    activations::Vector{Dict}
    accuracies::Vector{Float32}
end

historiessm() = historiessm([],[],[],[])

function (hist::historiessm)()
    #@show losses
    push!(hist.normal_αs_sm, softmax.(deepcopy(m.normal_αs)) |> cpu)
    push!(hist.reduce_αs_sm, softmax.(deepcopy(m.reduce_αs)) |> cpu)
    #push!(hist.activations, copy(m.activations.currentacts) |> cpu)
    CUDA.reclaim()
    GC.gc()
end

Base.@kwdef mutable struct historiessml
    normal_αs_sm::Vector{Vector{Array{Float32, 1}}}
    reduce_αs_sm::Vector{Vector{Array{Float32, 1}}}
    activations::Vector{Dict}
    accuracies::Vector{Float32}
    train_losses::Vector{Float32}
    val_losses::Vector{Float32}
end

historiessml() = historiessml([],[],[],[],[],[])

function (hist::historiessml)()
    @show losses
    push!(hist.normal_αs_sm, softmax.(deepcopy(m.normal_αs)) |> cpu)
    push!(hist.reduce_αs_sm, softmax.(deepcopy(m.reduce_αs)) |> cpu)
    #push!(hist.activations, copy(m.activations.currentacts) |> cpu)
    push!(hist.train_losses, losses[1])
    push!(hist.val_losses, losses[2])
    CUDA.reclaim()
    GC.gc()
end

struct CbAll
    cbs
end
CbAll(cbs...) = CbAll(cbs)

function timefunc(func)
    @show func
    @time out = func()
    out
end

(cba::CbAll)() = foreach(cb -> cb(), cba.cbs)

function prepare_folder(algo::String, args::Dict)
    if "SLURM_JOB_ID" in keys(ENV)
        uniqueid = ENV["SLURM_JOB_ID"]
    else
        uniqueid = Dates.now()
    end
    if ispath("/gpfs/work/p21001/maile/dnas/models/")
        model_dir = "/gpfs/work/p21001/maile/dnas/models/"
    elseif ispath("/projets/reva/kmaile/dnas/models/")
        model_dir = "/projets/reva/kmaile/dnas/models/"
    else
        model_dir = "test/models/"
    end
    base_folder = string(model_dir, algo, "_", uniqueid)
    mkpath(base_folder)
    BSON.@save joinpath(base_folder, "args.bson") args
    base_folder
end

function save_progress()
    m_cpu = m |> cpu
    normal_αs = m_cpu.normal_αs
    reduce_αs = m_cpu.reduce_αs
    #BSON.@save joinpath(base_folder, "model.bson") m_cpu args
    BSON.@save joinpath(base_folder, "alphas.bson") normal_αs reduce_αs
    BSON.@save joinpath(base_folder, "histepoch.bson") histepoch
    BSON.@save joinpath(base_folder, "histbatch.bson") histbatch
    if args["checkpoint"] > 0 && length(histepoch.train_losses) % args["checkpoint"] == 0
        BSON.@save joinpath(base_folder, string("model", length(histepoch.train_losses), ".bson")) m_cpu args
    end
end


struct TimeSeriesConstraintSpecInternal
    constraint_infos::Vector{DeviceTimeSeriesConstraintInfo}
    constraint_name::Symbol
    variable_name::Symbol
    bin_variable_name::Union{Nothing, Symbol}
    param_reference::Union{Nothing, UpdateRef}
end

function lazy_lb!(psi_container::PSIContainer, inputs::TimeSeriesConstraintSpecInternal)
    time_steps = model_time_steps(psi_container)
    names = [get_component_name(x) for x in inputs.constraint_infos]
    variable = get_variable(psi_container, inputs.variable_name)
    lb_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "lb")
    con_lb = add_cons_container!(psi_container, lb_name, names, time_steps)

    for constraint_info in inputs.constraint_infos
        ci_name = get_component_name(constraint_info)
        if constraint_info.range.limits.min > -Inf &&
           !isempty(constraint_info.range.additional_terms_lb)
            con_lb[ci_name, :].data .= JuMP.AffExpr(0.0)
            continue
        end
        for t in time_steps
            expression_lb = JuMP.AffExpr(0.0, variable[ci_name, t] => 1.0)
            for val in constraint_info.range.additional_terms_lb
                JuMP.add_to_expression!(
                    expression_lb,
                    get_variable(psi_container, val)[ci_name, t],
                    -1.0,
                )
            end
            lb_val = max(0.0, constraint_info.range.limits.min)
            con_lb[ci_name, t] =
                JuMP.@constraint(psi_container.JuMPmodel, expression_lb >= lb_val)
        end
    end
    return
end

@doc raw"""
Constructs upper bound for given variable and time series data and a multiplier.

# Constraint

```variable[name, t] <= constraint_infos[name].multiplier * ts_data[name].timeseries[t] ```

# LaTeX

`` x_t \leq r^{val} r_t, \forall t ``
"""
function device_timeseries_ub!(
    psi_container::PSIContainer,
    inputs::TimeSeriesConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    names = [get_component_name(x) for x in inputs.constraint_infos]
    variable = get_variable(psi_container, inputs.variable_name)
    ub_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    con_ub = add_cons_container!(psi_container, ub_name, names, time_steps)
    lazy_add_lb = false

    for constraint_info in inputs.constraint_infos
        ci_name = get_component_name(constraint_info)
        for t in time_steps
            expression_ub = JuMP.AffExpr(0.0, variable[ci_name, t] => 1.0)
            for val in constraint_info.range.additional_terms_ub
                JuMP.add_to_expression!(
                    expression_ub,
                    get_variable(psi_container, val)[ci_name, t],
                )
            end
            con_ub[ci_name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_ub <= constraint_info.multiplier * constraint_info.timeseries[t]
            )
        end
        if constraint_info.range.limits.min > -Inf ||
           !isempty(constraint_info.range.additional_terms_lb)
            lazy_add_lb = true
        end
    end

    @debug lazy_add_lb
    lazy_add_lb && lazy_lb!(psi_container, inputs)

    return
end

@doc raw"""
Constructs lower bound for given variable subject to time series data and a multiplier.

# Constraint

``` constraint_infos[name].multiplier * ts_data[name].timeseries[t] <= variable[name, t] ```

# LaTeX

`` r^{val} r_t \leq x_t, \forall t ``

where (name, data) in range_data.
"""
function device_timeseries_lb!(
    psi_container::PSIContainer,
    inputs::TimeSeriesConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    variable = get_variable(psi_container, inputs.variable_name)
    lb_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "lb")
    names = [get_component_name(x) for x in inputs.constraint_infos]
    constraint = add_cons_container!(psi_container, lb_name, names, time_steps)

    for constraint_info in inputs.constraint_infos
        ci_name = get_component_name(constraint_info)
        for t in time_steps
            expression_lb = JuMP.AffExpr(0.0, variable[ci_name, t] => 1.0)
            for val in constraint_info.range.additional_terms_lb
                JuMP.add_to_expression!(
                    expression_lb,
                    get_variable(psi_container, val)[ci_name, t],
                    -1.0,
                )
            end
            constraint[ci_name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_lb >= constraint_info.multiplier * constraint_info.timeseries[t]
            )
        end
    end
    return
end

@doc raw"""
Constructs upper bound for given variable using a parameter. The constraint is
    built with a time series data vector and a multiplier

# Constraint

``` variable[name, t] <= constraint_infos[name].multiplier * param[name, t] ```

# LaTeX

`` x^{var}_t \leq r^{val} x^{param}_t, \forall t ``
"""
function device_timeseries_param_ub!(
    psi_container::PSIContainer,
    inputs::TimeSeriesConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    names = [get_component_name(x) for x in inputs.constraint_infos]
    variable = get_variable(psi_container, inputs.variable_name)
    ub_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    con_ub = add_cons_container!(psi_container, ub_name, names, time_steps)
    container =
        add_param_container!(psi_container, inputs.param_reference, names, time_steps)
    multiplier = get_multiplier_array(container)
    param = get_parameter_array(container)
    lazy_add_lb = false

    for constraint_info in inputs.constraint_infos
        ci_name = get_component_name(constraint_info)
        for t in time_steps
            expression_ub = JuMP.AffExpr(0.0, variable[ci_name, t] => 1.0)
            for val in constraint_info.range.additional_terms_ub
                JuMP.add_to_expression!(
                    expression_ub,
                    get_variable(psi_container, val)[ci_name, t],
                )
            end
            param[ci_name, t] =
                PJ.add_parameter(psi_container.JuMPmodel, constraint_info.timeseries[t])
            con_ub[ci_name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_ub <= constraint_info.multiplier * param[ci_name, t]
            )
            multiplier[ci_name, t] = constraint_info.multiplier
        end
        if constraint_info.range.limits.min > -Inf ||
           !isempty(constraint_info.range.additional_terms_lb)
            lazy_add_lb = true
        end
    end

    @debug lazy_add_lb
    lazy_add_lb && lazy_lb!(psi_container, inputs)
    return
end

@doc raw"""
Constructs lower bound for given variable using a parameter. The constraint is
    built with a time series data vector and a multiplier

# Constraint

``` constraint_infos[name].multiplier * param[name, t] <= variable[name, t] ```

# LaTeX

`` r^{val} x^{param}_t \leq x^{var}_t, \forall t ``
"""
function device_timeseries_param_lb!(
    psi_container::PSIContainer,
    inputs::TimeSeriesConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    variable = get_variable(psi_container, inputs.variable_name)
    lb_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "lb")
    names = [get_component_name(x) for x in inputs.constraint_infos]
    constraint = add_cons_container!(psi_container, lb_name, names, time_steps)
    container =
        add_param_container!(psi_container, inputs.param_reference, names, time_steps)
    multiplier = get_multiplier_array(container)
    param = get_parameter_array(container)

    for constraint_info in inputs.constraint_infos
        ci_name = get_component_name(constraint_info)
        for t in time_steps
            expression_lb = JuMP.AffExpr(0.0, variable[ci_name, t] => 1.0)
            for val in constraint_info.range.additional_terms_lb
                JuMP.add_to_expression!(
                    expression_lb,
                    get_variable(psi_container, val)[ci_name, t],
                    -1.0,
                )
            end
            param[ci_name, t] =
                PJ.add_parameter(psi_container.JuMPmodel, constraint_info.timeseries[t])
            constraint_info[ci_name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_lb >= constraint_info.multiplier * param[ci_name, t]
            )
            multiplier[ci_name, t] = constraint_info.multiplier
        end
    end

    return
end

@doc raw"""
Constructs upper bound for variable and time series or confines to 0 depending on binary variable.
    The upper bound is defined by a time series and a multiplier.

# constraint_infos

``` varcts[name, t] <= varbin[name, t]* constraint_infos[name].multiplier * ts_data[name].timeseries[t] ```

where (name, data) in range_data.

# LaTeX

`` x^{cts}_t \leq r^{val} r_t x^{bin}_t, \forall t ``
"""
function device_timeseries_ub_bin!(
    psi_container::PSIContainer,
    inputs::TimeSeriesConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    ub_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    varcts = get_variable(psi_container, inputs.variable_name)
    varbin = get_variable(psi_container, inputs.bin_variable_name)
    names = [get_component_name(x) for x in inputs.constraint_infos]
    con_ub = add_cons_container!(psi_container, ub_name, names, time_steps)
    for constraint_info in inputs.constraint_infos
        ci_name = get_component_name(constraint_info)
        for t in time_steps
            forecast = constraint_info.timeseries[t]
            multiplier = constraint_info.multiplier
            expression_ub = JuMP.AffExpr(0.0, varcts[ci_name, t] => 1.0)
            for val in constraint_info.range.additional_terms_ub
                JuMP.add_to_expression!(
                    expression_ub,
                    get_variable(psi_container, val)[ci_name, t],
                )
            end
            con_ub[ci_name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_ub <= varbin[ci_name, t] * multiplier * forecast
            )
        end
    end
    return
end

@doc raw"""
Constructs upper bound for variable and time series and a multiplier or confines to 0 depending on binary variable.
    Uses BigM constraint type to allow for parameter since ParameterJuMP doesn't support var*parameter

# constraint_infos

``` varcts[name, t] - constraint_infos[name].multipliers * param[name, t] <= (1 - varbin[name, t]) * M_value ```

``` varcts[name, t] <= varbin[name, t]*M_value ```

# LaTeX

`` x^{cts}_t - r^{val} x^{param}_t \leq M(1 - x^{bin}_t ), forall t ``

`` x^{cts}_t \leq M x^{bin}_t, \forall t ``
"""
function device_timeseries_ub_bigM!(
    psi_container::PSIContainer,
    inputs::TimeSeriesConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    ub_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    key_status = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "status")

    varcts = get_variable(psi_container, inputs.variable_name)
    varbin = get_variable(psi_container, inputs.bin_variable_name)
    names = [get_component_name(x) for x in inputs.constraint_infos]
    con_ub = add_cons_container!(psi_container, ub_name, names, time_steps)
    con_status = add_cons_container!(psi_container, key_status, names, time_steps)
    container =
        add_param_container!(psi_container, inputs.param_reference, names, time_steps)
    multiplier = get_multiplier_array(container)
    param = get_parameter_array(container)

    for constraint_info in inputs.constraint_infos
        ci_name = get_component_name(constraint_info)
        for t in time_steps
            expression_ub = JuMP.AffExpr(0.0, varcts[ci_name, t] => 1.0)
            for val in constraint_info.range.additional_terms_ub
                JuMP.add_to_expression!(
                    expression_ub,
                    get_variable(psi_container, val)[ci_name, t],
                )
            end
            param[ci_name, t] =
                PJ.add_parameter(psi_container.JuMPmodel, constraint_info.timeseries[t])
            con_ub[ci_name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_ub - param[ci_name, t] * constraint_info.multiplier <=
                (1 - varbin[ci_name, t]) * M_VALUE
            )
            con_status[ci_name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_ub <= varbin[ci_name, t] * M_VALUE
            )
            multiplier[ci_name, t] = constraint_info.multiplier
        end
    end
    return
end


#! format: off
#############################################################################
# ECOS.jl
# Wrapper around the ECOS solver https://github.com/ifa-ethz/ecos
# See http://github.com/jump-dev/ECOS.jl
#############################################################################
# test/direct.jl
# Test the direct interface for the ECOS.jl solver wrapper
#############################################################################

import ECOS
import SparseArrays
using Test

"""
    test_options()

The values below are copied from data.h in the ECOS source code.
"""
function test_options()
    println(ECOS.ECOS_ver())
    n = 223
    m = 220
    p = 114
    l = 201
    ncones = 6
    c = Float64[0, 0, 0, 0.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    h = Float64[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    q = [3, 3, 3, 3, 3, 4]
    Gjc = [0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220]
    Gir = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219]
    Gpr = Float64[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
    Ajc = [0, 111, 222, 322, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 539, 540, 541, 564, 565, 566, 589, 590, 591, 618, 619, 620, 632, 633, 634, 635, 636, 637, 638]
    Air = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 111, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 112, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 110, 113, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 3, 9, 15, 27, 53, 54, 57, 66, 67, 71, 74, 87, 92, 96, 98, 100, 101, 2, 8, 12, 19, 29, 30, 34, 37, 39, 42, 43, 44, 47, 50, 51, 56, 59, 70, 73, 79, 82, 85, 94, 102, 103, 0, 4, 7, 13, 14, 16, 17, 21, 23, 28, 31, 33, 36, 38, 60, 64, 78, 81, 88, 90, 91, 95, 99, 104, 105, 1, 6, 10, 20, 22, 26, 32, 35, 41, 46, 48, 49, 52, 55, 58, 65, 68, 69, 75, 76, 77, 80, 83, 84, 86, 93, 97, 106, 107, 5, 11, 18, 24, 25, 40, 45, 61, 62, 63, 72, 89, 108, 109, 110, 111, 112, 113]
    Apr = Float64[-6.748099080141577400e-001, 9.587587238674218900e-001, -3.380262965696901900e-001, -6.289006455522118100e-001, 1.458932368908371500e+000, 8.103505672584934100e-001, -8.308676110497204000e-002, 2.638152658681099300e+000, 1.000625209233638700e+000, 1.443285740856571400e-001, 1.198960671699122800e+000, 8.042866751960948700e-001, -2.146877760548437900e-002, 1.252109754253975600e+000, 1.681372772953815700e+000, 1.160558110039982400e+000, 8.971595941547876300e-001, 9.934462168535226300e-001, 4.030425643552826300e-003, 4.520907269135507900e-001, 1.957531681292445900e+000, 8.367513704018149100e-001, -8.919073879004054700e-001, 1.364968014685880000e-001, -5.391443069926015200e-001, 1.218605709809697200e+000, 1.379628849398701500e+000, 7.705722581573115600e-002, -9.721580354507602900e-001, 1.341091680254979900e+000, 1.416856131617781900e+000, 1.367782802004573000e+000, 9.310015370881947700e-001, 1.025960903569425500e+000, 4.418055920652105800e-001, 1.797608571927105100e-001, -1.566236421056189400e+000, -1.293790728779894400e-001, -2.548793507296202000e-001, -1.327158583802827700e+000, 8.378869252146687900e-001, 9.208088134425553900e-001, 8.954835779157467300e-001, 1.872488747783192400e+000, 1.607749082930002300e-001, 1.703097419135647200e+000, 1.485966470102646500e+000, 1.950727391193988900e+000, 1.261282074772785800e+000, 2.116080672704367100e+000, 4.418819171779880300e-001, -6.070414095655145600e-001, 2.236741804227308500e-001, -1.390565074056968600e+000, -3.689063294445240100e-001, 6.275925564016111600e-001, 9.419119351808997700e-001, 2.051844704916749900e-002, -2.055013180419911200e+000, -8.138411920140069500e-001, -1.670986440999406000e+000, -4.312826132928389000e-001, -1.317178055102502500e-001, 4.129877571668971700e-002, -1.178000809237306800e-001, -5.094809536848188300e-002, 4.887140456297478000e-001, -6.048920026011540600e-001, -1.622657687025348600e+000, 9.468924561461561400e-002, -1.076752006167445400e+000, -4.544402225817885700e-001, -1.138681599263542200e+000, 6.297099350763062400e-001, 7.161553122227841700e-001, -2.326213079050594200e-001, -2.421425730439526200e-001, -3.527400894253913700e-001, -5.991000491511071500e-001, -4.501982881436522000e-001, -1.073527618321097300e+000, -1.837831580649836900e+000, -1.093506922417250900e+000, 3.123080272148109900e-002, 4.764624397863112500e-001, -9.744593174286131400e-001, -5.475935551016516400e-001, -1.185577848308040100e+000, -1.512429413719079500e+000, -8.503358079552569800e-001, -1.048119449917124500e+000, -2.950353712891531400e-001, 4.436355670528358600e-001, -1.976143927829304500e+000, -1.154872945269262400e-001, -8.267783537066453200e-001, -2.524533500828647400e-001, -2.934013148063512300e-001, -9.559308197633347200e-001, -5.099545278978906400e-001, 7.104569782287112300e-001, 2.586151051423653000e-001, 9.670875274511733200e-001, -1.580472136196336100e-001, 4.036019169202506600e-001, -4.419329075671284400e-001, 1.335760493316310000e-001, -2.443367984060260100e-001, 5.346171843774407700e-001, 9.240837092450902800e-002, 1, -7.627629774659758300e+000, -8.317906940770274800e+000, -7.063111046330970300e+000, -8.991213209598194900e+000, -7.132056801314573300e+000, -6.933147988332642300e+000, -8.002850393982971200e+000, -7.763929473153173600e+000, -7.316196508551362500e+000, -6.857489440772187800e+000, -6.933435917031895100e+000, -8.453055934420948600e+000, -7.746956878849250200e+000, -6.987480243537588000e+000, -6.898256465718047200e+000, -7.035033952525910900e+000, -7.332300809407010800e+000, -8.868148466875196000e+000, -7.895445920224761800e+000, -8.477688226774738200e+000, -6.724790281248850500e+000, -8.026080227654171400e+000, -8.247796522362861500e+000, -7.893131787368933100e+000, -7.953077643849715300e+000, -7.085517647070380700e+000, -7.757918623860442500e+000, -9.404980440162075800e+000, -9.111021916635516900e+000, -6.753547752596220200e+000, -6.208328120779675900e+000, -7.460712402671073400e+000, -8.051154905293161700e+000, -6.979795864345947000e+000, -7.285373299414776400e+000, -7.935549231771511300e+000, -7.333663096710840700e+000, -7.549354579130950400e+000, -7.125439770244579300e+000, -8.444455837718113100e+000, -7.607705936847165300e+000, -6.947711222611268100e+000, -7.559561213742542300e+000, -6.633232534378238100e+000, -7.285067573640397100e+000, -7.198034090285221800e+000, -8.301173708858458500e+000, -7.740676955875905200e+000, -8.389317110487786200e+000, -7.030503781850090800e+000, 7.483837125022816400e-001, 9.972172897309477000e-001, 8.635272324715717000e-001, 3.437357445286000600e-001, 1.356057545748600200e+000, 7.747994541454063000e-001, -5.029337660688439900e-001, 7.320209587792132500e-001, 9.007328043370961300e-001, 1.839182838430090300e+000, -1.352604803382542600e+000, 1.440339792882617200e+000, 5.409160633286138600e-001, -9.096356667070149000e-002, 2.145906552675194100e-001, 1.749366326171780000e+000, 1.710862586429138200e+000, 5.617170924142114200e-001, 8.075937970441778900e-001, 5.294053117812258500e-001, 8.007328881544743000e-001, 6.824506852180559600e-001, -4.466422007073587200e-001, 4.125165857556476500e-001, 1.264599468598445900e+000, 1.195482116831558800e+000, 5.815701470999368200e-001, 1.480145121963046200e+000, 1.228974130124139900e+000, -9.887375252743877400e-001, 4.957232610676656800e-001, 2.201242074660017000e-001, -8.949779589135298300e-002, 4.463714750361750200e-001, 1.104458211701637000e+000, 1.146057798756675600e+000, 1.607064328082671100e+000, -3.342540671866551800e-001, -3.733869343080511400e-001, 1.376984209826379600e+000, 1.134842638638303900e+000, 1.578816025652644900e+000, 8.631161307174450800e-003, 6.628552272325566300e-001, -4.235551988694613600e-001, -5.931434773175525700e-002, 1.107744286910284700e+000, 1.308043758809049000e+000, 1.474220016986390400e+000, 6.094036504382410700e-001, 2.586151051423653000e-001, 7.690092056142631100e-001, -1.580472136196336100e-001, 2.410499789745967300e-001, -4.419329075671284400e-001, 6.725262868347948700e-001, -2.443367984060260100e-001, 9.310955439202097700e-001, 9.240837092450902800e-002, 9.816510048711224500e-001, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -5.0e-001, -5.0e-001, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1]
    b = Float64[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0, 0, -0.5]
    pwork = ECOS.ECOS_setup(
        n,
        m,
        p,
        l,
        ncones,
        q,
        0,
        Gpr,
        Gjc,
        Gir,
        Apr,
        Ajc,
        Air,
        c,
        h,
        b,
    )
    ECOS.unsafe_add_settings(pwork, Dict(:maxit => 1))
    @test ECOS.ECOS_solve(pwork) == ECOS.ECOS_MAXIT
    ECOS.ECOS_cleanup(pwork, 0)
    # Now test without max iterations
    pwork = ECOS.ECOS_setup(
        n,
        m,
        p,
        l,
        ncones,
        q,
        0,
        Gpr,
        Gjc,
        Gir,
        Apr,
        Ajc,
        Air,
        c,
        h,
        b,
    )
    @test ECOS.ECOS_solve(pwork) == ECOS.ECOS_OPTIMAL
    ECOS.ECOS_cleanup(pwork, 0)
    return
end

"""
Original problem:

Max   x
s.t.  x = 3
      8u + 9v <= 25
      -x + u + v = 0
      x free
      u <= 0
      v >= 0

ECOS form:

Min  -x
s.t.  x           == 3
     -x +  u +  v == 0
          8u + 9v <= 25
           u      <= 0
               -v <= 0
"""
function test_primal_infeasible()
    G = SparseArrays.sparse(Float64[0  8  9; 0  1  0; 0  0 -1])
    A = SparseArrays.sparse(Float64[1  0  0; -1  1  1])
    infeas_prob = ECOS.ECOS_setup(
        3,
        3,
        2,
        3,
        0,
        Int[],
        0,
        G.nzval,
        G.colptr .- 1,
        G.rowval .- 1,
        A.nzval,
        A.colptr .- 1,
        A.rowval .- 1,
        Float64[-1, 0, 0],
        Float64[25, 0, 0],
        Float64[3, 0],
    )
    @test ECOS.ECOS_solve(infeas_prob) == ECOS.ECOS_PINF
    ECOS.ECOS_cleanup(infeas_prob, 0)
    return
end

test_options()
test_primal_infeasible()


push!(LOAD_PATH, pwd())
include("startup.jl")

#using PyCall
#pygui(:tk) # :tk, :gtk3, :gtk, :qt5, :qt4, :qt, or :wx
import PyPlot
import .InterpolationFunctions
import  .MasslistFunctions
import .ResultFileFunctions

file = "ExampleFiles/TOFDATA/results/_result.hdf5"
availableTraces = ResultFileFunctions.loadResults(file,masslistOnly=true)
linewidth = 2

fig=PyPlot.figure()
concAx = PyPlot.subplot(1,1,1)
data =   ResultFileFunctions.getTraces(file,massIndices=[1])
concAx.semilogy(availableTraces.Times, data, linewidth=linewidth, label="m/z $(round(availableTraces.MasslistMasses[1],digits=3)) -  $(MasslistFunctions.sumFormulaStringFromCompositionArray(availableTraces.MasslistCompositions[:,1]))","b")
concAx.set_ylabel("CPS")
concAx.set_xlabel("Time")
concAx.grid()
concAx.legend(loc=1)
lastPlottedIndex = 1
activeIndices = []

function changeLastPlotTo(i)
    global lastPlottedIndex, data
    if length(concAx.lines) > 0
        concAx.lines[end].remove()
    end
    data = ResultFileFunctions.getTraces(file,massIndices=[i])
    concAx.semilogy(availableTraces.Times, data, linewidth=linewidth, label="m/z $(round(availableTraces.MasslistMasses[i],digits=3)) -  $(MasslistFunctions.sumFormulaStringFromCompositionArray(availableTraces.MasslistCompositions[:,i]))","b")
    concAx.legend(loc=1)
    global lastPlottedIndex = i
    println("Setting $lastPlottedIndex as last plot")
end

function addNewPlot()
    global lastPlottedIndex, data
    println("Adding $lastPlottedIndex to plots")
    if length(concAx.lines) > 0
        concAx.lines[end].remove()
    end
    data = ResultFileFunctions.getTraces(file,massIndices=[lastPlottedIndex])
    concAx.semilogy(availableTraces.Times, data, linewidth=linewidth, label="m/z $(round(availableTraces.MasslistMasses[lastPlottedIndex],digits=3)) -  $(MasslistFunctions.sumFormulaStringFromCompositionArray(availableTraces.MasslistCompositions[:,lastPlottedIndex]))")
    concAx.semilogy(availableTraces.Times, data, linewidth=linewidth, label="m/z $(round(availableTraces.MasslistMasses[lastPlottedIndex],digits=3)) -  $(MasslistFunctions.sumFormulaStringFromCompositionArray(availableTraces.MasslistCompositions[:,lastPlottedIndex]))","b")
    concAx.legend(loc=1)
end

function keyReleaseHandler(event)
    global activeIndices
    k=event.key
    if k == " "
        println("Adding Plot permanently")
        addNewPlot()
        push!(activeIndices,lastPlottedIndex)
    end
end
eventStepCumulator=0
function scrollHandler(event)
    global lastPlottedIndex, eventStepCumulator
    if event.step != 0
        println("Scrolled $(event[:step])")
    end
    eventStepCumulator += event.step # mouse pads scroll fractional
    if (eventStepCumulator >= 1) || (eventStepCumulator <=-0.6)
        plotIndex = clamp(lastPlottedIndex + eventStepCumulator, 1, length(availableTraces.MasslistMasses))
        changeLastPlotTo(Int64(floor(plotIndex)))
        eventStepCumulator = 0
    end
end

fig.canvas.mpl_connect("scroll_event", scrollHandler)
fig.canvas.mpl_connect("key_release_event", keyReleaseHandler)


const MANTISSA_MASK = Base.significand_mask(Float64)
const EXP_MASK = Base.exponent_mask(Float64) >> Base.significand_bits(Float64)

memcpy(d, doff, s, soff, n) = ccall(:memcpy, Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}, Int), d + doff - 1, s + soff - 1, n)
memmove(d, doff, s, soff, n) = ccall(:memmove, Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}, Int), d + doff - 1, s + soff - 1, n)

pow5_bitcount(::Type{Float16}) = 30
pow5_bitcount(::Type{Float32}) = 61
pow5_bitcount(::Type{Float64}) = 121

pow5_inv_bitcount(::Type{Float16}) = 30
pow5_inv_bitcount(::Type{Float32}) = 59
pow5_inv_bitcount(::Type{Float64}) = 122

qinvbound(::Type{Float16}) = 4
qinvbound(::Type{Float32}) = 9
qinvbound(::Type{Float64}) = 21

qbound(::Type{Float16}) = 15
qbound(::Type{Float32}) = 31
qbound(::Type{Float64}) = 63

"""
    Ryu.log10pow2(e::Integer)

Computes `floor(log10(2^e))`. This is valid for all `e < 1651`.
"""
log10pow2(e) = (e * 78913) >> 18

"""
    Ryu.log10pow5(e::Integer)

Computes `floor(log10(5^e))`. This is valid for all `e < 2621`.
"""
log10pow5(e) = (e * 732923) >> 20

"""
    Ryu.pow5bits(e)

Computes `e == 0 ? 1 : ceil(log2(5^e))`. This is valid for `e < 3529` (if performend in `Int32` arithmetic).
"""
pow5bits(e) = ((e * 1217359) >> 19) + 1

""""
     Ryu.mulshift(m::U, mula, j) where {U<:Unsigned}

Compute `(m * mul) >> j`, where `j >= 8*sizeof(U)`. The type of the results is the larger of `U` or `UInt32`.
"""
@inline function mulshift(m::U, mul, j) where {U<:Unsigned}
    W = widen(U)
    nbits = 8*sizeof(U)
    return ((((W(m) * (mul % U)) >> nbits) + W(m) * (mul >> nbits)) >> (j - nbits)) % promote_type(U,UInt32)
end

indexforexp(e) = div(e + 15, 16)
pow10bitsforindex(idx) = 16 * idx + 120
lengthforindex(idx) = div(((Int64(16 * idx) * 1292913986) >> 32) + 1 + 16 + 8, 9)

"""
    Ryu.pow5(x, p)

Return `true` if `5^p` is a divisor of `x`.
"""
@inline function pow5(x, p)
    count = 0
    while true
        q = div(x, 5)
        r = x - 5 * q
        r != 0 && return count >= p
        x = q
        count += 1
    end
end

"""
    Ryu.pow2(x, p)

Return `true` if `2^p` is a divisor of `x`. In other words, if the trailing `p` bits of `x` are zero.
"""
pow2(x, p) = (x & ((Int64(1) << p) - 1)) == 0

"""
    Ryu.decimallength(v)

The number of decimal digits of the integer `v`.
"""
@inline function decimallength(v)
    v >= 10000000000000000 && return 17
    v >= 1000000000000000 && return 16
    v >= 100000000000000 && return 15
    v >= 10000000000000 && return 14
    v >= 1000000000000 && return 13
    v >= 100000000000 && return 12
    v >= 10000000000 && return 11
    v >= 1000000000 && return 10
    v >= 100000000 && return 9
    v >= 10000000 && return 8
    v >= 1000000 && return 7
    v >= 100000 && return 6
    v >= 10000 && return 5
    v >= 1000 && return 4
    v >= 100 && return 3
    v >= 10 && return 2
    return 1
end
@inline function decimallength(v::UInt32)
    v >= 100000000 && return 9
    v >= 10000000 && return 8
    v >= 1000000 && return 7
    v >= 100000 && return 6
    v >= 10000 && return 5
    v >= 1000 && return 4
    v >= 100 && return 3
    v >= 10 && return 2
    return 1
end
@inline function decimallength(v::UInt16)
    v >= 10000 && return 5
    v >= 1000 && return 4
    v >= 100 && return 3
    v >= 10 && return 2
    return 1
end

@inline function mulshiftinvsplit(::Type{Float64}, mv, mp, mm, i, j)
    @inbounds mul = DOUBLE_POW5_INV_SPLIT[i + 1]
    vr = mulshift(mv, mul, j)
    vp = mulshift(mp, mul, j)
    vm = mulshift(mm, mul, j)
    return vr, vp, vm
end

@inline function mulshiftinvsplit(::Type{Float32}, mv, mp, mm, i, j)
    @inbounds mul = FLOAT_POW5_INV_SPLIT[i + 1]
    vr = mulshift(mv, mul, j)
    vp = mulshift(mp, mul, j)
    vm = mulshift(mm, mul, j)
    return vr, vp, vm
end

@inline function mulshiftinvsplit(::Type{Float16}, mv, mp, mm, i, j)
    @inbounds mul = HALF_POW5_INV_SPLIT[i + 1]
    vr = mulshift(mv, mul, j)
    vp = mulshift(mp, mul, j)
    vm = mulshift(mm, mul, j)
    return vr, vp, vm
end

@inline function mulshiftsplit(::Type{Float64}, mv, mp, mm, i, j)
    @inbounds mul = DOUBLE_POW5_SPLIT[i + 1]
    vr = mulshift(mv, mul, j)
    vp = mulshift(mp, mul, j)
    vm = mulshift(mm, mul, j)
    return vr, vp, vm
end

@inline function mulshiftsplit(::Type{Float32}, mv, mp, mm, i, j)
    @inbounds mul = FLOAT_POW5_SPLIT[i + 1]
    vr = mulshift(mv, mul, j)
    vp = mulshift(mp, mul, j)
    vm = mulshift(mm, mul, j)
    return vr, vp, vm
end

@inline function mulshiftsplit(::Type{Float16}, mv, mp, mm, i, j)
    @inbounds mul = HALF_POW5_SPLIT[i + 1]
    vr = mulshift(mv, mul, j)
    vp = mulshift(mp, mul, j)
    vm = mulshift(mm, mul, j)
    return vr, vp, vm
end

"""
    Ryu.umul256(a::UInt128, bHi::UInt64, bLo::UInt64)::Tuple{UInt128, UInt128}

Compute `p = a*b` where `b = bLo + bHi<<64`, returning the result as `pLo, pHi` where `p = pLo + pHi<<128`.
"""
@inline function umul256(a, bHi, bLo)
    aLo = a % UInt64
    aHi = (a >> 64) % UInt64

    b00 = UInt128(aLo) * bLo
    b01 = UInt128(aLo) * bHi
    b10 = UInt128(aHi) * bLo
    b11 = UInt128(aHi) * bHi

    b00Lo = b00 % UInt64
    b00Hi = (b00 >> 64) % UInt64

    mid1 = b10 + b00Hi
    mid1Lo = mid1 % UInt64
    mid1Hi = (mid1 >> 64) % UInt64

    mid2 = b01 + mid1Lo
    mid2Lo = mid2 % UInt64
    mid2Hi = (mid2 >> 64) % UInt64

    pHi = b11 + mid1Hi + mid2Hi
    pLo = (UInt128(mid2Lo) << 64) | b00Lo
    return pLo, pHi
end

"""
    Ryu.umul256_hi(a::UInt128, bHi::UInt64, bLo::UInt64)::UInt128

Compute `pHi = (a*b)>>128` where `b = bLo + bHi<<64`.
"""
@inline umul256_hi(a, bHi, bLo) = umul256(a, bHi, bLo)[2]

"""
    Ryu.mulshiftmod1e9(m, mula, mulb, mulc, j)::UInt32

Compute `(m * mul) >> j % 10^9` where `mul = mula + mulb<<64 + mulc<<128`, and `j >= 128`.
"""
@inline function mulshiftmod1e9(m, mula, mulb, mulc, j)
    b0 = UInt128(m) * mula
    b1 = UInt128(m) * mulb
    b2 = UInt128(m) * mulc
    mid = b1 + ((b0 >> 64) % UInt64)
    s1 = b2 + ((mid >> 64) % UInt64)
    v = s1 >> (j - 128)
    multiplied = umul256_hi(v, 0x89705F4136B4A597, 0x31680A88F8953031)
    shifted = (multiplied >> 29) % UInt32
    return (v % UInt32) - UInt32(1000000000) * shifted
end

@inline function append_n_digits(olength, digits, buf, pos)
    i = 0
    while digits >= 10000
        c = digits % 10000
        digits = div(digits, 10000)
        c0 = (c % 100) << 1
        c1 = div(c, 100) << 1
        unsafe_copyto!(buf, pos + olength - i - 2, DIGIT_TABLE, c0 + 1, 2)
        unsafe_copyto!(buf, pos + olength - i - 4, DIGIT_TABLE, c1 + 1, 2)
        i += 4
    end
    if digits >= 100
        c = (digits % 100) << 1
        digits = div(digits, 100)
        unsafe_copyto!(buf, pos + olength - i - 2, DIGIT_TABLE, c + 1, 2)
        i += 2
    end
    if digits >= 10
        c = digits << 1
        unsafe_copyto!(buf, pos + olength - i - 2, DIGIT_TABLE, c + 1, 2)
        i += 2
    else
        buf[pos] = UInt8('0') + digits
        i += 1
    end
    return pos + i
end

@inline function append_d_digits(olength, digits, buf, pos, decchar)
    i = 0
    while digits >= 10000
        c = digits % 10000
        digits = div(digits, 10000)
        c0 = (c % 100) << 1
        c1 = div(c, 100) << 1
        unsafe_copyto!(buf, pos + olength + 1 - i - 2, DIGIT_TABLE, c0 + 1, 2)
        unsafe_copyto!(buf, pos + olength + 1 - i - 4, DIGIT_TABLE, c1 + 1, 2)
        i += 4
    end
    if digits >= 100
        c = (digits % 100) << 1
        digits = div(digits, 100)
        unsafe_copyto!(buf, pos + olength + 1 - i - 2, DIGIT_TABLE, c + 1, 2)
        i += 2
    end
    if digits >= 10
        c = digits << 1
        buf[pos] = DIGIT_TABLE[c + 1]
        buf[pos + 1] = decchar
        buf[pos + 2] = DIGIT_TABLE[c + 2]
        i += 3
    else
        buf[pos] = UInt8('0') + digits
        buf[pos + 1] = decchar
        i += 2
    end
    return pos + i
end

@inline function append_c_digits(count, digits, buf, pos)
    i = 0
    while i < count - 1
        c = (digits % 100) << 1
        digits = div(digits, 100)
        unsafe_copyto!(buf, pos + count - i - 2, DIGIT_TABLE, c + 1, 2)
        i += 2
    end
    if i < count
        buf[pos + count - i - 1] = UInt8('0') + (digits % 10)
        i += 1
    end
    return pos + i
end

@inline function append_nine_digits(digits, buf, pos)
    if digits == 0
        for _ = 1:9
            buf[pos] = UInt8('0')
            pos += 1
        end
        return pos
    end
    i = 0
    while i < 5
        c = digits % 10000
        digits = div(digits, 10000)
        c0 = (c % 100) << 1
        c1 = div(c, 100) << 1
        unsafe_copyto!(buf, pos + 7 - i, DIGIT_TABLE, c0 + 1, 2)
        unsafe_copyto!(buf, pos + 5 - i, DIGIT_TABLE, c1 + 1, 2)
        i += 4
    end
    buf[pos] = UInt8('0') + digits
    i += 1
    return pos + i
end

const BIG_MASK = (big(1) << 64) - 1

const POW10_SPLIT = collect(Iterators.flatten(map(0:63) do idx
    pow10bits = pow10bitsforindex(idx)
    map(0:lengthforindex(idx)-1) do i
        v = (div(big(1) << pow10bits, big(10)^(9 * i)) + 1) % ((big(10)^9) << 136)
        return (UInt64(v & BIG_MASK), UInt64((v >> 64) & BIG_MASK), UInt64((v >> 128) & BIG_MASK))
    end
end))

function generateinversetables()
    POW10_OFFSET_2 = Vector{UInt16}(undef, 68 + 1)
    MIN_BLOCK_2 = fill(0xff, 68 + 1)
    POW10_SPLIT_2 = Tuple{UInt64, UInt64, UInt64}[]
    lowerCutoff = big(1) << (54 + 8)
    for idx = 0:67
        POW10_OFFSET_2[idx + 1] = length(POW10_SPLIT_2)
        i = 0
        while true
            v = ((big(10)^(9 * (i + 1)) >> (-(120 - 16 * idx))) % (big(10)^9) << (120 + 16))
            if MIN_BLOCK_2[idx + 1] == 0xff && ((v * lowerCutoff) >> 128) == 0
                i += 1
                continue
            end
            if MIN_BLOCK_2[idx + 1] == 0xff
                MIN_BLOCK_2[idx + 1] = i
            end
            v == 0 && break
            push!(POW10_SPLIT_2, ((v & BIG_MASK) % UInt64, ((v >> 64) & BIG_MASK) % UInt64, ((v >> 128) & BIG_MASK) % UInt64))
            i += 1
        end
    end
    POW10_OFFSET_2[end] = length(POW10_SPLIT_2)
    MIN_BLOCK_2[end] = 0x00

    return POW10_OFFSET_2, MIN_BLOCK_2, POW10_SPLIT_2
end

const POW10_OFFSET_2, MIN_BLOCK_2, POW10_SPLIT_2 = generateinversetables()

"""
    Ryu.pow5invsplit(T, i)

Compute `floor(2^k/5^i)+1`, where `k = pow5bits(i) - 1 + pow5_inv_bitcount(T)`. The result
is an unsigned integer twice as wide as `T` (i.e. a `UInt128` if `T == Float64`), with
`pow5_inv_bitcount(T)` significant bits.
"""
function pow5invsplit(::Type{T}, i) where {T<:AbstractFloat}
    W = widen(uinttype(T))
    pow = big(5)^i
    inv = div(big(1) << (ndigits(pow, base=2) - 1 + pow5_inv_bitcount(T)), pow) + 1
    return W(inv)
end

"""
    Ryu.pow5split(T, i)

Compute `floor(5^i/2^k)`, where `k = pow5bits(i) - pow5_bitcount(T)`. The result is an
unsigned integer twice as wide as `T` (i.e. a `UInt128` if `T == Float64`), with
`pow5_bitcount(T)` significant bits.
"""
function pow5split(::Type{T}, i) where {T<:AbstractFloat}
    W = widen(uinttype(T))
    pow = big(5)^i
    return W(pow >> (ndigits(pow, base=2) - pow5_bitcount(T)))
end

const DOUBLE_POW5_INV_SPLIT = map(i->pow5invsplit(Float64, i), 0:291)
const FLOAT_POW5_INV_SPLIT = map(i->pow5invsplit(Float32, i), 0:30)
const HALF_POW5_INV_SPLIT = map(i->pow5invsplit(Float16, i), 0:17)

const DOUBLE_POW5_SPLIT = map(i->pow5split(Float64, i), 0:325)
const FLOAT_POW5_SPLIT = map(i->pow5split(Float32, i), 0:46)
const HALF_POW5_SPLIT = map(i->pow5split(Float16, i), 0:23)

const DIGIT_TABLE = UInt8[
  '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9',
  '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9',
  '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9',
  '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9',
  '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9',
  '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9',
  '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9',
  '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9',
  '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9',
  '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9'
]

const POW10_OFFSET = UInt16[
  0, 2, 5, 8, 12, 16, 21, 26, 32, 39,
  46, 54, 62, 71, 80, 90, 100, 111, 122, 134,
  146, 159, 173, 187, 202, 217, 233, 249, 266, 283,
  301, 319, 338, 357, 377, 397, 418, 440, 462, 485,
  508, 532, 556, 581, 606, 632, 658, 685, 712, 740,
  769, 798, 828, 858, 889, 920, 952, 984, 1017, 1050,
  1084, 1118, 1153, 1188
]


###
### LongSequence specific specializations of src/biosequence/transformations.jl
###

"""
    resize!(seq, size)

Resize a biological sequence `seq`, to a given `size`.
"""
function Base.resize!(seq::LongSequence{A}, size::Integer) where {A}
    if size < 0
        throw(ArgumentError("size must be non-negative"))
    end
    orphan!(seq, size)
    resize!(seq.data, seq_data_len(A, size + seq.part.start - 1))
    seq.part = seq.part.start:seq.part.start+size-1
    return seq
end

function Base.filter!(f::Function, seq::LongSequence{A}) where {A}
    orphan!(seq)

    len = 0
    next = bitindex(seq, 1)
    j = index(next)
    datum::UInt64 = 0
    for i in 1:lastindex(seq)
        x = inbounds_getindex(seq, i)
        if f(x)
            datum |= enc64(seq, x) << offset(next)
            len += 1
            #TODO: Resolve use of bits_per_symbol.
            next += bits_per_symbol(A())
            if index(next) != j
                seq.data[j] = datum
                datum = 0
                j = index(next)
            end
        end
    end
    if offset(next) > 0
        seq.data[j] = datum
    end
    resize!(seq, len)

    return seq
end

function Base.map!(f::Function, seq::LongSequence)
    orphan!(seq)
    for i in 1:lastindex(seq)
        unsafe_setindex!(seq, f(inbounds_getindex(seq, i)), i)
    end
    return seq
end

"""
    reverse!(seq::LongSequence)

Reverse a biological sequence `seq` in place.
"""
function Base.reverse!(seq::LongSequence)
    orphan!(seq) # TODO: Is the orphan call really nessecery given the indexing calls will also call orphan?
    @inbounds for i in 1:div(lastindex(seq), 2)
	    x = seq[i]
        i′ = lastindex(seq) - i + 1
	    seq[i] = seq[i′]
	    seq[i′] = x
    end
    return seq
end

"""
    Base.reverse(seq::LongSequence{A}) where {A<:NucleicAcidAlphabet}

Create a reversed copy of a LongSequence representing a DNA or RNA sequence.

This version of the reverse method is optimized for speed, taking advantage of
bit-parallel operations.
"""
function Base.reverse(seq::LongSequence{A}) where {A<:NucleicAcidAlphabet}
    data = Vector{UInt64}(undef, seq_data_len(A, length(seq)))
    i = 1
    next = lastbitindex(seq)
    stop = bitindex(seq, 0)
    r = rem(offset(next) + bits_per_symbol(seq), 64)
    if r == 0
        @inbounds while next - stop > 0
            x = seq.data[index(next)]
            data[i] = reversebits(x, BitsPerSymbol(seq))
            i += 1
            next -= 64
        end
    else
        @inbounds while next - stop > 64
            j = index(next)
            x = (seq.data[j] << (64 - r)) | (seq.data[j - 1] >> r)
            data[i] = reversebits(x, BitsPerSymbol(seq))
            i += 1
            next -= 64
        end
        if next - stop > 0
            j = index(next)
            x = seq.data[j] << (64 - r)
            if r < next - stop
                x |= seq.data[j - 1] >> r
            end
            data[i] = reversebits(x, BitsPerSymbol(seq))
        end
    end
    return LongSequence{A}(data, 1:length(seq), false)
end

"""
    complement!(seq)

Make a complement sequence of `seq` in place.
"""
function complement!(seq::LongSequence{A}) where {A<:NucleicAcidAlphabet}
    orphan!(seq)
    next = firstbitindex(seq)
    stop = bitindex(seq, lastindex(seq) + 1)
    seqdata = seq.data
    @inbounds while next < stop
        x = seqdata[index(next)]
        seqdata[index(next)] = complement_bitpar(x, Alphabet(seq))
        next += 64
    end
    return seq
end

###
### Shuffle
###

function Random.shuffle!(seq::LongSequence)
    orphan!(seq) # TODO: Is this call to orphan nessecery, given setindex should call `orphan!` for us?
    # Fisher-Yates shuffle
    for i in 1:lastindex(seq) - 1
        j = rand(i:lastindex(seq))
        seq[i], seq[j] = seq[j], seq[i]
    end
    return seq
end


module RoME

using Reexport

@reexport using IncrementalInference
@reexport using TransformUtils
@reexport using Distributions
@reexport using KernelDensityEstimate
@reexport using ApproxManifoldProducts

using
  Distributed,
  LinearAlgebra,
  Statistics,
  Graphs,  # TODO determine how many parts still require Graphs still directly
  Rotations,
  CoordinateTransformations,
  JLD2,
  ProgressMeter,
  DocStringExtensions

import Base: +, \, convert
import TransformUtils: ⊖, ⊕, convert, compare, ominus, veeQuaternion
import IncrementalInference: convert, getSample, reshapeVec2Mat, extractdistribution, DFG

# const AMP = ApproxManifoldProducts

export
  IIF,  # Aliases for various packages
  KDE,
  TU,
  AMP,
  # initfg,
  # RoME specific functions
  measureMeanDist,
  predictBodyBR,
  getLastPose,
  getLastPose2D,
  odomKDE,
  initFactorGraph!,
  addOdoFG!,
  addposeFG!,
  newLandm!,
  addBRFG!,
  addMMBRFG!,
  addAutoLandmBR!,
  projNewLandm!,
  malahanobisBR,
  veePose3,
  veePose,
  \,
  RangeAzimuthElevation,

  # helper functions
  get2DSamples,
  # getAll2D,
  get2DSampleMeans,
  getAll2DMeans,
  getAll2DPoses,
  get2DPoseSamples,
  get2DPoseMeans,
  getKDE,
  getVertKDE,
  get2DPoseMax,
  getAll2DLandmarks,
  get2DLandmSamples,
  get2DLandmMeans,
  get2DLandmMax,

  # helper functions
  getLastLandm2D,
  getLastPose2D,
  getNextLbl,

  # RobotUtils
  getRangeKDEMax2D,

  # some transform functions
  cart2pol,
  pol2cart,

  # Feature tracking code
  Feature,
  initTrackersFrom,
  propAllTrackers!,
  measUpdateTrackers!,
  assocMeasWFeats!,

  lsrBR,

  # Didson model
  evalPotential,
  LinearRangeBearingElevation,
  project!,
  project,
  backprojectRandomized!,
  residual!,
  residualLRBE!,
  reuseLBRA,
  ominus,
  ominus!,
  +,
  evalPotential,
  getSample!,
  getSample,
  # obsolete
  WrapParam,
  WrapParamArray,

  # Didson convenience function
  addLinearArrayConstraint,

  # camera model -- TODO --separate out
  CameraIntrinsic,
  CameraExtrinsic,
  CameraModelFull,
  project!,
  project,
  backprojectRandomized!,
  # keep
  cameraResidual!,

  # Point2D
  Point2,
  Point2Point2,
  PackedPoint2Point2,
  Point2Point2WorldBearing,
  PackedPoint2Point2WorldBearing,
  Point2Point2Range,
  PackedPoint2Point2Range,
  PriorPoint2,
  PackedPriorPoint2,
  Pose2Point2BearingRange,
  Pose2Point2BearingRangeMH,
  PackedPose2Point2BearingRange,
  PackedPose2Point2BearingRangeMH,
  Pose2Point2Bearing,
  PackedPose2Point2Bearing,
  Pose2Point2Range,
  PackedPose2Point2Range,
  PriorPoint2,
  PackedPriorPoint2,
  # Point2D with null hypotheses
  PriorPoint2DensityNH,
  PackedPriorPoint2DensityNH,

  # Velocity in Point2 types
  DynPoint2,
  DynPoint2VelocityPrior,
  DynPoint2DynPoint2,
  VelPoint2VelPoint2,
  Point2Point2Velocity,
  PackedDynPoint2VelocityPrior,
  PackedVelPoint2VelPoint2,

  # likely to be deprecated
  solveLandm,
  solvePose2,
  solveSetSeps,
  addPose2Pose2!,

  # acoustics
  Pose2Point2BearingRangeDensity,
  PackedPose2Point2BearingRangeDensity,
  Pose2Point2RangeDensity,
  PackedPose2Point2RangeDensity,

  # Pose2D
  Pose2,
  PriorPose2,
  PackedPriorPose2,
  PartialPriorYawPose2,
  PackedPartialPriorYawPose2,
  Pose2Pose2,
  PackedPose2Pose2,
  # velocity in Pose2
  DynPose2,
  DynPose2VelocityPrior,
  PackedDynPose2VelocityPrior,
  VelPose2VelPose2,
  PackedVelPose2VelPose2,
  DynPose2Pose2,
  PackedDynPose2Pose2,
  # Will be deprecated
  addPose2Pose2,

  # Polar types
  Polar,
  PolarPolar,
  PriorPolar,

  # MultipleFeatures2D constraint functions
  MultipleFeatures2D,
  getUvecScaleFeature2D,
  getUvecScaleBaseline2D,

  # Pose3, Three dimensional
  Pose3,
  Point3,
  PriorPoint3,
  PriorPose3,
  PackedPriorPose3,
  Pose3Pose3,
  PackedPose3Pose3,
  projectParticles,
  ⊕,
  Pose3Pose3NH,
  PackedPose3Pose3NH,

  # partial Pose3
  PriorPose3ZRP,
  Pose3Pose3XYYaw,
  # Various utilities
  passTypeThrough,

  # SLAM specific functions
  SLAMWrapper,

  # FG Analysis tools
  rangeErrMaxPoint2,
  rangeCompAllPoses,
  rangeCompAllPoses,

  # new robot navigation functionality
  triggerPose,
  GenericInSituSystem,
  InSituSystem,
  makeInSituSys,
  makeGenericInSituSys,
  advOdoByRules,
  poseTrigAndAdd!,
  poseTrigAndAdd!,
  processTreeTrackersUpdates!,
  addSoftEqualityPoint2D,
  vectoarr2,
  basicFactorGraphExample,
  predictVariableByFactor,

  # jld required Features Type
  LaserFeatures,

  # Deprecated
  PartialPriorRollPitchZ,
  PackedPartialPriorRollPitchZ,
  PartialPose3XYYaw,
  PackedPartialPose3XYYaw,
  PartialPose3XYYawNH,
  PackedPartialPose3XYYawNH
  # Point2DPoint2DRange,
  # PackedPoint2DPoint2DRange,
  # PackedPoint2DPoint2DRange,
  # PackedPriorPoint2D,
  # Pose2DPoint2DRangeDensity, # to be deprecated
  # Point2DPoint2D, # deprecated
  # Point2DPoint2DRange, # deprecated
  # PriorPoint2D, # deprecated
  # Pose2DPoint2DBearingRange, # begin deprecated
  # Pose2DPoint2DBearing, # deprecated
  # PriorPoint2D, # deprecated
  # PackedPriorPoint2D # deprecated`

# doesnt seem to work
# @info "Setting IncrementalInference de-serialization namespace RoME"
# setSerializationNamespace!("RoME" => RoME)
# @info "done..."

  # # solve with isam in pytslam
  # doISAMSolve,
  # drawCompPosesLandm,
  #
  # # Victoria Park data specific
  # addLandmarksFactoGraph!,
  # appendFactorGraph!,
  # doBatchRun,
  # rotateFeatsToWorld

include("SpecialDefinitions.jl")

include("BayesTracker.jl")

include("SensorModels.jl")
include("CameraModel.jl")

# 2D
include("variables/Point2D.jl")
include("variables/Pose2D.jl")
include("variables/DynPoint2D.jl")
include("variables/DynPose2D.jl")

# 3D
include("variables/Point3D.jl")
include("variables/Pose3D.jl")

include("factors/Point2D.jl")
include("factors/Polar.jl")
include("factors/Pose2D.jl")
include("factors/Bearing2D.jl")
include("factors/Range2D.jl")
include("factors/BearingRange2D.jl")
include("factors/DynPoint2D.jl")
include("factors/DynPose2D.jl")
include("factors/Point3D.jl")
include("factors/Pose3Pose3.jl")
include("factors/PartialPose3.jl")
include("factors/MultipleFeaturesConstraint.jl")
include("factors/InertialPose3.jl")

include("Slam.jl")

include("RobotUtils.jl")

include("SimulationUtils.jl")

include("FactorGraphAnalysisTools.jl")

include("RobotDataTypes.jl") #WheeledRobotUtils
include("NavigationSystem.jl")

include("Deprecated.jl")

# include("dev/ISAMRemoteSolve.jl")

end


using FTCTests
using Test
using Transducers
using LinearAlgebra
using DataFrames

@testset "evaluate" begin
    t0, tf = 0.0, 1.0
    ts = t0:0.01:tf
    d = 3
    sol = ts |> Map(t -> (; plant= (;state = (; p=ones(d))))) |> collect
    df = DataFrame(time=ts, sol=sol)
    traj_des(t) = zeros(d)
    jld2 = Dict("df" => df, "traj_des" => traj_des, "t0" => t0, "tf" => tf)
    t1 = 0.5
    error_norm = norm(ones(d))
    for threshold in [error_norm-0.1, error_norm+0.1]
        is_success = evaluate(jld2, t1, threshold)
        if error_norm < threshold
            @test is_success
        else
            @test !is_success
        end
    end
end