@testset "Decision Model kwargs" begin
    template = get_thermal_dispatch_template_network()
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    c_sys5_re = PSB.build_system(PSITestSystems, "c_sys5_re")

    @test_throws MethodError DecisionModel(template, c_sys5; bad_kwarg=10)

    model = DecisionModel(template, c_sys5; optimizer=GLPK_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT

    model = DecisionModel(
        MockOperationProblem,
        get_thermal_dispatch_template_network(
            NetworkModel(CopperPlatePowerModel; use_slacks=true),
        ),
        c_sys5_re;
        optimizer=GLPK_optimizer,
    )
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT
    model = DecisionModel(
        get_thermal_dispatch_template_network(),
        c_sys5;
        optimizer=GLPK_optimizer,
    )
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT

    #"Test passing custom JuMP model"
    my_model = JuMP.Model()
    my_model.ext[:PSI_Testing] = 1
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    model = DecisionModel(
        get_thermal_dispatch_template_network(),
        c_sys5,
        my_model;
        optimizer=GLPK_optimizer,
    )
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT
    @test haskey(PSI.get_optimization_container(model).JuMPmodel.ext, :PSI_Testing)
    @test (:ParameterJuMP in keys(PSI.get_optimization_container(model).JuMPmodel.ext)) ==
          false
end

@testset "Set optimizer at solve call" begin
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    template = get_thermal_standard_uc_template()
    set_service_model!(
        template,
        ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "test"),
    )
    UC = DecisionModel(template, c_sys5; optimizer=GLPK_optimizer)
    output_dir = mktempdir(cleanup=true)
    @test build!(UC; output_dir=output_dir) == PSI.BuildStatus.BUILT
    @test solve!(UC; optimizer=GLPK_optimizer) == RunStatus.SUCCESSFUL
    res = ProblemResults(UC)
    @test isapprox(get_objective_value(res), 340000.0; atol=100000.0)
    vars = res.variable_values
    @test PSI.VariableKey(ActivePowerVariable, PSY.ThermalStandard) in keys(vars)
    @test size(read_variable(res, "StartVariable__ThermalStandard")) == (24, 6)
    @test size(read_parameter(res, "ActivePowerTimeSeriesParameter__PowerLoad")) == (24, 4)
    @test size(read_expression(res, "ProductionCostExpression__ThermalStandard")) == (24, 6)
    @test size(read_aux_variable(res, "TimeDurationOn__ThermalStandard")) == (24, 6)
    @test length(read_variables(res)) == 4
    @test length(read_parameters(res)) == 1
    @test length(read_duals(res)) == 0
    @test length(read_expressions(res)) == 1
    @test read_variables(res, ["StartVariable__ThermalStandard"])["StartVariable__ThermalStandard"] ==
          read_variable(res, "StartVariable__ThermalStandard")
    @test read_variables(res, [(StartVariable, ThermalStandard)])["StartVariable__ThermalStandard"] ==
          read_variable(res, StartVariable, ThermalStandard)
    @test read_parameters(res, ["ActivePowerTimeSeriesParameter__PowerLoad"])["ActivePowerTimeSeriesParameter__PowerLoad"] ==
          read_parameter(res, "ActivePowerTimeSeriesParameter__PowerLoad")
    @test read_parameters(res, [(ActivePowerTimeSeriesParameter, PowerLoad)])["ActivePowerTimeSeriesParameter__PowerLoad"] ==
          read_parameter(res, ActivePowerTimeSeriesParameter, PowerLoad)
    @test read_aux_variables(res, ["TimeDurationOff__ThermalStandard"])["TimeDurationOff__ThermalStandard"] ==
          read_aux_variable(res, "TimeDurationOff__ThermalStandard")
    @test read_aux_variables(res, [(TimeDurationOff, ThermalStandard)])["TimeDurationOff__ThermalStandard"] ==
          read_aux_variable(res, TimeDurationOff, ThermalStandard)
    @test read_expressions(res, ["ProductionCostExpression__ThermalStandard"])["ProductionCostExpression__ThermalStandard"] ==
          read_expression(res, "ProductionCostExpression__ThermalStandard")
    @test read_expressions(res, [(PSI.ProductionCostExpression, ThermalStandard)])["ProductionCostExpression__ThermalStandard"] ==
          read_expression(res, PSI.ProductionCostExpression, ThermalStandard)
    @test length(read_aux_variables(res)) == 2
    @test first(keys(read_aux_variables(res, [(PSI.TimeDurationOff, ThermalStandard)]))) ==
          "TimeDurationOff__ThermalStandard"
    export_results(res)
    results_dir = joinpath(output_dir, "results")
    @test isfile(joinpath(results_dir, "optimizer_stats.csv"))
    variables_dir = joinpath(results_dir, "variables")
    @test isfile(joinpath(variables_dir, "ActivePowerVariable__ThermalStandard.csv"))
end

@testset "Test optimization debugging functions" begin
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    template = get_thermal_standard_uc_template()
    set_service_model!(
        template,
        ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "test"),
    )
    model = DecisionModel(template, c_sys5; optimizer=GLPK_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT
    container = PSI.get_optimization_container(model)
    MOIU.attach_optimizer(container.JuMPmodel)
    constraint_indices = get_all_constraint_index(model)
    for (key, index, moi_index) in constraint_indices
        val1 = get_constraint_index(model, moi_index)
        val2 = container.constraints[key].data[index]
        @test val1 == val2
    end
    @test get_constraint_index(model, length(constraint_indices) + 1) === nothing

    var_keys = PSI.get_all_variable_keys(model)
    var_index = get_all_variable_index(model)
    for (ix, (key, index, moi_index)) in enumerate(var_keys)
        index_tuple = var_index[ix]
        @test index_tuple[1] == PSI.encode_key(key)
        @test index_tuple[2] == index
        @test index_tuple[3] == moi_index
        val1 = get_variable_index(model, moi_index)
        val2 = container.variables[key].data[index]
        @test val1 == val2
    end
    @test get_variable_index(model, length(var_index) + 1) === nothing
end

@testset "Decision Model Solve with Slacks" begin
    c_sys5_re = PSB.build_system(PSITestSystems, "c_sys5_re")
    networks = [StandardPTDFModel, DCPPowerModel, ACPPowerModel]
    for network in networks
        template = get_thermal_dispatch_template_network(
            NetworkModel(network; use_slacks=true, PTDF=PTDF(c_sys5_re)),
        )
        model = DecisionModel(template, c_sys5_re; optimizer=ipopt_optimizer)
        @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT
        @test solve!(model) == RunStatus.SUCCESSFUL
    end
end

@testset "Default Decisions Constructors" begin
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    model_ed =
        EconomicDispatchProblem(c_sys5; output_dir=mktempdir(), optimizer=HiGHS_optimizer)
    moi_tests(model_ed, false, 120, 0, 120, 120, 24, false)
    model_uc =
        UnitCommitmentProblem(c_sys5; output_dir=mktempdir(), optimizer=HiGHS_optimizer)
    moi_tests(model_uc, false, 480, 0, 240, 120, 144, true)
    ED_output =
        run_economic_dispatch(c_sys5; output_dir=mktempdir(), optimizer=HiGHS_optimizer)
    UC_output =
        run_unit_commitment(c_sys5; output_dir=mktempdir(), optimizer=HiGHS_optimizer)
    @test ED_output == RunStatus.SUCCESSFUL
    @test UC_output == RunStatus.SUCCESSFUL
end

@testset "Test Locational Marginal Prices between DC lossless with PowerModels vs StandardPTDFModel" begin
    networks = [DCPPowerModel, StandardPTDFModel]
    sys = PSB.build_system(PSITestSystems, "c_sys5")
    ptdf = PTDF(sys)
    # These are the duals of interest for the test
    dual_constraint = [[NodalBalanceActiveConstraint], [CopperPlateBalanceConstraint]]
    LMPs = []
    for (ix, network) in enumerate(networks)
        template = get_template_dispatch_with_network(
            NetworkModel(network; PTDF=ptdf, duals=dual_constraint[ix]),
        )
        if network == StandardPTDFModel
            set_device_model!(
                template,
                DeviceModel(PSY.Line, PSI.StaticBranch; duals=[NetworkFlowConstraint]),
            )
        end
        model = DecisionModel(template, sys; optimizer=HiGHS_optimizer)
        @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT
        @test solve!(model) == RunStatus.SUCCESSFUL
        res = ProblemResults(model)

        # These tests require results to be working
        if network == StandardPTDFModel
            push!(LMPs, abs.(psi_ptdf_lmps(res, ptdf)))
        else
            duals = read_dual(res, NodalBalanceActiveConstraint, Bus)
            duals = abs.(duals[:, propertynames(duals) .!== :DateTime])
            push!(LMPs, duals[!, sort(propertynames(duals))])
        end
    end
    @test isapprox(LMPs[1], LMPs[2], atol=100.0)
end

@testset "Test ProblemResults interfaces" begin
    sys = PSB.build_system(PSITestSystems, "c_sys5_re")
    template = get_template_dispatch_with_network(
        NetworkModel(CopperPlatePowerModel; duals=[CopperPlateBalanceConstraint]),
    )
    model = DecisionModel(template, sys; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT
    @test solve!(model) == RunStatus.SUCCESSFUL

    res = ProblemResults(model)
    container = PSI.get_optimization_container(model)
    constraint_key = PSI.ConstraintKey(CopperPlateBalanceConstraint, PSY.System)
    constraints = PSI.get_constraints(container)[constraint_key]
    dual_results = PSI.read_duals(container)[constraint_key]
    dual_results_read = read_dual(res, constraint_key)
    realized_dual_results =
        read_duals(res, [constraint_key])[PSI.encode_key_as_string(constraint_key)]
    realized_dual_results_string =
        read_duals(res, [PSI.encode_key_as_string(constraint_key)])[PSI.encode_key_as_string(
            constraint_key,
        )]
    @test dual_results ==
          dual_results_read[:, propertynames(dual_results_read) .!= :DateTime] ==
          realized_dual_results[:, propertynames(realized_dual_results) .!= :DateTime] ==
          realized_dual_results_string[
              :,
              propertynames(realized_dual_results_string) .!= :DateTime,
          ]
    for i in axes(constraints)[1]
        dual = JuMP.dual(constraints[i])
        @test isapprox(dual, dual_results[i, :CopperPlateBalanceConstraint__System])
    end

    system = PSI.get_system(model)
    parameter_key = PSI.ParameterKey(ActivePowerTimeSeriesParameter, PSY.PowerLoad)
    param_vals = PSI.read_parameters(container)[parameter_key]
    for load in get_components(PowerLoad, system)
        name = get_name(load)
        vals = get_time_series_values(Deterministic, load, "max_active_power")
        vals = vals .* get_max_active_power(load) * -1.0
        @test all(vals .== param_vals[!, name])
    end

    res = ProblemResults(model)
    @test length(list_variable_names(res)) == 1
    @test length(list_dual_names(res)) == 1
    @test get_model_base_power(res) == 100.0
    @test isa(get_objective_value(res), Float64)
    @test isa(res.variable_values, Dict{PSI.VariableKey, DataFrames.DataFrame})
    @test isa(read_variables(res), Dict{String, DataFrames.DataFrame})
    @test isa(PSI.get_total_cost(res), Float64)
    @test isa(get_optimizer_stats(res), DataFrames.DataFrame)
    @test isa(res.dual_values, Dict{PSI.ConstraintKey, DataFrames.DataFrame})
    @test isa(read_duals(res), Dict{String, DataFrames.DataFrame})
    @test isa(res.parameter_values, Dict{PSI.ParameterKey, DataFrames.DataFrame})
    @test isa(read_parameters(res), Dict{String, DataFrames.DataFrame})
    @test isa(PSI.get_resolution(res), Dates.TimePeriod)
    @test isa(get_system(res), PSY.System)
    @test length(get_timestamps(res)) == 24
end

@testset "Solve DecisionModelModel with auto-build" begin
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    template = get_thermal_standard_uc_template()
    set_service_model!(
        template,
        ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "test"),
    )
    UC = DecisionModel(template, c_sys5; optimizer=GLPK_optimizer)
    output_dir = mktempdir(cleanup=true)
    @test_throws ErrorException solve!(UC)
    @test solve!(UC; optimizer=GLPK_optimizer, output_dir=output_dir) ==
          RunStatus.SUCCESSFUL
end

@testset "Test Serialization, deserialization and write optimizer problem" begin
    fpath = mktempdir(cleanup=true)
    sys = PSB.build_system(PSITestSystems, "c_sys5_re")
    template = get_template_dispatch_with_network(
        NetworkModel(CopperPlatePowerModel; duals=[CopperPlateBalanceConstraint]),
    )
    model = DecisionModel(template, sys; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=fpath) == PSI.BuildStatus.BUILT
    @test solve!(model) == RunStatus.SUCCESSFUL

    file_list = sort!(collect(readdir(fpath)))
    model_name = PSI.get_name(model)
    @test PSI._JUMP_MODEL_FILENAME in file_list
    @test PSI._SERIALIZED_MODEL_FILENAME in file_list
    ED2 = DecisionModel(fpath, HiGHS_optimizer)
    @test build!(ED2, output_dir=fpath) == PSI.BuildStatus.BUILT
    psi_checksolve_test(ED2, [MOI.OPTIMAL], 240000.0, 10000)

    path2 = mktempdir(cleanup=true)
    model_no_sys =
        DecisionModel(template, sys; optimizer=HiGHS_optimizer, system_to_file=false)

    @test build!(model_no_sys; output_dir=path2) == PSI.BuildStatus.BUILT
    @test solve!(model_no_sys) == RunStatus.SUCCESSFUL

    file_list = sort!(collect(readdir(path2)))
    @test .!all(occursin.(r".h5", file_list))
    ED3 = DecisionModel(path2, HiGHS_optimizer; system=sys)
    build!(ED3, output_dir=path2)
    psi_checksolve_test(ED3, [MOI.OPTIMAL], 240000.0, 10000)
end

@testset "Test NonSpinning reseve model" begin
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5_uc_non_spin", add_reserves=true)
    template = get_thermal_standard_uc_template()
    set_device_model!(
        template,
        DeviceModel(ThermalMultiStart, ThermalStandardUnitCommitment),
    )
    set_service_model!(
        template,
        ServiceModel(VariableReserveNonSpinning, NonSpinningReserve, "NonSpinningReserve"),
    )

    UC = DecisionModel(template, c_sys5, optimizer=HiGHS_optimizer)
    output_dir = mktempdir(cleanup=true)
    @test build!(UC; output_dir=output_dir) == PSI.BuildStatus.BUILT
    @test solve!(UC) == RunStatus.SUCCESSFUL
    res = ProblemResults(UC)
    @test isapprox(get_objective_value(res), 247448.0; atol=10000.0)
    vars = res.variable_values
    service_key = PSI.VariableKey(
        ActivePowerReserveVariable,
        PSY.VariableReserveNonSpinning,
        "NonSpinningReserve",
    )
    @test service_key in keys(vars)
end

@testset "Test serialization/deserialization of DecisionModel results" begin
    path = mktempdir(cleanup=true)
    sys = PSB.build_system(PSITestSystems, "c_sys5_re")
    template = get_template_dispatch_with_network(
        NetworkModel(CopperPlatePowerModel; duals=[CopperPlateBalanceConstraint]),
    )
    model = DecisionModel(template, sys; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=path) == PSI.BuildStatus.BUILT
    @test solve!(model, export_problem_results=true) == RunStatus.SUCCESSFUL
    results1 = ProblemResults(model)
    var1_a = read_variable(results1, ActivePowerVariable, ThermalStandard)
    # Ensure that we can deserialize strings into keys.
    var1_b = read_variable(results1, "ActivePowerVariable__ThermalStandard")

    # Results were automatically serialized here.
    results2 = ProblemResults(PSI.get_output_dir(model))
    var2 = read_variable(results2, ActivePowerVariable, ThermalStandard)
    @test var1_a == var2

    # Serialize to a new directory with the exported function.
    results_path = joinpath(path, "results")
    serialize_results(results1, results_path)
    @test isfile(joinpath(results_path, PSI._PROBLEM_RESULTS_FILENAME))
    results3 = ProblemResults(results_path)
    var3 = read_variable(results3, ActivePowerVariable, ThermalStandard)
    @test var1_a == var3
    @test get_system(results3) === nothing
    set_system!(results3, get_system(results1))
    @test get_system(results3) !== nothing

    exp_file =
        joinpath(path, "results", "variables", "ActivePowerVariable__ThermalStandard.csv")
    var4 = PSI.read_dataframe(exp_file)
    # Manually Multiply by the base power var1_a has natural units and export writes directly from the solver
    @test var1_a[:, propertynames(var1_a) .!= :DateTime] == var4 .* 100.0

    @test length(readdir(export_realized_results(results1))) === 6
end

@testset "Test Numerical Stability of Constraints" begin
    template = get_thermal_dispatch_template_network()
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    valid_bounds = (coefficient=(min=1.0, max=1.0), rhs=(min=0.4, max=9.930296584))
    model = DecisionModel(template, c_sys5; optimizer=GLPK_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT

    bounds = PSI.get_constraint_numerical_bounds(model)
    _check_constraint_bounds(bounds, valid_bounds)

    model_bounds = PSI.get_detailed_constraint_numerical_bounds(model)
    valid_model_bounds = Dict(
        :CopperPlateBalanceConstraint__System => (
            coefficient=(min=1.0, max=1.0),
            rhs=(min=6.434489705000001, max=9.930296584),
        ),
        :ActivePowerVariableLimitsConstraint__ThermalStandard__lb =>
            (coefficient=(min=1.0, max=1.0), rhs=(min=Inf, max=-Inf)),
        :ActivePowerVariableLimitsConstraint__ThermalStandard__ub =>
            (coefficient=(min=1.0, max=1.0), rhs=(min=0.4, max=6.0)),
    )
    for (constriant_key, constriant_bounds) in model_bounds
        _check_constraint_bounds(
            constriant_bounds,
            valid_model_bounds[PSI.encode_key(constriant_key)],
        )
    end
end

@testset "Test Numerical Stability of Variables" begin
    template = get_template_basic_uc_simulation()
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5_uc")
    valid_bounds = (min=0.0, max=6.0)
    model = DecisionModel(template, c_sys5; optimizer=GLPK_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == PSI.BuildStatus.BUILT

    bounds = PSI.get_variable_numerical_bounds(model)
    _check_variable_bounds(bounds, valid_bounds)

    model_bounds = PSI.get_detailed_variable_numerical_bounds(model)
    valid_model_bounds = Dict(
        :StopVariable__ThermalStandard => (min=0.0, max=1.0),
        :StartVariable__ThermalStandard => (min=0.0, max=1.0),
        :ActivePowerVariable__ThermalStandard => (min=0.4, max=6.0),
        :OnVariable__ThermalStandard => (min=0.0, max=1.0),
    )
    for (variable_key, variable_bounds) in model_bounds
        _check_variable_bounds(
            variable_bounds,
            valid_model_bounds[PSI.encode_key(variable_key)],
        )
    end
end

@testset "Decision Model initial_conditions test for ThermalGen" begin
    ######## Test with ThermalStandardUnitCommitment ########
    template = get_thermal_standard_uc_template()
    c_sys5_uc = PSB.build_system(PSITestSystems, "c_sys5_pglib"; force_build=true)
    set_device_model!(template, ThermalMultiStart, ThermalStandardUnitCommitment)
    model = DecisionModel(template, c_sys5_uc; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    check_duration_on_initial_conditions_values(model, ThermalStandard)
    check_duration_off_initial_conditions_values(model, ThermalStandard)
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with ThermalMultiStartUnitCommitment ########
    template = get_thermal_standard_uc_template()
    c_sys5_uc = PSB.build_system(PSITestSystems, "c_sys5_pglib"; force_build=true)
    set_device_model!(template, ThermalMultiStart, ThermalMultiStartUnitCommitment)
    model = DecisionModel(template, c_sys5_uc; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT

    check_duration_on_initial_conditions_values(model, ThermalStandard)
    check_duration_off_initial_conditions_values(model, ThermalStandard)
    check_duration_on_initial_conditions_values(model, ThermalMultiStart)
    check_duration_off_initial_conditions_values(model, ThermalMultiStart)
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with ThermalCompactUnitCommitment ########
    template = get_thermal_standard_uc_template()
    c_sys5_uc = PSB.build_system(PSITestSystems, "c_sys5_pglib"; force_build=true)
    set_device_model!(template, ThermalMultiStart, ThermalCompactUnitCommitment)
    set_device_model!(template, ThermalStandard, ThermalCompactUnitCommitment)
    model = DecisionModel(template, c_sys5_uc; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    check_duration_on_initial_conditions_values(model, ThermalStandard)
    check_duration_off_initial_conditions_values(model, ThermalStandard)
    check_duration_on_initial_conditions_values(model, ThermalMultiStart)
    check_duration_off_initial_conditions_values(model, ThermalMultiStart)
    @test solve!(model) == RunStatus.SUCCESSFUL
end

@testset "Decision Model initial_conditions test for Storage" begin
    ######## Test with BookKeeping ########
    template = get_thermal_dispatch_template_network()
    c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat"; force_build=true)
    set_device_model!(template, GenericBattery, BookKeeping)
    model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    check_energy_initial_conditions_values(model, GenericBattery)
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with BatteryAncillaryServices ########
    template = get_thermal_dispatch_template_network()
    c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat"; force_build=true)
    set_device_model!(template, GenericBattery, BatteryAncillaryServices)
    model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    check_energy_initial_conditions_values(model, GenericBattery)
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with EnergyTarget ########
    template = get_thermal_dispatch_template_network()
    c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat_ems"; force_build=true)
    set_device_model!(template, BatteryEMS, EnergyTarget)
    model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    check_energy_initial_conditions_values(model, BatteryEMS)
    @test solve!(model) == RunStatus.SUCCESSFUL
end

@testset "Decision Model initial_conditions test for Hydro" begin
    ######## Test with HydroDispatchRunOfRiver ########
    template = get_thermal_dispatch_template_network()
    c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hyd"; force_build=true)
    set_device_model!(template, HydroDispatch, HydroDispatchRunOfRiver)
    set_device_model!(template, HydroEnergyReservoir, HydroDispatchRunOfRiver)
    model = DecisionModel(template, c_sys5_hyd; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    initial_conditions_data =
        PSI.get_initial_conditions_data(PSI.get_optimization_container(model))
    @test !PSI.has_initial_condition_value(
        initial_conditions_data,
        ActivePowerVariable(),
        HydroEnergyReservoir,
    )
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with HydroCommitmentRunOfRiver ########
    template = get_thermal_dispatch_template_network()
    c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hyd"; force_build=true)
    set_device_model!(template, HydroDispatch, HydroCommitmentRunOfRiver)
    set_device_model!(template, HydroEnergyReservoir, HydroCommitmentRunOfRiver)
    model = DecisionModel(template, c_sys5_hyd; optimizer=HiGHS_optimizer)

    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    initial_conditions_data =
        PSI.get_initial_conditions_data(PSI.get_optimization_container(model))
    @test PSI.has_initial_condition_value(
        initial_conditions_data,
        OnVariable(),
        HydroEnergyReservoir,
    )
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with HydroDispatchReservoirBudget ########
    template = get_thermal_dispatch_template_network()
    c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hyd"; force_build=true)
    set_device_model!(template, HydroEnergyReservoir, HydroDispatchReservoirBudget)
    model = DecisionModel(template, c_sys5_hyd; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    initial_conditions_data =
        PSI.get_initial_conditions_data(PSI.get_optimization_container(model))
    @test !PSI.has_initial_condition_value(
        initial_conditions_data,
        ActivePowerVariable(),
        HydroEnergyReservoir,
    )
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with HydroCommitmentReservoirBudget ########
    template = get_thermal_dispatch_template_network()
    c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hyd"; force_build=true)
    set_device_model!(template, HydroEnergyReservoir, HydroCommitmentReservoirBudget)
    model = DecisionModel(template, c_sys5_hyd; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    initial_conditions_data =
        PSI.get_initial_conditions_data(PSI.get_optimization_container(model))
    @test PSI.has_initial_condition_value(
        initial_conditions_data,
        OnVariable(),
        HydroEnergyReservoir,
    )
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with HydroDispatchReservoirStorage ########
    template = get_thermal_dispatch_template_network()
    c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hyd_ems"; force_build=true)
    set_device_model!(template, HydroEnergyReservoir, HydroDispatchReservoirStorage)
    model = DecisionModel(template, c_sys5_hyd; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    initial_conditions_data =
        PSI.get_initial_conditions_data(PSI.get_optimization_container(model))
    @test !PSI.has_initial_condition_value(
        initial_conditions_data,
        ActivePowerVariable(),
        HydroEnergyReservoir,
    )
    check_energy_initial_conditions_values(model, HydroEnergyReservoir)
    @test solve!(model) == RunStatus.SUCCESSFUL

    ######## Test with HydroCommitmentReservoirStorage ########
    template = get_thermal_dispatch_template_network()
    c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hyd_ems"; force_build=true)
    set_device_model!(template, HydroEnergyReservoir, HydroCommitmentReservoirStorage)
    model = DecisionModel(template, c_sys5_hyd; optimizer=HiGHS_optimizer)
    @test build!(model; output_dir=mktempdir(cleanup=true)) == BuildStatus.BUILT
    initial_conditions_data =
        PSI.get_initial_conditions_data(PSI.get_optimization_container(model))
    @test PSI.has_initial_condition_value(
        initial_conditions_data,
        OnVariable(),
        HydroEnergyReservoir,
    )
    check_energy_initial_conditions_values(model, HydroEnergyReservoir)
    @test solve!(model) == RunStatus.SUCCESSFUL
end

@testset "Test serialization of InitialConditionsData" begin
    sys = PSB.build_system(PSITestSystems, "c_sys5")
    template = get_thermal_standard_uc_template()
    set_service_model!(
        template,
        ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "test"),
    )
    optimizer = GLPK_optimizer

    # Construct and build with default behavior that builds initial conditions.
    model = DecisionModel(template, sys; optimizer=optimizer)
    output_dir = mktempdir(cleanup=true)

    @test build!(model; output_dir=output_dir) == PSI.BuildStatus.BUILT
    ic_file = PSI.get_initial_conditions_file(model)
    test_ic_serialization_outputs(model, ic_file_exists=true, message="make")
    @test solve!(model) == RunStatus.SUCCESSFUL

    # Build again. Initial conditions should be rebuilt.
    PSI.reset!(model)
    @test build!(model; output_dir=output_dir) == PSI.BuildStatus.BUILT
    test_ic_serialization_outputs(model, ic_file_exists=true, message="make")
    @test solve!(model) == RunStatus.SUCCESSFUL

    # Build again, use existing initial conditions.
    model = DecisionModel(
        template,
        sys;
        optimizer=optimizer,
        deserialize_initial_conditions=true,
    )
    @test build!(model; output_dir=output_dir) == PSI.BuildStatus.BUILT
    test_ic_serialization_outputs(model, ic_file_exists=true, message="deserialize")
    @test solve!(model) == RunStatus.SUCCESSFUL

    # Construct and build again with custom initial conditions file.
    initialization_file = joinpath(output_dir, ic_file * ".old")
    mv(ic_file, initialization_file)
    touch(ic_file)
    model = DecisionModel(
        template,
        sys;
        optimizer=optimizer,
        initialization_file=initialization_file,
        deserialize_initial_conditions=true,
    )
    @test build!(model; output_dir=output_dir) == PSI.BuildStatus.BUILT
    test_ic_serialization_outputs(model, ic_file_exists=true, message="deserialize")
    @test solve!(model) == RunStatus.SUCCESSFUL

    # Construct and build again while skipping build of initial conditions.
    rm(ic_file)
    model = DecisionModel(template, sys; optimizer=optimizer, initialize_model=false)
    @test build!(model; output_dir=output_dir) == PSI.BuildStatus.BUILT
    test_ic_serialization_outputs(model, ic_file_exists=false, message="skip")
    @test solve!(model) == RunStatus.SUCCESSFUL

    # Conflicting inputs
    model = DecisionModel(
        template,
        sys;
        optimizer=optimizer,
        initialize_model=false,
        deserialize_initial_conditions=true,
    )
    @test build!(model; output_dir=output_dir, console_level=Logging.AboveMaxLevel) ==
          PSI.BuildStatus.FAILED
    model = DecisionModel(
        template,
        sys;
        optimizer=optimizer,
        initialize_model=false,
        initialization_file="init_file.bin",
    )
    build!(model; output_dir=output_dir, console_level=Logging.AboveMaxLevel) ==
    PSI.BuildStatus.FAILED
end

@testset "Solve with detailed optimizer stats" begin
    c_sys5 = PSB.build_system(PSITestSystems, "c_sys5")
    template = get_thermal_standard_uc_template()
    set_service_model!(
        template,
        ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "test"),
    )
    UC = DecisionModel(
        template,
        c_sys5;
        optimizer=GLPK_optimizer,
        detailed_optimizer_stats=true,
    )
    output_dir = mktempdir(cleanup=true)
    @test build!(UC; output_dir=output_dir) == PSI.BuildStatus.BUILT
    @test solve!(UC) == RunStatus.SUCCESSFUL
    # We only test this field because most free solvers don't support detailed stats
    @test !ismissing(get_optimizer_stats(UC).objective_bound)
end


# Unit tests for implementations/molly/molly_simulator.jl

@testset "molly_simulator.jl" begin
    simulator1 = MollySimulator(400, 10, coupling=Molly.AndersenThermostat(94.4u"K", 0.1u"ps"))
    simulator2 = MollySimulator{Molly.StormerVerlet}(400, 10, t₀=1000, stride=2)
    simulator3 = MollySimulator{Molly.StormerVerlet}(400u"ns", 10)
    simulator4 = MollySimulator(400u"ns", 10, t₀=1000u"ns")

    @test simulator1 isa MollySimulator{Molly.VelocityVerlet,typeof(1u"ħ_au / hartree"),<:Molly.AndersenThermostat}
    @test simulator1.Δt == 400.0u"ħ_au / hartree"
    @test simulator1.steps == 10
    @test simulator1.t₀ == 0.0u"ħ_au / hartree"
    @test simulator1.coupling == Molly.AndersenThermostat(94.4u"K", 0.1u"ps")
    @test simulator1.stride == 1

    @test simulator2 isa MollySimulator{Molly.StormerVerlet,typeof(1u"ħ_au / hartree"),Molly.NoCoupling}
    @test simulator2.Δt == 400.0u"ħ_au / hartree"
    @test simulator2.steps == 10
    @test simulator2.t₀ == 1000.0u"ħ_au / hartree"
    @test simulator2.coupling == Molly.NoCoupling()
    @test simulator2.stride == 2

    @test simulator3 isa MollySimulator{Molly.StormerVerlet,typeof(1u"ns"),Molly.NoCoupling}
    @test simulator3.Δt == 400.0u"ns"
    @test simulator3.steps == 10
    @test simulator3.t₀ == 0.0u"ns"
    @test simulator3.coupling == Molly.NoCoupling()
    @test simulator3.stride == 1

    @test simulator4 isa MollySimulator{Molly.VelocityVerlet,typeof(1u"ns"),Molly.NoCoupling}
    @test simulator4.Δt == 400.0u"ns"
    @test simulator4.steps == 10
    @test simulator4.t₀ == 1000.0u"ns"
    @test simulator4.coupling == Molly.NoCoupling()
    @test simulator4.stride == 1

    particles = [
        AtomsBase.Atom(:Ar, (@SVector [7.0, 7.0, 7.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au"),
        AtomsBase.Atom(:Ar, (@SVector [7.0, 7.0, 21.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au"),
        AtomsBase.Atom(:Ar, (@SVector [7.0, 21.0, 7.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au"),
        AtomsBase.Atom(:Ar, (@SVector [7.0, 21.0, 21.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au"),
        AtomsBase.Atom(:Ar, (@SVector [21.0, 7.0, 7.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au"),
        AtomsBase.Atom(:Ar, (@SVector [21.0, 7.0, 21.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au"),
        AtomsBase.Atom(:Ar, (@SVector [21.0, 21.0, 7.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au"),
        AtomsBase.Atom(:Ar, (@SVector [21.0, 21.0, 21.0])u"bohr", 6e-5(@SVector randn(3))u"bohr * hartree / ħ_au")
    ]
    box = [[28.0, 0.0, 0.0], [0.0, 28.0, 0.0], [0.0, 0.0, 28.0]]u"bohr"
    system = periodic_system(particles, box)

    potential = InteratomicPotentials.LennardJones(1.657e-21u"J", 0.34u"nm", 0.765u"nm", [:Ar])

    result = simulate(system, simulator1, potential)

    @test result isa MollyResult
end


# lidHttpServer.jl
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

using Text
using Base.Test, Stage, Ollam, DataStructures
using HttpServer

filename = "./data/text.tsv"
if size(ARGS, 1) > 0
    filename = ARGS[1]
end

# ----------------------------------------------------------------------
# LID
# ----------------------------------------------------------------------

# train the model

train       = map(l -> split(chomp(l), '\t')[2], filelines(filename))
train_truth = map(l -> split(chomp(l), '\t')[1], filelines(filename))

bkgmodel, fextractor, model = tc_train(train, train_truth, lid_iterating_tokenizer, mincount = 2, cutoff = 1e10,
                                       trainer = (fvs, truth, init_model) -> train_mira(fvs, truth, init_model, iterations = 20, k = 2, C = 0.01, average = true),
                                       iteration_method = :eager)

# ----------------------------------------------------------------------

# function to identify language from text using the model

function detectLang(text)
    fv      = fextractor(lid_iterating_tokenizer(text))
    scores  = score(model, fv)
    bidx, b = best(scores)
    return model.index_class[bidx]
end

# ----------------------------------------------------------------------

# HTTP Server to identify language from PUT Request data

http = HttpHandler() do req::Request, res::Response
    if req.method != "PUT"
        return Response("{\"lang\": \"error - use PUT method\"}")
    end
    text = bytestring(req.data)
    # println(text)
    # println(detectLang(text))
    return Response(string("{\"lang\": \"", detectLang(text), "\"}"))
end
http.events["error"]  = (client, err) -> println(err)
http.events["listen"] = (saddr) -> println("Running on http://$saddr (Press CTRL+C to quit)")

server = Server(http)
run(server, host=IPv4(127,0,0,1), port=8000)


# This file is a part of Julia. License is MIT: https://julialang.org/license

using Test, LinearAlgebra, SparseArrays

@testset "threaded SuiteSparse tests" begin
    A = sprandn(200, 200, 0.2)
    b = rand(200)

    function test(n::Integer)
        _A = A[1:n, 1:n]
        _b = b[1:n]
        x = qr(_A) \ _b
        return norm(x)
    end

    res_threads = zeros(100)
    Threads.@threads for i in 1:100
        res_threads[i] = test(i + 100)
    end

    @test res_threads ≈ [test(i + 100) for i in 1:100]
end


@testset "Embedder: build, embed" begin
    cfg_symbol = CONFIG_FUNCTIONS[1]
	cfg = mktemp() do path, io  # write and parse config file on-the-fly
               write(io, eval(cfg_symbol)())
			   flush(io)
			   parse_configuration(path)
		   end
    dbdata = cfg.data_loader()
    _embeddable_fields = cfg.embedder_configs[1].embeddable_fields
    _embedder_kwarguments = Dict{Symbol,Any}()
    T = eval(cfg.vectors_eltype)
    entries = [(field1=1000, field2="2000"), (field1="not_embeddable",)]  # 2 documents
    fields = [:field1, :field2]

    for _vectors in [:count, :tf, :tfidf, :bm25]
        for _vectors_transform in [:none, :rp, :lsa]
            for _vectors_dimension in [5, 1000]
                for _oov_policy in [:none, :large_vector]
                    config = (id="id",
                              description="",
                              language="english",
                              stem_words=false,
                              ngram_complexity=1,
                              vectors=_vectors,
                              vectors_transform=_vectors_transform,
                              vectors_dimension=_vectors_dimension,
                              #embeddings_path=_embeddings_path,
                              #embeddings_kind=_embeddings_kind,
                              #doc2vec_method=_doc2vec_method,
                              #glove_vocabulary=_glove_vocabulary,
                              oov_policy=_oov_policy,
                              embedder_kwarguments=_embedder_kwarguments,
                              embeddable_fields=_embeddable_fields,
                              text_strip_flags=UInt32(0),
                              #sif_alpha=_sif_alpha,
                              #borep_dimension=_borep_dimension,
                              #borep_pooling_function=_borep_pooling_function,
                              #disc_ngram=_disc_ngram
                             )
                    embedder = Garamond.build_embedder(dbdata, config; vectors_eltype=T, id_key=cfg.id_key)
                    @test embedder isa AbstractEmbedder{T}
                    embedded, isembedded = embed(embedder, entries; fields=fields)
                    @test embedded isa SparseMatrixCSC{T,Int}
                    @test isembedded isa BitArray
                    @test isembedded[1] == true && isembedded[2] == false
                end
            end
        end
    end

    root_path = "/" * joinpath(split(pathof(Garamond), "/")[1:end-2]...)
    VECTORS = [:word2vec, :word2vec, :glove, :conceptnet, :compressed, :compressed]
    EMBEDDINGS_KINDS = [:binary, :text, :text, :text, :binary, :text]
    EMBEDDINGS_PATHS = [joinpath(root_path, "test", "embeddings", "word2vec", "sample_model.bin"),
                        joinpath(root_path, "test", "embeddings", "word2vec", "sample_model.txt"),
                        joinpath(root_path, "test", "embeddings", "glove", "sample_model.txt"),
                        joinpath(root_path, "test", "embeddings", "conceptnet", "sample_model.txt"),
                        joinpath(root_path, "test", "embeddings", "compressed", "sample_model.bin"),
                        joinpath(root_path, "test", "embeddings", "compressed", "sample_model.txt")]
    for (_vectors, _embeddings_path, _embeddings_kind) in zip(VECTORS, EMBEDDINGS_PATHS, EMBEDDINGS_KINDS)
        for _doc2vec_method in [:boe, :sif, :borep, :cpmean, :disc]
            for _oov_policy in [:none, :large_vector]
                config = (id="id",
                          description="",
                          language="english",
                          stem_words=false,
                          #ngram_complexity=1,
                          vectors=_vectors,
                          #vectors_transform=_vectors_transform,
                          #vectors_dimension=_vectors_dimension,
                          embeddings_path=_embeddings_path,
                          embeddings_kind=_embeddings_kind,
                          doc2vec_method=_doc2vec_method,
                          glove_vocabulary="",
                          oov_policy=_oov_policy,
                          embedder_kwarguments=_embedder_kwarguments,
                          embeddable_fields=_embeddable_fields,
                          text_strip_flags=UInt32(0),
                          sif_alpha=Garamond.DEFAULT_SIF_ALPHA,
                          borep_dimension=Garamond.DEFAULT_BOREP_DIMENSION,
                          borep_pooling_function=Garamond.DEFAULT_BOREP_POOLING_FUNCTION,
                          disc_ngram=Garamond.DEFAULT_DISC_NGRAM
                         )
                embedder = Garamond.build_embedder(dbdata, config; vectors_eltype=T, id_key=cfg.id_key)
                @test embedder isa AbstractEmbedder{T}
                embedded, isembedded = embed(embedder, entries; fields=fields)
                @test embedded isa Matrix{T}
                @test Garamond.dimensionality(embedder) == size(embedded, 1)
                @test isembedded isa BitArray
                @test isembedded[1] == true && isembedded[2] == false
            end
        end
    end
end


# TODO: move plot resonance to NMRSpectraSimulator

import NMRDataSetup
import NMRSpectraSimulator
import NMRSpecifyRegions

include("../src/NMRCalibrate.jl")
import .NMRCalibrate

using LinearAlgebra
using FFTW
import PyPlot
import BSON
import JLD

#import Clustering
import Statistics
import Random

include("./helpers/final_helpers.jl")
include("./helpers/solute_calibrate.jl")
include("./helpers/loop_entries1.jl")

PyPlot.close("all")
fig_num = 1

Random.seed!(25)
PyPlot.matplotlib["rcParams"][:update](["font.size" => 22, "font.family" => "serif"])

max_iters = 50000
#max_iters = 5

# dummy_SSFID = NMRSpectraSimulator.SpinSysFIDType1(0.0)
# project_name = "test_serine1"
# molecule_names = ["L-Serine";]
# w = [1.0; ]

dummy_SSFID = NMRSpectraSimulator.SpinSysFIDType1(0.0)
project_name = "test_glucose1"
molecule_names = ["D-(+)-Glucose";]
w = [1.0; ]

# dummy_SSFID = NMRSpectraSimulator.SpinSysFIDType1(0.0)
# project_name = "D-(+)-Glucose-700-r3"
# molecule_names = ["D-(+)-Glucose";]
# w = [1.0; ]

# dummy_SSFID = NMRSpectraSimulator.SpinSysFIDType2(0.0)
# project_name = "test_glucose2"
# molecule_names = ["D-(+)-Glucose";]
# w = [1.0; ]

# project_name = "D-(+)-Glucose-NRC-600"
# molecule_names = ["D-(+)-Glucose";]
# w = [1.0; ]
#
# project_name = "L-Serine-700"
# molecule_names = ["L-Serine"; "DSS"]
# w = [20.0/46; 1.0] # BMRB: DSS is 1 % => 46 mM
#
# project_name = "D-(+)-Glucose-700"
# molecule_names = ["D-(+)-Glucose"; "DSS"]
# w = [20.0/4.6; 1.0] # BMRB: DSS is 0.1 % => 4.6 mM
#
# project_name = "L-Isoleucine-700"
# molecule_names = ["L-Isoleucine"; "DSS"]
# w = [20.0/46; 1.0] # BMRB: DSS is 1 % => 46 mM
#
# project_name = "L-Leucine-500-2mM"
# molecule_names = ["L-Leucine";]
# w = [1.0]
#
# project_name = "L-Glutamine-700"
# molecule_names = ["L-Glutamine"; "DSS"]
# w = [20.0/0.5; 1.0] # BMRB: DSS is 500 uM => 0.5 mM

projects_dir = "/home/roy/MEGAsync/outputs/NMR/calibrate/final"
base_path_JLD = "/home/roy/Documents/repo/NMRData//src/input/molecules"
cs_config_path = "/home/roy/Documents/repo/NMRData/src/input/reduced_cs_config.txt"

println("Now on $(project_name)")

PyPlot.close("all")
fig_num = 1

Random.seed!(25)
PyPlot.matplotlib["rcParams"][:update](["font.size" => 22, "font.family" => "serif"])

### user inputs.
# 0.1% DSS is 0.0046 M = 4.6 mM.
save_folder_path = joinpath(projects_dir, project_name)
#@assert 1==2

w = w ./ norm(w) # since the fit data, y, is normalized.

# proxy-related.
tol_coherence = 1e-2
α_relative_threshold = 0.05
#Δc_partition_radius = 1e-1 # 17 element partition for glucose spin group 2.
Δc_partition_radius_candidates = [1e-1; 0.3; 0.5; 0.7; 0.8; 0.9]
#Δc_partition_radius_candidates = [0.9;]
λ0 = 3.4
Δcs_max = 0.2 # for proxy.
κ_λ_lb = 0.5
κ_λ_ub = 2.5

# if a Δc_partition_radius candidate gives max partition sizes less than of
#   equal to this value, then use that candidate.
early_exit_part_size = 7

### end inputs.

## load data.
load_folder_path = joinpath(projects_dir, project_name)
load_path = joinpath(load_folder_path, "$(project_name).bson")
dic = BSON.load(load_path)
s_t = dic[:s_t]
fs = dic[:fs]
SW = dic[:SW]
ν_0ppm = dic[:ν_0ppm]

hz2ppmfunc = uu->(uu - ν_0ppm)*SW/fs
ppm2hzfunc = pp->(ν_0ppm + pp*fs/SW)

offset_Hz = ν_0ppm - (ppm2hzfunc(0.3)-ppm2hzfunc(0.0))

N = length(s_t)
DFT_s = fft(s_t)
U_DFT, U_y, U_inds = NMRDataSetup.getwraparoundDFTfreqs(N, fs, offset_Hz)

Z = maximum(abs.(DFT_s))
y = DFT_s[U_inds] ./ Z

#
S_U = DFT_s[U_inds]
P_y = hz2ppmfunc.(U_y)

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P_y, real.(S_U), label = "data spectrum")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("data spectra")

####### mixture proxy.
Δcs_max_mixture = collect( Δcs_max for i = 1:length(molecule_names))

println("Timing: trydiffΔcradius()")
@time mixture_params, Δc_partition_radius = NMRCalibrate.trydiffΔcradius(Δc_partition_radius_candidates,
    molecule_names, base_path_JLD, Δcs_max_mixture, hz2ppmfunc, ppm2hzfunc,
    fs, SW, λ0, ν_0ppm, early_exit_part_size, Δcs_max, tol_coherence,
    α_relative_threshold, dummy_SSFID)
As = mixture_params

ΩS_ppm = NMRSpectraSimulator.getPsnospininfo(As, hz2ppmfunc)
ΩS_ppm_sorted = sort(NMRSpectraSimulator.combinevectors(ΩS_ppm))

println("$(project_name): Partition sizes:")
display(NMRCalibrate.displaypartitionsizes(As[1]))
println("Δc_partition_radius = ", Δc_partition_radius)
println()

#@assert 1==2

# u_min = ppm2hzfunc(-0.5)
# u_max = ppm2hzfunc(3.0)

u_offset = 0.5
u_min = ppm2hzfunc(ΩS_ppm_sorted[1] - u_offset)
u_max = ppm2hzfunc(ΩS_ppm_sorted[end] + u_offset)

println("Timing: fitproxies!()")
@time NMRSpectraSimulator.fitproxies!(As;
κ_λ_lb = κ_λ_lb,
κ_λ_ub = κ_λ_ub,
u_min = u_min,
u_max = u_max,
Δr = 1.0,
Δκ_λ = 0.05)

######## end mixture proxy.

N_d = sum( NMRCalibrate.getNd(As[n]) for n = 1:length(As) )
#N_β = sum( NMRCalibrate.getNβ(As[n]) for n = 1:length(As) )
N_λ = sum( NMRCalibrate.getNλ(As[n]) for n = 1:length(As) )

####### cost func.
combinevectors = NMRSpectraSimulator.combinevectors

Δsys_cs, y_cost_all, U_cost_all, P_cost_all, exp_info, cost_inds,
cost_inds_set = NMRCalibrate.prepareoptim(cs_config_path, molecule_names, hz2ppmfunc,
U_y, y, As; region_min_dist = 0.1)

# visualize cost regions.
# sort(P_cost_set[1]) # etc..
P_cost_set = collect( P_y[cost_inds_set[r]] for r = 1:length(cost_inds_set) )

### optim all regions. # 900 secs.
y_cost = y_cost_all
U_cost = U_cost_all
P_cost = P_cost_all

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P_y, real.(y), label = "data spectrum")
PyPlot.plot(P_cost, real.(y_cost), "^", label = "positions")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("positions against data spectrum, real part")

#@assert 1==2

Δ_shifts = NMRSpectraSimulator.combinevectors(Δsys_cs)

##### set up updates.

P = LinRange(hz2ppmfunc(u_min), hz2ppmfunc(u_max), 50000)
U = ppm2hzfunc.(P)
U_rad = U .* (2*π)
#ΩS_ppm = collect( hz2ppmfunc.( NMRSpectraSimulator.combinevectors(A.Ωs) ./ (2*π) ) for A in mixture_params )

## parameters that affect qs.
# A.ss_params.d, A.ss_params.κs_λ, A.ss_params.κs_β
# A.d_singlets, A.αs_singlets, A.Ωs_singlets, A.β_singlets, A.λ0, A.κs_λ_singlets
# purposely perturb κ.

Es = collect( NMRSpectraSimulator.κCompoundFIDType(As[i]) for i = 1:length(As) )

κ_lb_default = 0.2
κ_ub_default = 50.0

# TODO here, change shift delta cs to each group.

# default, type2.
γ = 0.001 # in ppm.
shift_constants = (Δsys_cs, γ)

# type1.
if typeof(As) == Vector{NMRSpectraSimulator.CompoundFIDType{Float64, NMRSpectraSimulator.SpinSysFIDType1{Float64}}}
    shift_constants = Δ_shifts
end

r = 3 # for glucose nrc.

if project_name == "test_serine1"
    r = 1 # for serine 700.
end
y_cost = y[cost_inds_set[r]]
U_cost = U_y[cost_inds_set[r]]
P_cost = P_y[cost_inds_set[r]]

U_rad_cost = U_cost .* (2*π)

###
LS_inds = 1:length(U_cost)

println("Timing: runalignment(), r = $(r)")
@time p_star, q, κ_BLS, getshiftfunc, getβfunc, getλfunc,
obj_func, N_vars_set, p_initial = NMRCalibrate.runalignment(shift_constants,
U_rad_cost, y_cost, LS_inds, Es, As, fs, SW;
max_iters = max_iters,
xtol_rel = 1e-5,
ftol_rel = 1e-5,
w = w,
κ_lb_default = κ_lb_default,
κ_ub_default = κ_ub_default,
λ_each_lb = 0.9,
λ_each_ub = 1.2)

p_star
κ_BLS
q

d_star = getshiftfunc(p_star)
β_star = getβfunc(p_star)
λ_star = getλfunc(p_star)

###### visualize.
T = Float64

import PlotlyJS
import Plots
Plots.plotly()

projects_dir = "/home/roy/MEGAsync/outputs/NMR/calibrate/final/$(project_name)"
plots_save_folder = joinpath(projects_dir, "plots")
isdir(plots_save_folder) || mkdir(plots_save_folder)
project_title = "test" # TODO change later.

# prep initial.
initial_cost = obj_func(p_initial)
NMRCalibrate.parseκ!(Es, ones(T, length(κ_BLS)))
fill!(w, 1.0)
q_initial_U = q.(U_rad)
#println("norm(q_initial_U) = ", norm(q_initial_U))

final_cost = obj_func(p_star)
q_final_U = q.(U_rad)

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P_y, real.(y), label = "y")
PyPlot.plot(P_cost, real.(y_cost), "x")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("r = $(r). cost vs. data (y)")

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P, real.(q_initial_U), label = "q initial")
PyPlot.plot(P_y, real.(y), label = "y")
PyPlot.plot(P, real.(q_final_U), "--", label = "q final")
#PyPlot.plot(P_cost, real.(y_cost), "x")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("r = $(r). data (y) vs. fit")

plots_save_path = joinpath(plots_save_folder, "region_$(r)_real.html")
title_string = "$(project_title): region $(r), real"
savefigfitresult(plots_save_path, title_string,
 real.(q_final_U), P, P_cost, real.(y_cost);
initial_fit = real.(q_initial_U))

plots_save_path = joinpath(plots_save_folder, "region_$(r)_imaginary.html")
title_string = "$(project_title): region $(r), imaginary"
savefigfitresult(plots_save_path, title_string,
imag.(q_final_U), P, P_cost, imag.(y_cost);
initial_fit = imag.(q_initial_U))

plots_save_path = joinpath(plots_save_folder, "region_$(r)_modulus.html")
title_string = "$(project_title): region $(r), modulus"
savefigfitresult(plots_save_path, title_string,
abs.(q_final_U), P, P_cost, abs.(y_cost);
initial_fit = abs.(q_initial_U))

### save.
save_path = joinpath(save_folder_path, "results_$(r).bson")
BSON.bson(save_path,
p_star = p_star,
κ_lb_default = κ_lb_default,
κ_ub_default = κ_ub_default,
κ_star = κ_BLS,
d_star = d_star,
β_star = β_star,
λ_star = λ_star,
w = w,
# proxy setup-related below.
Δc_partition_radius = Δc_partition_radius,
tol_coherence = tol_coherence,
α_relative_threshold = α_relative_threshold,
λ0 = λ0,
Δcs_max = Δcs_max,
κ_λ_lb = κ_λ_lb,
κ_λ_ub = κ_λ_ub,
# experiement/cost-related below.
cost_inds_set = cost_inds_set,
Δsys_cs = Δsys_cs,
y_cost = y_cost,
U_cost = U_cost,
P_cost = P_cost,
As = As,
Es = Es,
y = y,
U_y = U_y,
fs = fs,
SW = SW,
ν_0ppm = ν_0ppm)


# using AmbulanceDeployment, Dates
#
# Pkg.add("Query")
# using Query
using AmbulanceDeployment

struct EMSEngine{T}
    eventlog::DataFrame
    eventqueue::PriorityQueue{T,Int,Base.Order.ForwardOrdering}
    shortqueue::PriorityQueue{T,Int,Base.Order.ForwardOrdering}
    guiArray::Array{Any, 1}
end

struct gui_event
    #call made / call responded / ambulance arrived / shortfall
    event_type::String
    #neighborhood where call is made from
    neighborhood_id::Int
    # station where call is made from
    deployment_id::Int
    # id of the event generated
    event_id::Int
    ambulance_id::Int
    arrival_time::Int
    remaining_amb::Int
    # current timestamp of event
    timestamp::DateTime
end

## generates the priority queue and instantiates the EMSEngine struct
function generateCalls(problem::DispatchProblem)
    ncalls = nrow(problem.emergency_calls)
    eventlog = DataFrame(
        id = 1:ncalls,
        dispatch_from = zeros(Int, ncalls),
        waittime = fill(0.0, ncalls),
        responsetime = fill(Inf, ncalls),
        scenetime = fill(Inf, ncalls),
        conveytime = fill(Inf, ncalls),
        returntime = fill(Inf, ncalls),
        return_to = zeros(Int, ncalls),
        return_type = fill(:station, ncalls),
        hospital = zeros(Int, ncalls),
        ambulance = zeros(Int, ncalls)
    )
    eventqueue = PriorityQueue{Tuple{Symbol,Int,Int,Int},Int}()
    shortqueue = PriorityQueue{Tuple{Symbol,Int,Int,Int},Int}()
    guiArray = Any[]
    for i in 1:nrow(problem.emergency_calls)
        t = problem.emergency_calls[i, :arrival_seconds]
        enqueue!(eventqueue, (:call, i, t, problem.emergency_calls[i, :neighborhood]), t)
    end
    EMSEngine{Tuple{Symbol,Int,Int,Int}}(eventlog, eventqueue, shortqueue, guiArray) ## returns the ems engine struct
end

function simulate_events!(problem::DispatchProblem, dispatch::ClosestDispatch)
    engine = generateCalls(problem)
    while(~isempty(engine.eventqueue))
        (event, id, t, value) = dequeue!(engine.eventqueue)

        if event == :call
            # if the event is reachable then change event to station2call
            # if not it is a shortfall
            println("calling event id: $id time: $t value: $value")
            call_event!(engine, problem, dispatch, id, t, value)

        elseif event == :station2call
            #once the ambulance arrives at the station change event to call2hospital
            println("Ambulance has arrived on the scene for event $id at time $t")
            station2call_event!(engine, problem, dispatch, id, t, value)
        elseif event == :call2hospital
            #once the ambulance arrives at the hospital it will hand over the patient and then
            #change the deploy back to its station
            println("Ambulance for event $id has arrived at the hospital at time $t")
            call2hosp_event!(engine, problem, dispatch, id, t, value)

        elseif event == :hospital2station
            #once the ambulance drops off the patient it will reroute to the station
            println("Ambulance for event $id is returning to the station at $t")
            return_event!(engine, problem, dispatch, id, t, value)

        elseif event == :done
            #increase the number of ambulances at that station now that it is done
            done_event!(engine, problem, dispatch, id, t, value)

        end
    end
    println("This is the Short Queue")
    while(~isempty(engine.shortqueue))
       (event, id, t, value) = dequeue!(engine.shortqueue)
        print("$event, $id, $t, $value")
        print("This is not empty")
    end
    engine.eventlog , engine.guiArray
end

function call_event!(
        ems::EMSEngine,
        problem::DispatchProblem,
        dispatch::DispatchModel,
        id::Int, # the id of the emergency call
        t::Int, # the time of the emergency call
        nbhd::Int; # the neighborhood the call is from
    )

    #check if there is an ambulance within the coverage matrix
    if sum(problem.deployment[problem.coverage[nbhd,:]]) == 0
        @assert false "$id: no ambulance reachable for call at $nbhd"
    #check if one of the ambulances within the coverage is available
    elseif sum(problem.available[problem.coverage[nbhd,:]]) > 0
        i = closest_available(dispatch, id, problem)
        @assert i > 0 "$id: dispatch from $i to nbhd $nbhd" # assume valid i (enforced by <if> condition)
        update_ambulances!(dispatch, i, -1)
        ems.eventlog[id, :dispatch_from] = i
        @assert problem.available[i] >= 0

        travel_time = ceil(Int, 60*problem.emergency_calls[id, Symbol("stn$(i)_min")])
        @assert travel_time >= 0
        if(t != problem.emergency_calls[id, :arrival_seconds])
            print("This is a shortfall")
        end
        ems.eventlog[id, :responsetime] = (travel_time + t - problem.emergency_calls[id,:arrival_seconds]) / 60 # minutes

        amb = respond_to!(dispatch, i, t)
        ems.eventlog[id, :ambulance] = amb

        event = gui_event("call responded", problem.emergency_calls[id, :neighborhood], i, id, amb,-1, problem.available[i] ,Dates.now())
        num_ambulances_array = Array{Integer}(undef, size(problem.available, 1))
        for i in 1:size(problem.available, 1)
            num_ambulances_array[i] = problem.available[i]
        end
        push!(ems.guiArray,event)
        push!(ems.guiArray,num_ambulances_array)
        enqueue!(ems.eventqueue, (:station2call, id, t + travel_time, amb), t + travel_time)
    #else queue it
    else
        event = gui_event("shortfall", problem.emergency_calls[id, :neighborhood], -1, id, -1,-1,-1 ,Dates.now())
        push!(ems.guiArray, event)
        enqueue!(ems.shortqueue, (:call, id, t, problem.emergency_calls[id, :neighborhood]), t)
        println(id, ": call from ", nbhd, " is a shortfall")
    end
end

function station2call_event!(
        ems::EMSEngine,
        problem::DispatchProblem,
        dispatch::DispatchModel,
        id::Int, # the id of the emergency call
        t::Int, # the time of the emergency call
        amb::Int
    )
    arriveatscene!(dispatch, amb, t)
    # time the ambulance spends at the scene
    scene_time = ceil(Int,60*0.4*rand(problem.turnaround)) # 60sec*0.4*mean(40minutes) ~ 15minutes
    ems.eventlog[id, :scenetime] = scene_time / 60 # minutes
    @assert scene_time > 0

    event = gui_event("ambulance arrived", problem.emergency_calls[id, :neighborhood], -1, id, amb, ceil(ems.eventlog[id, :responsetime]*60) ,-1 ,Dates.now())
    push!(ems.guiArray, event)
    enqueue!(ems.eventqueue, (:call2hospital, id, t + scene_time, amb), t + scene_time)
end

function call2hosp_event!(
        ems::EMSEngine,
        problem::DispatchProblem,
        dispatch::DispatchModel,
        id::Int, # the id of the emergency call
        t::Int, # the time of the emergency call
        amb::Int
    )
    h = let mintime = Inf, minindex = 0
        for h in 1:nrow(problem.hospitals)
            traveltime = problem.emergency_calls[id, Symbol("hosp$(h)_min")]
            if !isna(traveltime) && !(traveltime=="NA") && traveltime < mintime
                @assert traveltime >= 0
                minindex = h; mintime = traveltime
            end
        end
        minindex
    end
    @assert h != 0
    dispatch.hospital[amb] = ems.eventlog[id, :hospital] = h
    conveytime = 60*15 + ceil(Int, 60*problem.emergency_calls[id, Symbol("hosp$(h)_min")]) # ~20minutes
    ems.eventlog[id, :conveytime] = conveytime / 60 # minutes
    @assert conveytime >= 0 conveytime
    arriveathospital!(dispatch, amb, h, t)
    event = gui_event("ambulance hospital", problem.emergency_calls[id, :neighborhood], -1, id, amb, t,-1 ,Dates.now())
    push!(ems.guiArray, event)

    enqueue!(ems.eventqueue, (:hospital2station, id, t+conveytime, amb), t+conveytime)
end

function return_event!(
        ems::EMSEngine,
        problem::DispatchProblem,
        dispatch::DispatchModel,
        id::Int,
        t::Int,
        amb::Int
    )
    stn = returning_to!(dispatch, amb, t)
    h = dispatch.hospital[amb]
    returntime = ceil(Int,60*2*problem.hospitals[h, Symbol("stn$(stn)_min")]) # ~ 10minutes
    dispatch.hospital[amb] = 0
    ems.eventlog[id, :returntime] = returntime / 60 # minutes
    @assert returntime >= 0 returntime
    t_end = t + returntime
    event = gui_event("ambulance returning", problem.emergency_calls[id, :neighborhood], -1, id, amb, t,-1 ,Dates.now())
    push!(ems.guiArray, event)

    enqueue!(ems.eventqueue, (:done, id, t_end, amb), t_end)
end

function done_event!(
        ems::EMSEngine,
        problem::DispatchProblem,
        dispatch::DispatchModel,
        id::Int,
        t::Int,
        amb::Int
    )
    i = dispatch.assignment[amb]
    println("Ambulance from event $id has returned to station $i")
    @assert i > 0 "$id: dispatch from $i to nbhd $nbhd" # assume valid i (enforced by <if> condition)
    update_ambulances!(dispatch, i, 1)
    push!(dispatch.ambulances[i], amb)
    dispatch.status[amb] = :available
    ems.eventlog[id, :return_to] = id
    @assert problem.available[i] > 0
    event = gui_event("ambulance returned", problem.emergency_calls[id, :neighborhood], -1, id, amb, t,-1 ,Dates.now())
    push!(ems.guiArray, event)

    while(~isempty(ems.shortqueue))
        # record time difference
        (event, short_id, short_t, value) = dequeue!(ems.shortqueue)
        enqueue!(ems.eventqueue, (event, short_id, t , value), short_t)
    end
end


    # Functions and methods for cheme::Cascade.HollowIonScheme computations

    """
    `Cascade.computeSteps(scheme::Cascade.HollowIonScheme, comp::Cascade.Computation, stepList::Array{Cascade.Step,1})`
        ... computes in turn all the requested (decay) transition amplitudes as well as AutoIonization.Line's, etc. for all
            pre-specified decay steps of the cascade. When compared with standard computations of these atomic
            processes, however, the amount of output is largely reduced and often just printed into the summary file.
            A set of  data::Cascade.DrRateData  is returned.
    """
    function computeSteps(scheme::Cascade.HollowIonScheme, comp::Cascade.Computation, stepList::Array{Cascade.Step,1})
        linesA = AutoIonization.Line[];    linesR = PhotoEmission.Line[];    linesC = ElectronCapture.Line[];    cOrbitals = Dict{Subshell, Orbital}()
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        nt = 0;   st = 0;   previousMeanEn = 0.
        for  step  in  stepList
            st = st + 1
            nc = length(step.initialMultiplet.levels) * length(step.finalMultiplet.levels)
            sa = "\n  $st) Perform $(string(step.process)) amplitude computations for up to $nc decay lines (without selection rules): "
            println(sa);    if  printSummary   println(iostream, sa)   end

            if      step.process == Basics.ElecCapture()
                # Compute continuum orbitals independently for all transitions in the given block.
                newLines = DielectronicCapture.computeLinesCascade(step.finalMultiplet, step.initialMultiplet, comp.nuclearModel, comp.grid,
                                                               step.settings, output=true, printout=false)
                append!(linesC, newLines);    nt = length(linesC)
            elseif  step.process == Basics.Auger()
                # Compute continuum orbitals independently for all transitions in the given block.
                newLines = AutoIonization.computeLinesCascade(step.finalMultiplet, step.initialMultiplet, comp.nuclearModel, comp.grid,
                                                              step.settings, output=true, printout=false)
                append!(linesA, newLines);    nt = length(linesA)
            elseif  step.process == Basics.Radiative()
                newLines = PhotoEmission.computeLinesCascade(step.finalMultiplet, step.initialMultiplet, comp.grid,
                                                             step.settings, output=true, printout=false)
                append!(linesR, newLines);    nt = length(linesR)
            else   error("Unsupported atomic process for cascade computations.")
            end
            sa = "     Step $st:: A total of $(length(newLines)) $(string(step.process)) lines are calculated, giving now rise " *
                 "to a total of $nt $(string(step.process)) decay lines."
            println(sa);    if  printSummary   println(iostream, sa)   end
        end
        #
        data = Cascade.DecayData(linesR, linesA)
    end

    """
    `Cascade.determineSteps(scheme::Cascade.HollowIonScheme, comp::Cascade.Computation, capturedList::Array{Cascade.Block,1})`
        ... determines all step::Cascade.Step's that need to be computed for this decay cascade. It cycles through all decay processes
            of the given scheme and selects all pairs of blocks due to the selected cascade approach. It checks that at least
            on pair of levels supports a decay within the step. A stepList::Array{Cascade.Step,1} is returned, and for which subsequently
            all required transition amplitudes and rates/cross sections are computed.
    """
    function determineSteps(scheme::Cascade.HollowIonScheme, comp::Cascade.Computation, capturedList::Array{Cascade.Block,1})
        stepList = Cascade.Step[]
        if  comp.approach  in  [Cascade.AverageSCA(), Cascade.SCA()]
            @warn("Transition within a cascade block are not yet considered.")
            for  (ia, blocka) in enumerate(capturedList)
                for  (ib, blockb) in enumerate(capturedList)
                    if   ia == ib   continue     end
                    for  process  in  comp.scheme.processes
                        if      process == Basics.Radiative()
                            if  blocka.NoElectrons == blockb.NoElectrons   &&
                                Basics.determineMeanEnergy(blocka.multiplet) - Basics.determineMeanEnergy(blockb.multiplet) > 0.
                                @show  Basics.determineMeanEnergy(blocka.multiplet) - Basics.determineMeanEnergy(blockb.multiplet)
                                settings = PhotoEmission.Settings(PhotoEmission.Settings(); multipoles=scheme.multipoles)
                                push!( stepList, Cascade.Step(process, settings, blocka.confs, blockb.confs, blocka.multiplet, blockb.multiplet) )
                            end
                        end
                    end
                end
            end
            #
        else  error("Unsupported cascade approach.")
        end
        return( stepList )
    end

    """
    `Cascade.generateBlocks(scheme::Cascade.HollowIonScheme, comp::Cascade.Computation, confs::Array{Configuration,1}; printout::Bool=true)`
        ... generate all block::Cascade.Block's, that need to be computed for this electron-capture and subsequent stabilization (DR) cascade,
            and compute also the corresponding multiplets. The different cascade approches realized different strategies how these block are
            selected and computed. A blockList::Array{Cascade.Block,1} is returned.
    """
    function generateBlocks(scheme::Cascade.HollowIonScheme, comp::Cascade.Computation, confs::Array{Configuration,1}; printout::Bool=true)
        blockList = Cascade.Block[]
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        #
        if    comp.approach == AverageSCA()
            sa = "\n* Generate blocks for hollow-ion cascade computations: \n" *
                 "\n  In the cascade approach $(comp.approach), the following assumptions/simplifications are made: " *
                 "\n    + orbitals are generated independently for each block for a Dirac-Fock-Slater potential; " *
                 "\n    + all blocks (multiplets) are generated from single-CSF levels and without any configuration mixing even in the SC; " *
                 "\n    + only the Coulomb interaction is considered for the electron capture. " *
                 "\n    + only E1 excitations are considered for the stabilization. \n"
            if  printout       println(sa)              end
            if  printSummary   println(iostream, sa)    end
            #
            for  confa  in confs
                print("  Multiplet computations for $(string(confa)[1:end])   with $(confa.NoElectrons) electrons ... ")
                if  printSummary   println(iostream, "\n*  Multiplet computations for $(string(confa)[1:end])   with $(confa.NoElectrons) electrons ... ")   end
                    ##x @show confa, comp.asfSettings
                    basis     = Basics.performSCF([confa], comp.nuclearModel, comp.grid, comp.asfSettings; printout=false)
                    multiplet = Basics.perform("computation: mutiplet from orbitals, no CI, CSF diagonal", [confa],  basis.orbitals,
                                               comp.nuclearModel, comp.grid, comp.asfSettings; printout=false)
                push!( blockList, Cascade.Block(confa.NoElectrons, [confa], true, multiplet) )
                println("and $(length(multiplet.levels[1].basis.csfs)) CSF done. ")
            end
        elseif    comp.approach == SCA()
            sa = "\n* Generate blocks for hollow-ion cascade computations: \n" *
                 "\n  In the cascade approach $(comp.approach), the following assumptions/simplifications are made: " *
                 "\n    + each single configuration forms an individual cascade block; " *
                 "\n    + orbitals are generated independently for each block for a Dirac-Fock-Slater potential; " *
                 "\n    + configuration mixing is included for each block, based on H^(DC); " *
                 "\n    + all requested multipoles are considered for the stabilization. \n"
            if  printout       println(sa)              end
            if  printSummary   println(iostream, sa)    end
            #
            for  confa  in confs
                print("  Multiplet computations for $(string(confa)[1:end])   with $(confa.NoElectrons) electrons ... ")
                if  printSummary   println(iostream, "\n*  Multiplet computations for $(string(confa)[1:end])   with $(confa.NoElectrons) electrons ... ")   end
                    basis     = Basics.performSCF([confa], comp.nuclearModel, comp.grid, comp.asfSettings; printout=false)
                    multiplet = Basics.performCI(basis,    comp.nuclearModel, comp.grid, comp.asfSettings; printout=false)
                push!( blockList, Cascade.Block(confa.NoElectrons, [confa], true, multiplet) )
                println("and $(length(multiplet.levels[1].basis.csfs)) CSF done. ")
            end
        else  error("Unsupported cascade approach.")
        end

        return( blockList )
    end

    """
    `Cascade.generateConfigurationsForDielectronicCapture(multiplets::Array{Multiplet,1},  scheme::HollowIonScheme,
                                                      nm::Nuclear.Model, grid::Radial.Grid)`
        ... generates all possible doubly-excited configurations due to (dielectronic) electron capture into the given multiplets.
            The number and type of such doubly-generated configurations depend on (1) the maximum (electron) energy for capturing an electron
            that is closely related to the (maximum) temperature of the plasma; (2) the fromShells from which (and how many displacements)
            are accepted as well as (3) the maximum principle and orbital angular quantum number of the additional (to-) shellsthe fromShells
            into which electrons excited and/or captured. A Tuple(initialConfList::Array{Configuration,1}, confList::Array{Configuration,1})
            is returned.
    """
    function generateConfigurationsForDielectronicCapture(multiplets::Array{Multiplet,1},  scheme::HollowIonScheme,
                                                      nm::Nuclear.Model, grid::Radial.Grid)
        # Determine all (reference) configurations from multiplets and generate the 'excited' configurations due to the specificed excitations
        initialConfList = Configuration[]
        for mp  in  multiplets
            confList = Basics.extractNonrelativisticConfigurations(mp.levels[1].basis)
            for  conf in confList   if  conf in initialConfList   nothing   else   push!(initialConfList, conf)      end      end
        end
        captureConfList = copy(initialConfList)
        for nc = 1:scheme.NoCapturedElectrons
            captureConfList = Basics.generateConfigurationsWithElectronCapture(captureConfList, Shell[], scheme.intoShells, 0);   @show nc, captureConfList
        end
        shellList       = Basics.extractNonrelativisticShellList(multiplets)
        shellList       = Basics.merge(scheme.intoShells, scheme.decayShells);  @show shellList
        blockConfList   = copy(initialConfList)
        for nc = 1:scheme.NoCapturedElectrons
            blockConfList   = Basics.generateConfigurationsWithElectronCapture(blockConfList, Shell[], shellList, 0);   @show nc, blockConfList
        end
        @show initialConfList
        @show blockConfList
        @warn("blockConfList does not yet support the autoionization of the high-n electrons.")
        #
        # Determine first a hydrogenic spectrum for all subshells of the initial and doubly-excited states
        allConfList   = Configuration[];      append!(allConfList, initialConfList);      append!(allConfList, blockConfList)
        allSubshells  = Basics.extractRelativisticSubshellList(allConfList)
        primitives    = Bsplines.generatePrimitives(grid)
        orbitals      = Bsplines.generateOrbitalsHydrogenic(primitives, nm, allSubshells, printout=true)
        # Exclude configurations with too high mean energies
        en            = Float64[];
        for conf in initialConfList
            wen = Basics.determineMeanEnergy(conf, orbitals, nm, grid)
            push!(en, wen)
        end
        initialMean = sum(en) / length(en)
        println(">>> initial configuration(s) have mena energies  $initialMean  [a.u.].")

        return( (initialConfList, blockConfList)  )
    end

    """
    `Cascade.perform(scheme::HollowIonScheme, comp::Cascade.Computation)`
        ... to set-up and perform a dielectronic-recombination (DR) plasma rate coefficient computation that combines the electron capture
            and the subsequent radiative stabilization steps. Such a computation starts from a given set of initial configurations xor
            initial multiplets and (1) generates all doubly-excited configurations due to the capture of an electron with a given maximum
            electron energy; (2) selects all electron capture (inverse Auger) and re-autoionization (Auger) steps and (3) selects
            all steps for radiative stabilization due to given parameters of the scheme::HollowIonScheme. The results of
            these DR plasma rate computation are comprised into (output) data::ExcitationData, while these data are only printed during
            the generation and nothing is returned.

    `Cascade.perform(scheme::HollowIonScheme, comp::Cascade.Computation; output=true, outputToFile::Bool=true)`
        ... to perform the same but to return the complete output in a dictionary that is written to disk and can be used in subsequent
            cascade simulation. The particular output depends on the specifications of the cascade.
    """
    function perform(scheme::HollowIonScheme, comp::Cascade.Computation; output::Bool=false, outputToFile::Bool=true)
        if  output    results = Dict{String, Any}()    else    results = nothing    end
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        #
        # Perform the SCF and CI computation for the intial-state multiplets if initial configurations are given
        if  comp.initialConfigs != Configuration[]
            basis      = Basics.performSCF(comp.initialConfigs, comp.nuclearModel, comp.grid, comp.asfSettings; printout=false)
            multiplet  = Basics.performCI(basis, comp.nuclearModel, comp.grid, comp.asfSettings; printout=false)
            multiplets = [Multiplet("initial states", multiplet.levels)]
        else
            multiplets = comp.initialMultiplets
        end
        # Print out initial configurations and levels
        Cascade.displayLevels(stdout, multiplets, sa="initial ")
        if  printSummary   Cascade.displayLevels(iostream, multiplets, sa="initial ")                            end
        #
        # Generate subsequent cascade configurations as well as display and group them together
        wa  = Cascade.generateConfigurationsForDielectronicCapture(multiplets, comp.scheme, comp.nuclearModel, comp.grid)
        ##x wb1 = Cascade.groupDisplayConfigurationList(comp.nuclearModel.Z, wa[1], sa="(initial part of the) electron capture ")
        wb2 = Cascade.groupDisplayConfigurationList(comp.nuclearModel.Z, wa[2], sa="hollow ion configurations ")
        #
        # Determine first all configuration 'blocks' and from them the individual steps of the cascade
        ##x wc1 = Cascade.generateBlocks(scheme, comp::Cascade.Computation, wb1)
        wc2 = Cascade.generateBlocks(scheme, comp::Cascade.Computation, wb2, printout=false)
        ##x Cascade.displayBlocks(stdout, wc1, sa="for the (initial part of the) excited cascade ");
        Cascade.displayBlocks(stdout, wc2, sa="for the hollow ion cascade ")
        if  printSummary   ##x Cascade.displayBlocks(iostream, wc1, sa="for the (initial part of the) excited cascade ")
                           Cascade.displayBlocks(iostream, wc2, sa="for the hollow ion cascade ")    end
        #
        # Determine, modify and compute the transition data for all steps, ie. the PhotoIonization.Line's, etc.
        gMultiplets = Multiplet[];     for block in wc2  push!(gMultiplets, block.multiplet)    end
        ##x we = Cascade.determineSteps(scheme, comp, wc1, wc2)
        we = Cascade.determineSteps(scheme, comp, wc2)
        Cascade.displaySteps(stdout, we, sa="hollow ion decay ")
        if  printSummary   Cascade.displaySteps(iostream, we, sa="hollow ion decay ")    end
        wf   = Cascade.modifySteps(we)
        #
        data = Cascade.computeSteps(scheme, comp, wf)
        if output
            results = Base.merge( results, Dict("name"                                  => comp.name) )
            results = Base.merge( results, Dict("cascade scheme"                        => comp.scheme) )
            results = Base.merge( results, Dict("initial multiplets:"                   => multiplets) )
            results = Base.merge( results, Dict("generated multiplets:"                 => gMultiplets) )
            results = Base.merge( results, Dict("hollow-ion line data:" => data) )
            #
            #  Write out the result to file to later continue with simulations on the cascade data
            filename = "zzz-cascade-hollow-ion-computations-" * string(Dates.now())[1:13] * ".jld"
            println("\n* Write all results to disk; use:\n   JLD.save(''$filename'', results) \n   using JLD " *
                    "\n   results = JLD.load(''$filename'')    ... to load the results back from file.")
            if  printSummary   println(iostream, "\n* Write all results to disk; use:\n   JLD.save(''$filename'', results) \n   using JLD " *
                                                 "\n   results = JLD.load(''$filename'')    ... to load the results back from file." )      end
            JLD2.@save filename results
        end
        ## return( results )
        return( results )
    end


using GLWindow, GLAbstraction, ImmutableArrays, GLFW, React, ModernGL, Quaternions, GLText, GLPlot, Images

window 	= createwindow("Mesh Display", 1000, 1000, debugging = false) # debugging just works on linux and windows
cam 	= PerspectiveCamera(window.inputs, Vec3(1,1,1), Vec3(0))

initplotting()

function textwithoffset(start::Vec3, text, advance, lineHeight)
	resulttext 	= Vec1[]
	offset 		= Vec3[start]
	for elem in text
		offset1 = last(offset)
		if elem == '\t'
        	offset[end] = Vec3(offset1[1] + advance * 3, offset1[2], 0f0)
        elseif  elem == ' '
        	offset[end] = Vec3(offset1[1] + advance, offset1[2], 0f0)
        elseif elem == '\r' || elem == '\n'
        	offset[end] = Vec3(start[1], offset1[2] - lineHeight, 0f0)
        else
        	glchar = float32(elem)
        	if glchar > 256
        		glchar = float32(0)
        	end
			push!(offset, Vec3(offset1[1] + advance, offset1[2], 0f0))
			push!(resulttext, Vector1(glchar))
        end
	end
	offset, resulttext
end

text = "ööähh Hallo, was ist denn hier los?#üa+#üsadi0ß24 \nakjskljd ="
font = GLFont("VeraMono")
offset, ctext 	= textwithoffset(Vec3(0), text, font.props[1]...)
println(offset)

parameters 		= [(GL_TEXTURE_WRAP_S,  GL_CLAMP_TO_EDGE),(GL_TEXTURE_MIN_FILTER, GL_NEAREST)]
texttex 		= Texture(ctext, parameters=parameters)
offset 			= Texture(offset, parameters=parameters)

view = [
  "GLSL_EXTENSIONS"     	=> "#extension GL_ARB_draw_instanced : enable",
  "offset_calculation"     => "texelFetch(offset, index, 0).rgb;",
]

data = merge([
	:index_offset		=> convert(GLint, 0),
    :rotation 			=> Vec4(1,0,0,0),
    :text 				=> texttex,
    :offset 			=> offset,
    :scale 				=> Vec2(1/500),
    :color 				=> Vec4(0,0,0,1),
    :backgroundcolor 	=> Vec4(0),
    :projectionview 	=> cam.projectionview
], font.data)

program = TemplateProgram("textShader.vert", "textShader.frag", view, data)
obj = instancedobject(data, program, length(text))
#prerender!(obj, glEnable, GL_DEPTH_TEST)
obj2 = toopengl(Texture("VeraMono.bmp"))

glClearColor(1,1,1,0)
while !GLFW.WindowShouldClose(window.glfwWindow)
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

  render(GRID)
  render(obj)
  #render(obj2)

  yield() # this is needed for react to work
  GLFW.SwapBuffers(window.glfwWindow)
  GLFW.PollEvents()
end
GLFW.Terminate()


# This file seems to need to exist for Julia package manager to be happy
module CORBITS

export prob_of_transits_approx

if VERSION >= v"0.7.0-"
   using Libdl
   using Pkg
   using CORBITS
   global const LIB_CORBITS = Libdl.find_library(["libcorbits.so"],[joinpath(dirname(pathof(CORBITS)),".."),"/usr/local/lib"] ) # WARNING: Assumes can find libcorbits.so
elseif VERSION >= v"0.4.0-"
   global const LIB_CORBITS = Libdl.find_library(["libcorbits.so"],[".",joinpath(Pkg.dir(),"ExoplanetsSysSim/"),joinpath(Pkg.dir(),"CORBITS/"),"/usr/local/lib"])  # WARNING: Assumes can find libcorbits.so
else
   global const LIB_CORBITS = find_library(["libcorbits.so"],[".",joinpath(Pkg.devdir(),"ExoplanetsSysSim/"),joinpath(Pkg.devdir(),"CORBITS/"),"/usr/local/lib"])  # WARNING: Assumes can find libcorbits.so
end

# Call CORBITS's function prob_of_transits_approx_arrays(a, r_star, r, e, Omega, omega, inc, use)
# Returns a Cdouble (aka Float64)
# For documentation see https://github.com/jbrakensiek/CORBITS
function prob_of_transits_approx(a::Vector{Cdouble},r_star::Cdouble,r::Vector{Cdouble}, e::Vector{Cdouble},
                                 Omega::Vector{Cdouble}, omega::Vector{Cdouble}, inc::Vector{Cdouble}, use::Vector{Cint})
  @assert(length(a) == length(r) == length(e) == length(Omega) == length(omega)
== length(inc) >= length(use) )
  @assert(length(use) >=1 )
  num_pl = length(use)
  return ccall( (:prob_of_transits_approx_arrays, LIB_CORBITS), Cdouble,
      (Ptr{Cdouble}, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}, Cint),
       a, r_star, r, e, Omega, omega, inc, use, num_pl)
end

end # module


# Julia wrapper for header: /usr/local/include/libavutil/display.h
# Automatically generated using Clang.jl wrap_c, version 0.0.0

export
    av_display_rotation_get,
    av_display_rotation_set,
    av_display_matrix_flip

function av_display_rotation_get(matrix::NTuple{9,Int32})
    ccall((:av_display_rotation_get,libavutil),Cdouble,(NTuple{9,Int32},),matrix)
end

function av_display_rotation_set(matrix::NTuple{9,Int32},angle::Cdouble)
    ccall((:av_display_rotation_set,libavutil),Void,(NTuple{9,Int32},Cdouble),matrix,angle)
end

function av_display_matrix_flip(matrix::NTuple{9,Int32},hflip::Integer,vflip::Integer)
    ccall((:av_display_matrix_flip,libavutil),Void,(NTuple{9,Int32},Cint,Cint),matrix,hflip,vflip)
end