text:
@testset "MOI Utilities" begin
    struct NewExtType <: EAGO.ExtensionType
    end
    @test MOIU.map_indices(exp, NewExtType()) == NewExtType()
    @test MOIU.map_indices(exp, EAGO.DefaultExt()) == EAGO.DefaultExt()
end

@testset "Set/Get Attributes" begin

    m = EAGO.Optimizer()
    @test MOI.get(m, MOI.SolverName()) === "EAGO: Easy Advanced Global Optimization"

    m._node_count = 55
    @test MOI.get(m, MOI.NodeCount()) === 55

    m._result_status_code = MOI.FEASIBLE_POINT
    @test MOI.get(m, MOI.ResultCount()) === 1

    m._result_status_code = MOI.OTHER_RESULT_STATUS
    @test MOI.get(m, MOI.ResultCount()) === 0

    m._parameters.verbosity = 2
    m._parameters.log_on = true
    MOI.set(m, MOI.Silent(), true)
    @test m._parameters.verbosity === 0
    @test m._parameters.log_on === false

    @test MOI.supports(m, MOI.ObjectiveSense())
    @test MOI.supports(m, MOI.TimeLimitSec())

    MOI.set(m, MOI.TimeLimitSec(), 1.1)
    @test m._parameters.time_limit == 1.1

    MOI.set(m, MOI.TimeLimitSec(), nothing)
    @test m._parameters.time_limit == Inf
end

@testset "Get Termination Code " begin

    model = EAGO.Optimizer()

    # Termination Status Code Checks
    model._termination_status_code = MOI.OPTIMAL
    status = @inferred MOI.get(model, MOI.TerminationStatus())
    @test status == MOI.OPTIMAL

    model._termination_status_code = MOI.ITERATION_LIMIT
    status = @inferred MOI.get(model, MOI.TerminationStatus())
    @test status == MOI.ITERATION_LIMIT
end

@testset "Add Variable, Get Number/Index" begin

    model = EAGO.Optimizer()

    # Add variable
    @inferred MOI.add_variable(model)
    nVar = @inferred MOI.get(model, MOI.NumberOfVariables())
    @test nVar == 1

    # Add second variable
    MOI.add_variables(model,3)
    nVar = @inferred MOI.get(model, MOI.NumberOfVariables())
    @test nVar == 4

    # Get variable indices
    indx = @inferred MOI.get(model, MOI.ListOfVariableIndices())
    @test indx == @inferred MOI.VariableIndex[MOI.VariableIndex(1), MOI.VariableIndex(2),
                                    MOI.VariableIndex(3), MOI.VariableIndex(4)]

    @test_nowarn @inferred EAGO.check_inbounds!(model, MOI.VariableIndex(1))
    @test_throws ErrorException @inferred EAGO.check_inbounds!(model,MOI.VariableIndex(6))
end

@testset "Variable Bounds" begin
    model = EAGO.Optimizer()

    @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.LessThan{Float64})
    @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.GreaterThan{Float64})
    @test MOI.supports_constraint(model, MOI.VariableIndex, MOI.EqualTo{Float64})

    x = MOI.add_variables(model,3)
    z = MOI.add_variable(model)

    @inferred MOI.add_constraint(model, x[1], MOI.GreaterThan(-1.0))
    @inferred MOI.add_constraint(model, x[2], MOI.LessThan(-1.0))
    @inferred MOI.add_constraint(model, x[3], MOI.EqualTo(2.0))

    @test model._input_problem._variable_info[1].is_integer == false
    @test model._input_problem._variable_info[1].lower_bound == -1.0
    @test model._input_problem._variable_info[1].has_lower_bound == true
    @test model._input_problem._variable_info[1].upper_bound == Inf
    @test model._input_problem._variable_info[1].has_upper_bound == false
    @test model._input_problem._variable_info[1].is_fixed == false

    @test model._input_problem._variable_info[2].is_integer == false
    @test model._input_problem._variable_info[2].lower_bound == -Inf
    @test model._input_problem._variable_info[2].has_lower_bound == false
    @test model._input_problem._variable_info[2].upper_bound == -1.0
    @test model._input_problem._variable_info[2].has_upper_bound == true
    @test model._input_problem._variable_info[2].is_fixed == false

    @test model._input_problem._variable_info[3].is_integer == false
    @test model._input_problem._variable_info[3].lower_bound == 2.0
    @test model._input_problem._variable_info[3].has_lower_bound == true
    @test model._input_problem._variable_info[3].upper_bound == 2.0
    @test model._input_problem._variable_info[3].has_upper_bound == true
    @test model._input_problem._variable_info[3].is_fixed == true

    @test_nowarn MOI.add_constraint(model, x[1], MOI.GreaterThan(NaN))
    @test_nowarn MOI.add_constraint(model, x[1], MOI.LessThan(NaN))

    @test_nowarn MOI.add_constraint(model, x[1], MOI.GreaterThan(-3.5))
    @test_nowarn MOI.add_constraint(model, x[1], MOI.EqualTo(-3.5))
    @test_nowarn MOI.add_constraint(model, x[1], MOI.ZeroOne())

    @test_nowarn MOI.add_constraint(model, x[2], MOI.LessThan(-3.5))
    @test_nowarn MOI.add_constraint(model, x[2], MOI.EqualTo(-3.5))
    @test_nowarn MOI.add_constraint(model, x[2], MOI.ZeroOne())

    @test_nowarn MOI.add_constraint(model, x[3], MOI.GreaterThan(-3.5))
    @test_nowarn MOI.add_constraint(model, x[3], MOI.LessThan(-3.5))
    @test_nowarn MOI.add_constraint(model, x[3], MOI.EqualTo(-3.5))
    @test_nowarn MOI.add_constraint(model, x[3], MOI.ZeroOne())
end

@testset "Add Linear Constraint " begin

    model = EAGO.Optimizer()

    @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
    @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64})
    @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64})

    x = MOI.add_variables(model,3)

    func1 = MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm.(Float64[5.0,-2.3],[x[1],x[2]]),2.0)
    func2 = MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm.(Float64[4.0,-2.2],[x[2],x[3]]),2.1)
    func3 = MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm.(Float64[3.0,-3.3],[x[1],x[3]]),2.2)

    set1 = MOI.LessThan{Float64}(1.0)
    set2 = MOI.GreaterThan{Float64}(2.0)
    set3 = MOI.EqualTo{Float64}(3.0)

    @inferred MOI.add_constraint(model, func1, set1)
    @inferred MOI.add_constraint(model, func2, set2)
    @inferred MOI.add_constraint(model, func3, set3)

    @test model._input_problem._linear_leq_constraints[1][1].constant == 2.0
    @test model._input_problem._linear_geq_constraints[1][1].constant == 2.1
    @test model._input_problem._linear_eq_constraints[1][1].constant == 2.2
    @test model._input_problem._linear_leq_constraints[1][1].terms[1].coefficient == 5.0
    @test model._input_problem._linear_geq_constraints[1][1].terms[1].coefficient == 4.0
    @test model._input_problem._linear_eq_constraints[1][1].terms[1].coefficient == 3.0
    @test model._input_problem._linear_leq_constraints[1][1].terms[2].coefficient == -2.3
    @test model._input_problem._linear_geq_constraints[1][1].terms[2].coefficient == -2.2
    @test model._input_problem._linear_eq_constraints[1][1].terms[2].coefficient == -3.3
    @test model._input_problem._linear_leq_constraints[1][1].terms[1].variable.value == 1
    @test model._input_problem._linear_geq_constraints[1][1].terms[1].variable.value == 2
    @test model._input_problem._linear_eq_constraints[1][1].terms[1].variable.value == 1
    @test model._input_problem._linear_leq_constraints[1][1].terms[2].variable.value == 2
    @test model._input_problem._linear_geq_constraints[1][1].terms[2].variable.value == 3
    @test model._input_problem._linear_eq_constraints[1][1].terms[2].variable.value == 3
    @test MOI.LessThan{Float64}(1.0) == model._input_problem._linear_leq_constraints[1][2]
    @test MOI.GreaterThan{Float64}(2.0) == model._input_problem._linear_geq_constraints[1][2]
    @test MOI.EqualTo{Float64}(3.0) == model._input_problem._linear_eq_constraints[1][2]
end

@testset "Add Quadratic Constraint " begin

    model = EAGO.Optimizer()

    @test MOI.supports_constraint(model, MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64})
    @test MOI.supports_constraint(model, MOI.ScalarQuadraticFunction{Float64}, MOI.GreaterThan{Float64})
    @test MOI.supports_constraint(model, MOI.ScalarQuadraticFunction{Float64}, MOI.EqualTo{Float64})

    x = MOI.add_variables(model,3)

    func1 = MOI.ScalarQuadraticFunction{Float64}([MOI.ScalarQuadraticTerm{Float64}(2.5,x[2],x[2])],
                                                 [MOI.ScalarAffineTerm{Float64}(5.0,x[1])], 2.0)
    func2 = MOI.ScalarQuadraticFunction{Float64}([MOI.ScalarQuadraticTerm{Float64}(2.2,x[1],x[2])],
                                                 [MOI.ScalarAffineTerm{Float64}(4.0,x[2])], 2.1)
    func3 = MOI.ScalarQuadraticFunction{Float64}([MOI.ScalarQuadraticTerm{Float64}(2.1,x[1],x[1])],
                                                 [MOI.ScalarAffineTerm{Float64}(3.0,x[3])], 2.2)

    set1 = MOI.LessThan{Float64}(1.0)
    set2 = MOI.GreaterThan{Float64}(2.0)
    set3 = MOI.EqualTo{Float64}(3.0)

    @inferred MOI.add_constraint(model, func1, set1)
    @inferred MOI.add_constraint(model, func2, set2)
    @inferred MOI.add_constraint(model, func3, set3)

    @test model._input_problem._quadratic_leq_constraints[1][1].constant == 2.0
    @test model._input_problem._quadratic_geq_constraints[1][1].constant == 2.1
    @test model._input_problem._quadratic_eq_constraints[1][1].constant == 2.2
    @test model._input_problem._quadratic_leq_constraints[1][1].quadratic_terms[1].coefficient == 2.5
    @test model._input_problem._quadratic_geq_constraints[1][1].quadratic_terms[1].coefficient == 2.2
    @test model._input_problem._quadratic_eq_constraints[1][1].quadratic_terms[1].coefficient == 2.1
    @test model._input_problem._quadratic_leq_constraints[1][1].affine_terms[1].coefficient == 5.0
    @test model._input_problem._quadratic_geq_constraints[1][1].affine_terms[1].coefficient == 4.0
    @test model._input_problem._quadratic_eq_constraints[1][1].affine_terms[1].coefficient == 3.0
    @test model._input_problem._quadratic_leq_constraints[1][1].quadratic_terms[1].variable_1.value == 2
    @test model._input_problem._quadratic_geq_constraints[1][1].quadratic_terms[1].variable_1.value == 1
    @test model._input_problem._quadratic_eq_constraints[1][1].quadratic_terms[1].variable_1.value == 1
    @test model._input_problem._quadratic_leq_constraints[1][1].quadratic_terms[1].variable_2.value == 2
    @test model._input_problem._quadratic_geq_constraints[1][1].quadratic_terms[1].variable_2.value == 2
    @test model._input_problem._quadratic_eq_constraints[1][1].quadratic_terms[1].variable_2.value == 1
    @test model._input_problem._quadratic_leq_constraints[1][1].affine_terms[1].variable.value == 1
    @test model._input_problem._quadratic_geq_constraints[1][1].affine_terms[1].variable.value == 2
    @test model._input_problem._quadratic_eq_constraints[1][1].affine_terms[1].variable.value == 3
    @test MOI.LessThan{Float64}(1.0) == model._input_problem._quadratic_leq_constraints[1][2]
    @test MOI.GreaterThan{Float64}(2.0) == model._input_problem._quadratic_geq_constraints[1][2]
    @test MOI.EqualTo{Float64}(3.0) == model._input_problem._quadratic_eq_constraints[1][2]
end

@testset "Set Objective" begin
    model = @inferred EAGO.Optimizer()

    x = MOI.add_variables(model,3)

    @inferred MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
    @test model._input_problem._optimization_sense == MOI.MIN_SENSE

    MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
    @test model._input_problem._optimization_sense == MOI.MAX_SENSE

    MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE)
    @test model._input_problem._optimization_sense == MOI.FEASIBILITY_SENSE

    @test MOI.supports(model, MOI.ObjectiveFunction{MOI.VariableIndex}())
    @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
    @test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}())

    x = MOI.add_variables(model,3)

    MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), MOI.VariableIndex(2))
    @test model._input_problem._objective == MOI.VariableIndex(2)

    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm.(Float64[5.0,-2.3],[x[1],x[2]]),2.0))
    @test model._input_problem._objective.constant == 2.0

    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(),
                                         MOI.ScalarQuadraticFunction{Float64}([MOI.ScalarQuadraticTerm{Float64}(2.5,x[2],x[2])], [MOI.ScalarAffineTerm{Float64}(5.0,x[1])],
                                         3.0))
    @test model._input_problem._objective.constant == 3.0
end

@testset "Empty/Isempty, EAGO Model, Single Storage, Optimize Hook " begin
    x = EAGO.Optimizer()
    model = x._global_optimizer
    @test @inferred MOI.is_empty(x)

    t = EAGO.DefaultExt()
    model._current_node = EAGO.NodeBB(Float64[1.0,5.0], Float64[2.0,6.0], Bool[false, false], true, -4.0, 1.0, 2, 1, 1, EAGO.BD_NONE, 1, 0.1)
    model._lower_objective_value = -3.0
    model._upper_objective_value = 0.0
    @test_nowarn EAGO.single_storage!(t, model)
    new_node = pop!(model._stack)
    @test new_node.lower_bound == -3.0
    @test new_node.upper_bound == 0.0

    @test_nowarn EAGO.single_storage!(model)

    @test_nowarn EAGO.optimize_hook!(EAGO.DefaultExt(), model)
    @test_nowarn EAGO.throw_optimize_hook!(x)
end
#=
@testset "Fallback Interval Bounds" begin

    # test linear expression interval fallback
    X = Interval(1.0, 2.0)
    Y = Interval(-4.0, 6.0)
    Z = Interval(-6.0,-5.0)

    ylower = Float64[X.lo; Y.lo; Z.lo]
    yupper = Float64[X.hi; Y.hi; Z.hi]

    n = EAGO.NodeBB(ylower, yupper, -Inf, Inf, 3, 2)

    coeff = Float64[3.0; 4.0; -7.0]
    cons = 3.2
    fintv_saf = coeff[1]*X + coeff[2]*Y + coeff[3]*Z + cons
    x = MOI.VariableIndex.(Int64[1;2;3])
    saf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(coeff, x), cons)
    saf_lo = @inferred EAGO.interval_bound(saf, n, true)
    saf_hi = @inferred EAGO.interval_bound(saf, n, false)
    @test isapprox(saf_lo, fintv_saf.lo, atol = 1E-7)
    @test isapprox(saf_hi, fintv_saf.hi, atol = 1E-7)

    # test quadratic expression interval fallback
    vi1 = [MOI.VariableIndex(1); MOI.VariableIndex(2); MOI.VariableIndex(3);
           MOI.VariableIndex(1); MOI.VariableIndex(2)];
    vi2 = [MOI.VariableIndex(1); MOI.VariableIndex(2); MOI.VariableIndex(3);
           MOI.VariableIndex(2); MOI.VariableIndex(3)];
    coef1 = Float64[1.0; 4.0; 1.0; 3.0; -5.5]
    sat_arr = MOI.ScalarAffineTerm{Float64}[MOI.ScalarAffineTerm{Float64}(4.0,MOI.VariableIndex(2))]
    sqf =  MOI.ScalarQuadraticFunction(sat_arr,
                                       MOI.ScalarQuadraticTerm.(coef1, vi1, vi2),
                                       cons)
    fintv_sqf = X^2 + 4.0*Y^2 + Z^2 + 3.0*X*Y - 5.5*Y*Z + 4.0*Y + 3.2
    sqf_lo = @inferred EAGO.interval_bound(sqf, n, true)
    sqf_hi = @inferred EAGO.interval_bound(sqf, n, false)
    @test isapprox(sqf_lo, fintv_sqf.lo, atol = 1E-7)
    @test isapprox(sqf_hi, fintv_sqf.hi, atol = 1E-7)
end

@testset "Fallback Interval Bound 2" begin
    # test linear expression interval fallback
    n = EAGO.NodeBB([-1.0; -1.0], [2.0; 2.0], -Inf, Inf, 3, 2)

    m1 = Model(with_optimizer(EAGO.Optimizer, verbosity = 4, cp_depth = -1))
    @variable(m1, -1.0 <= x <= 2.0)
    @variable(m1, -1.0 <= y <= 2.0)
    @constraint(m1, x^2 + y + x*y <= 50.0)
    @constraint(m1, x^2 + y >= -50.0)
    @constraint(m1, 2x + y <= 50.0)
    @constraint(m1, 2x - y >= -50.0)
    @NLconstraint(m1, x^2 + sin(x) <= 50.0)
    @NLconstraint(m1, x^2 + sin(x) >= -50.0)
    @NLobjective(m1, Min, cos(x)*x)
    optimize!(m1)
    obj_value = objective_value(m1)
    @test isapprox(obj_value, -0.8322104859004729, atol=1E-3)

    b = backend(m1).optimizer.model.optimizer
    EAGO.interval_lower_bound!(b, n)
    #@test isapprox(b._lower_objective_value, -0.1513, atol=1E-3)
end
=#

@testset "LP Problems" begin
    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0,
                                        "presolve_scrubber_flag" => false,
                                        "presolve_to_JuMP_flag" => false))

    @variable(m, 1 <= x <= 3)
    @variable(m, 1 <= y <= 3)

    @objective(m, Min, x + y)

    @constraint(m, x + y <= 10)
    @constraint(m, x - y <= 10)
    @constraint(m, y >= 0)

    JuMP.optimize!(m)

    @test isapprox(JuMP.value(x), 1.0, atol=1E-4)
    @test isapprox(JuMP.value(y), 1.0, atol=1E-4)
    @test isapprox(JuMP.objective_value(m), 2.0, atol=1E-4)
    @test JuMP.termination_status(m) == MOI.OPTIMAL
    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0,
                                        "presolve_scrubber_flag" => false,
                                        "presolve_to_JuMP_flag" => false))

    @variable(m, -3 <= x <= -1)
    @variable(m, -2 <= y <= 2)
    @variable(m, 1 <= z <= 3)

    @objective(m, Min, x - y + 2z)

    @constraint(m, x + 2y >= -10)
    @constraint(m, z - 2y <= 2)
    @constraint(m, y >= 0)

    JuMP.optimize!(m)

    @test isapprox(JuMP.value(x), -3.0, atol=1E-4)
    @test isapprox(JuMP.value(y), 2.0, atol=1E-4)
    @test isapprox(JuMP.value(z), 1.0, atol=1E-4)
    @test isapprox(JuMP.objective_value(m), -3.0, atol=1E-4)
    @test JuMP.termination_status(m) == MOI.OPTIMAL
    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0,
                                        "presolve_scrubber_flag" => false,
                                        "presolve_to_JuMP_flag" => false))

    @variable(m, -3 <= x <= -1)
    @variable(m, -2 <= y <= 2)
    @variable(m, 1 <= z <= 3)
    @variable(m, -10 <= q <= 9)

    @objective(m, Min, 2x - 3y + 2z)

    @constraint(m, x + 2y >= -10)
    @constraint(m, z - 2y <= 2)
    @constraint(m, y >= 0)
    @constraint(m, q-3*z-y >= 0)

    JuMP.optimize!(m)

    @test isapprox(JuMP.value(x), -3.0, atol=1E-4)
    @test isapprox(JuMP.value(y), 2.0, atol=1E-4)
    @test isapprox(JuMP.value(z), 1.0, atol=1E-4)
    @test isapprox(JuMP.objective_value(m), -10.0, atol=1E-4)
    @test JuMP.termination_status(m) == MOI.OPTIMAL
    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0,
                                        "presolve_scrubber_flag" => false,
                                        "presolve_to_JuMP_flag" => false))

    @variable(m, -3 <= x <= -1)
    @variable(m, -2 <= y <= 2)
    @variable(m, 1 <= z <= 3)
    @variable(m, -10 <= q <= 9)

    @NLobjective(m, Min, 2x - 3y + 2z)

    @constraint(m, x + 2y >= -10)
    @constraint(m, z - 2y <= 2)
    @constraint(m, y >= 4)
    @constraint(m, q-3*z-y >= 0)

    JuMP.optimize!(m)

    @test isapprox(JuMP.value(x), 0.0, atol=1E-4)
    @test isapprox(JuMP.value(y), 0.0, atol=1E-4)
    @test isapprox(JuMP.value(z), 0.0, atol=1E-4)
    @test isapprox(JuMP.value(q), 0.0, atol=1E-4)
    @test JuMP.termination_status(m) == MOI.INFEASIBLE
    @test JuMP.primal_status(m) == MathOptInterface.NO_SOLUTION

    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0,
                                        "presolve_scrubber_flag" => false,
                                        "presolve_to_JuMP_flag" => false))

    @variable(m, -3 <= x <= -1)
    @variable(m, -2 <= y <= 2)
    @variable(m, 1 <= z <= 3)
    @variable(m, q == 1)

    @objective(m, Max, x)

    @constraint(m, x + 2y >= -10)
    @constraint(m, z - 2y <= 2)
    @constraint(m, y >= 0)

    JuMP.optimize!(m)

    @test isapprox(JuMP.value(x), -1.0, atol=1E-4)
    @test isapprox(JuMP.value(q), 1.0, atol=1E-4)
    @test isapprox(JuMP.objective_value(m), -1.0, atol=1E-4)
    @test JuMP.termination_status(m) == MOI.OPTIMAL
    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0,
                                        "presolve_scrubber_flag" => false,
                                        "presolve_to_JuMP_flag" => false))

    @variable(m, -3 <= x <= -1)
    @variable(m, -2 <= y <= 2)
    @variable(m, 1 <= z <= 3)
    @variable(m, -10 <= q <= 9)

    @objective(m, Max, 2x - 3y + 2z)

    @constraint(m, x + 2y >= -10)
    @constraint(m, z - 2y <= 2)
    @constraint(m, y >= 0)
    @constraint(m, q-3*z-y >= 0)

    JuMP.optimize!(m)

    @test isapprox(JuMP.value(x), -1.0, atol=1E-4)
    @test isapprox(JuMP.value(y), 0.428571, atol=1E-4)
    @test isapprox(JuMP.value(z), 2.857142, atol=1E-4)
    @test isapprox(JuMP.objective_value(m), 2.428571, atol=1E-4)
    @test JuMP.termination_status(m) == MOI.OPTIMAL
    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
end

@testset "NLP Problem #1" begin
    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0))

    @variable(m, -200 <= x <= -100)
    @variable(m, 200 <= y <= 400)
    @constraint(m, -500 <= x + 2y <= 400)
    @NLobjective(m, Min, x*y)
    JuMP.optimize!(m)

    @test JuMP.termination_status(m) == MOI.OPTIMAL
    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
    @test isapprox(JuMP.value(x), -200.0, atol=1E-5)
    @test isapprox(JuMP.value(y), 300.0, atol=1E-5)
    @test isapprox(JuMP.objective_value(m), -59999.4011899692, atol=2.0)
end

@testset "NLP Problem #2" begin
    m = Model(optimizer_with_attributes(EAGO.Optimizer, "verbosity" => 0))
    x_Idx = Any[1, 2, 3]
    @variable(m, x[x_Idx])
    JuMP.set_lower_bound(x[1], -5.0)
    JuMP.set_upper_bound(x[1], 5.0)
    JuMP.set_lower_bound(x[2], -5.0)
    JuMP.set_upper_bound(x[2], 5.0)
    JuMP.set_lower_bound(x[3], -100.0)
    JuMP.set_upper_bound(x[3], 100.0)

    @NLconstraint(m, e2, 2* (x[2])^2+4*x[1]*x[2]-42*x[1]+4* (x[1])^3-x[3] <= 14.0)
    @NLconstraint(m, e3, (-2* (x[2])^2)-4*x[1]*x[2]+42*x[1]-4* (x[1])^3-x[3] <= -14.0)
    @NLconstraint(m, e4, 2* (x[1])^2+4*x[1]*x[2]-26*x[2]+4* (x[2])^3-x[3] <= 22.0)
    @NLconstraint(m, e5, (-2* (x[1])^2)-4*x[1]*x[2]+26*x[2]-4* (x[2])^3-x[3] <= -22.0)
    @NLobjective(m, Min, x[3])
    JuMP.optimize!(m)

    @test isapprox(JuMP.objective_value(m), 0.000, atol=1E-3)
    @test JuMP.termination_status(m) == MOI.OPTIMAL
    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
end

@testset "NLP Problem #3" begin
    m = Model(EAGO.Optimizer)
    set_optimizer_attributes(m, "verbosity" => 0,
                             "output_iterations" => 0,
                             "absolute_tolerance" => 1.0E-2)

    # ----- Variables ----- #
    x_Idx = Any[2, 3, 4]
    @variable(m, x[x_Idx])
    JuMP.set_lower_bound(x[2], 1.0e-6)
    JuMP.set_upper_bound(x[2], 1.0)
    JuMP.set_lower_bound(x[3], 1.0e-6)
    JuMP.set_upper_bound(x[3], 1.0)
    JuMP.set_lower_bound(x[4], 1.0e-6)
    JuMP.set_upper_bound(x[4], 1.0)

    # ----- Constraints ----- #
    @constraint(m, e2, x[2]+x[3]+x[4] == 1.0)

    # ----- Objective ----- #
    @NLobjective(m, Max, (15.3261663216011*x[2]+23.2043471859416*x[3]+6.69678129464404*x[4])*log(2.1055*x[2]+3.1878*x[3]+0.92*x[4]) - (1.0-2.0) + (2.0*3.0) + (4.1+6.2) + (4.1/6.2) + (3.1^2))

    JuMP.optimize!(m)
    @test isapprox(objective_value(m), 54.0, atol=1E-0)
end

@testset "NLP Problem #4" begin

    m = Model(EAGO.Optimizer)
    set_optimizer_attributes(m, "verbosity" => 0,
                             "output_iterations" => 0,
                             "absolute_tolerance" => 1.0E-2)

    # ----- Variables ----- #
    x_Idx = Any[2, 3, 4]
    @variable(m, x[x_Idx])
    JuMP.set_lower_bound(x[2], 1.0e-6)
    JuMP.set_upper_bound(x[2], 1.0)
    JuMP.set_lower_bound(x[3], 1.0e-6)
    JuMP.set_upper_bound(x[3], 1.0)
    JuMP.set_lower_bound(x[4], 1.0e-6)
    JuMP.set_upper_bound(x[4], 1.0)

    @NLexpression(m, nl_expr, x[2]*x[3] + cos(x[4]) + tanh(x[2]))
    @NLconstraint(m, nl_expr >= -3.0)
    @NLobjective(m, Min, nl_expr)
    JuMP.optimize!(m)

    @test isapprox(JuMP.objective_value(m), 0.5403032960741783, atol=1E-3)
    @test JuMP.termination_status(m) === MOI.OPTIMAL
    @test JuMP.primal_status(m) === MOI.FEASIBLE_POINT
end

@testset "NLP Problem #5" begin
    m = Model(EAGO.Optimizer)
    set_optimizer_attributes(m, "verbosity" => 0,
                                "output_iterations" => 0,
                                "absolute_tolerance" => 1.0E-2)

    # ----- Variables ----- #
    xL = [500.0 1300.0 5000.0 100.0 200.0 200.0 200.0 300.0 6900.0]
    xU = [600.0 1500.0 6000.0 200.0 300.0 300.0 400.0 500.0 7100.0]
    @variable(m, xL[i] <= x[i=1:9] <= xU[i])

    # ----- Constraints ----- #
    @constraint(m, e1, x[1] + x[2]+ x[3] - x[9] == 0.0)
    @constraint(m, e2, 0.0025*x[4]+0.0025*x[6] <= 1.0)
    @constraint(m, e3, -0.0025*x[4]+0.0025*x[5]+0.0025*x[7] <= 1.0)
    @constraint(m, e4, -0.01*x[5]+0.01*x[8] <= 1.0)
    @constraint(m, e5, 100*x[1]-x[1]*x[6]+833.33252*x[4] <= 83333.333)
    @constraint(m, e6, -x[2]*x[4]+x[2]*x[7]+1250*x[4]-1250*x[5] >= 0.0)
    @constraint(m, e7, x[3]*x[5]-x[3]*x[8]-2500*x[5] <= -1.25e6)

    # ----- Objective ----- #
    @objective(m, Min, x[9])
    optimize!(m)
    @test isapprox(objective_value(m), 7049.247765631681, atol=1E0)
end

@testset "NLP Problem #6" begin
    m = Model(EAGO.Optimizer)
    set_optimizer_attributes(m, "verbosity" => 0,
                                "output_iterations" => 0,
                                "absolute_tolerance" => 1.0E-2)
    xL = [-2.0 0.0]; xU = [2.0 4.0]
    @variable(m, xL[i] <= x[i=1:2] <= xU[i])
    @constraint(m, -x[1]^2 + x[2]^2 - 1.0 == 0.0)
    @constraint(m, 0.4*(x[1]-3.0)^2 + 0.2*x[2]^2 <= 3.0)
    @objective(m, Min, x[2])
    optimize!(m)
    @test isapprox(JuMP.objective_value(m), 1.0652212400578724, atol=1E-3)
end

@testset "NLP Problem #7" begin
    m = Model(EAGO.Optimizer)
    set_optimizer_attributes(m, "verbosity" => 0)
    xL = [-2.0 0.0]; xU = [2.0 4.0]
    @variable(m, xL[i] <= x[i=1:2] <= xU[i])
    @NLobjective(m, Max, x[2]^2 + x[1]^2 + x[1]*x[2])
    optimize!(m)
    @test isapprox(JuMP.objective_value(m), 27.99971055498271, atol=1E-3)
end

@testset "NLP Problem #8" begin
    m = Model(EAGO.Optimizer)
    set_optimizer_attributes(m, "verbosity" => 0)
    pL = [0.5, 0.8401, 0.5]
    pU = [1.0, 1.0, 2.0]
    @variable(m, pL[i] <= p[i=1:3] <= pU[i])
    @NLobjective(m, Min,(560.0 - p[3]*(1 - 0.84/p[2]))^(-p[1]*p[2]))
    JuMP.optimize!(m)
    obj_val = objective_value(m)
    @test isapprox(JuMP.objective_value(m), 0.0018, atol=1E-3)
end

@testset "Register special expressions" begin
    raw_index(v::MOI.VariableIndex) = v.value

    model = Model()
    @variable(model, x)
    @variable(model, y)
    register_eago_operators!(model)

    @NLconstraint(model, c1, relu(x) <= 0.0)
    @NLconstraint(model, c2, leaky_relu(x) <= 0.0)
    @NLconstraint(model, c3, maxsig(x) <= 0.0)
    @NLconstraint(model, c4, maxtanh(x) <= 0.0)
    @NLconstraint(model, c5, softplus(x) <= 0.0)
    @NLconstraint(model, c6, pentanh(x) <= 0.0)

    @NLconstraint(model, c7, sigmoid(x) <= 0.0)
    @NLconstraint(model, c8, bisigmoid(x) <= 0.0)

    @NLconstraint(model, c9, softsign(x) <= 0.0)
    @NLconstraint(model, c10, gelu(x) <= 0.0)
    @NLconstraint(model, c11, swish1(x) <= 0.0)

    @NLconstraint(model, c12, positive(x) <= 0.0)
    @NLconstraint(model, c13, negative(x) <= 0.0)

    @NLconstraint(model, c14, xlogx(x) <= 0.0)

    @NLconstraint(model, c15, param_relu(x, 0.3) <= 0.0)
    @NLconstraint(model, c16, elu(x, 0.3) <= 0.0)
    @NLconstraint(model, c17, selu(x, 1.1, 0.3) <= 0.0)

    @NLconstraint(model, c18, lower_bnd(x, -1.0) <= 0.0)
    @NLconstraint(model, c19, upper_bnd(x, 1.0) <= 0.0)
    @NLconstraint(model, c20, bnd(x, -1.0, 1.0) <= 0.0)

    @NLconstraint(model, c21, arh(x, 3.0) <= 0.0)
    @NLconstraint(model, c22, xexpax(x, 2.0) <= 0.0)

    @NLconstraint(model, c23, f_erf(x) <= 0.0)
    @NLconstraint(model, c24, f_erfinv(x) <= 0.0)
    @NLconstraint(model, c25, f_erfc(x) <= 0.0)
    @NLconstraint(model, c26, f_erfcinv(x) <= 0.0)

    values = zeros(2)
    x_index = raw_index(JuMP.index(x))
    y_index = raw_index(JuMP.index(y))
    values[x_index] = 0.5
    values[y_index] = 0.5

    d = NLPEvaluator(model)
    MOI.initialize(d, Symbol[:Grad])
    out = zeros(26)
    MOI.eval_constraint(d, out, values)

    @test isapprox(out[1], 0.5, atol = 1E-3)
    @test isapprox(out[2], 0.5, atol = 1E-3)
    @test isapprox(out[3], 0.6224593312018546, atol = 1E-3)
    @test isapprox(out[4], 0.5, atol = 1E-3)
    @test isapprox(out[5], 0.9740769841801067, atol = 1E-3)
    @test isapprox(out[6], 0.46211715726000974, atol = 1E-3)
    @test isapprox(out[7], 0.6224593312018546, atol = 1E-3)
    @test isapprox(out[8], 0.24491866240370913, atol = 1E-3)
    @test isapprox(out[9], 0.3333333333333333, atol = 1E-3)
    @test isapprox(out[10], 0.34573123063700656, atol = 1E-3)
    @test isapprox(out[11], 0.3112296656009273, atol = 1E-3)
    @test isapprox(out[12], 0.5, atol = 1E-3)
    @test isapprox(out[13], 0.5, atol = 1E-3)
    @test isapprox(out[14], -0.34657359027997264, atol = 1E-3)
    @test isapprox(out[15], 0.5, atol = 1E-3)
    @test isapprox(out[16], 0.5, atol = 1E-3)
    @test isapprox(out[17], 0.15, atol = 1E-3)
    @test isapprox(out[18], 0.5, atol = 1E-3)
    @test isapprox(out[19], 0.5, atol = 1E-3)
    @test isapprox(out[20], 0.5, atol = 1E-3)
    @test isapprox(out[21], 0.0024787521766663585, atol = 1E-3)
    @test isapprox(out[22], 1.3591409142295225, atol = 1E-3)
    @test isapprox(out[23], 0.5204998778130465, atol = 1E-3)
    @test isapprox(out[24], 0.4769362762044699, atol = 1E-3)
    @test isapprox(out[25], 0.4795001221869535, atol = 1E-3)
    @test isapprox(out[26], 0.4769362762044699, atol = 1E-3)
end

@testset "Display Testset" begin
    m = EAGO.Optimizer()
    MOI.set(m, MOI.RawParameter("verbosity"), 2)
    @test_nowarn EAGO.print_solution!(m._global_optimizer)
    @test_nowarn EAGO.print_results!(m._global_optimizer, true)
    @test_nowarn EAGO.print_results!(m._global_optimizer, false)
    @test_nowarn EAGO.print_iteration!(m._global_optimizer)
    @test_nowarn EAGO.print_node!(m._global_optimizer)
end

#=
@testset "User Defined Function Scrubber" begin
    gamma1_x1(z) = z[1]*(1253/z[3])/(1 + 2.62*(z[1]/z[2]))^2
    gamma2_x2(z) = z[2]*(479/z[3])/(1 + 0.382*(z[2]/z[1]))^2

    function cons_1ex(z...)
        temp = ForwardDiff.gradient(z -> log(gamma1_x1(z)), collect(z))
        temp[1]
    end

    unity(x) = x::Float64
    function cons_2ex(z...)
        temp = ForwardDiff.gradient(z -> log(gamma2_x2(z)), collect(z))
        unity(temp[2])
    end

    # Define the JuMP model and solve
    m = Model(with_optimizer(EAGO.Optimizer, presolve_scrubber_flag = true))
    register(m, :cons_1ex, 3, cons_1ex, autodiff = true)
    register(m, :cons_2ex, 3, cons_2ex, autodiff = true)
    @variable(m, 0.01 <= x1 <= 0.99)
    @variable(m, 0.01 <= x2 <= 0.99)
    @variable(m, 363.15 <= T <= 398.15)
    @constraint(m, x1 + x2 == 1.0)
    @NLexpression(m, P1, 1.33*exp(11.83572 - 4169.84/(T - 17.665)))
    @NLexpression(m, P2, 1.33*exp(11.33986 - 3724.523/(T - 69.854)))

    @NLconstraint(m, cons_1ex(x1,x2,T)*P1 + cons_2ex(x1,x2,T)*P2 == 1.02)
    @NLconstraint(m, cons_1ex(x1,x2,T)*P1/1.02 >= 0.95)
    @NLconstraint(m, cons1, cons_1ex(x1, x2, T) >= 0.001)
    @NLconstraint(m, cons2, cons_2ex(x1, x2, T) >= 0.001)
    @NLobjective(m, Min, T)
    optimize!(m)
    @test MOI.INFEASIBILITY_CERTIFICATE === primal_status(m)
end
=#

max_stars_repo_name: PSORLab/RSActivationFunctions

max_issues_repo_name: PSORLab/RSActivationFunctions

max_forks_repo_name: PSORLab/RSActivationFunctions


text:
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md

using JuliaFEM, Test

# test continuum 3d linear elasticity with surface load

X = Dict(1 => [0.0, 0.0, 0.0],
         2 => [1.0, 0.0, 0.0],
         3 => [1.0, 1.0, 0.0],
         4 => [0.0, 1.0, 0.0],
         5 => [0.0, 0.0, 1.0],
         6 => [1.0, 0.0, 1.0],
         7 => [1.0, 1.0, 1.0],
         8 => [0.0, 1.0, 1.0])

element1 = Element(Hex8, (1, 2, 3, 4, 5, 6, 7, 8))
element2 = Element(Quad4, (5, 6, 7, 8))
update!((element1, element2), "geometry", X)
update!(element1, "youngs modulus", 288.0)
update!(element1, "poissons ratio", 1/3)
update!(element2, "displacement traction force 3", 288.0)
update!(element1, "displacement load 3", 576.0)

problem = Problem(Elasticity, "solve continuum block", 3)
problem.properties.finite_strain = false
add_elements!(problem, element1, element2)

symxy = Element(Quad4, (1, 2, 3, 4))
symxz = Element(Quad4, (1, 2, 6, 5))
symyz = Element(Quad4, (1, 4, 8, 5))
update!([symxy, symxz, symyz], "geometry", X)
update!(symyz, "displacement 1", 0.0)
update!(symxz, "displacement 2", 0.0)
update!(symxy, "displacement 3", 0.0)

bc = Problem(Dirichlet, "symmetry boundary conditions", 3, "displacement")
add_elements!(bc, symxy, symxz, symyz)

analysis = Analysis(Linear)
add_problems!(analysis, problem, bc)
run!(analysis)

u = element1("displacement", [1.0, 1.0, 1.0], 0.0)
u_expected = 2.0 * [-1/3, -1/3, 1.0]
@debug("displacement at tip", u, u_expected)
@test isapprox(u, u_expected)

# run similar analysis for different meshes

function solve_rod_model_elasticity(eltype)
    fn = @__DIR__() * "/testdata/rod_short.med"
    mesh = aster_read_mesh(fn, eltype)
    element_sets = join(keys(mesh.element_sets), ", ")
    @debug("element sets", element_sets)
    p1 = Problem(Elasticity, "rod", 3)
    p2 = Problem(Elasticity, "trac", 3)
    p3 = Problem(Dirichlet, "fixed", 3, "displacement")
    p4 = Problem(Dirichlet, "fixed", 3, "displacement")
    p5 = Problem(Dirichlet, "fixed", 3, "displacement")
    p1_elements = create_elements(mesh, "ROD")
    p2_elements = create_elements(mesh, "FACE2")
    p3_elements = create_elements(mesh, "FACE1")
    p4_elements = create_elements(mesh, "FACE3")
    p5_elements = create_elements(mesh, "FACE5")
    update!(p1_elements, "youngs modulus", 96.0)
    update!(p1_elements, "poissons ratio", 1/3)
    update!(p2_elements, "displacement traction force 1", 96.0)
    update!(p3_elements, "displacement 1", 0.0)
    update!(p4_elements, "displacement 2", 0.0)
    update!(p5_elements, "displacement 3", 0.0)
    add_elements!(p1, p1_elements)
    add_elements!(p2, p2_elements)
    add_elements!(p3, p3_elements)
    add_elements!(p4, p4_elements)
    add_elements!(p5, p5_elements)
    analysis = Analysis(Linear)
    add_problems!(analysis, p1, p2, p3, p4, p5)
    run!(analysis)
    u = p1("displacement", 0.0)
    u_max = maximum(maximum(ui for ui in values(u)))
    @debug("analysis of block", eltype, u_max)
    return u_max
end

@test isapprox(solve_rod_model_elasticity("Tet4"), 0.2)
@test isapprox(solve_rod_model_elasticity("Tet10"), 0.2)
@test isapprox(solve_rod_model_elasticity("Hex8"), 0.2)
@test isapprox(solve_rod_model_elasticity("Hex20"), 0.2)
@test isapprox(solve_rod_model_elasticity("Hex27"), 0.2)

ext: jl


text:
# Autogenerated wrapper script for SuiteSparse_jll for x86_64-apple-darwin
export libamd, libbtf, libcamd, libccolamd, libcholmod, libcolamd, libklu, libldl, librbio, libspqr, libsuitesparseconfig, libumfpack

using libblastrampoline_jll
JLLWrappers.@generate_wrapper_header("SuiteSparse")
JLLWrappers.@declare_library_product(libamd, "@rpath/libamd.2.dylib")
JLLWrappers.@declare_library_product(libbtf, "@rpath/libbtf.1.dylib")
JLLWrappers.@declare_library_product(libcamd, "@rpath/libcamd.2.dylib")
JLLWrappers.@declare_library_product(libccolamd, "@rpath/libccolamd.2.dylib")
JLLWrappers.@declare_library_product(libcholmod, "@rpath/libcholmod.3.dylib")
JLLWrappers.@declare_library_product(libcolamd, "@rpath/libcolamd.2.dylib")
JLLWrappers.@declare_library_product(libklu, "@rpath/libklu.1.dylib")
JLLWrappers.@declare_library_product(libldl, "@rpath/libldl.2.dylib")
JLLWrappers.@declare_library_product(librbio, "@rpath/librbio.2.dylib")
JLLWrappers.@declare_library_product(libspqr, "@rpath/libspqr.2.dylib")
JLLWrappers.@declare_library_product(libsuitesparseconfig, "@rpath/libsuitesparseconfig.5.dylib")
JLLWrappers.@declare_library_product(libumfpack, "@rpath/libumfpack.5.dylib")
function __init__()
    JLLWrappers.@generate_init_header(libblastrampoline_jll)
    JLLWrappers.@init_library_product(
        libamd,
        "lib/libamd.2.4.6.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libbtf,
        "lib/libbtf.1.2.6.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libcamd,
        "lib/libcamd.2.4.6.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libccolamd,
        "lib/libccolamd.2.9.6.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libcholmod,
        "lib/libcholmod.3.0.14.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libcolamd,
        "lib/libcolamd.2.9.6.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libklu,
        "lib/libklu.1.3.8.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libldl,
        "lib/libldl.2.2.6.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        librbio,
        "lib/librbio.2.2.6.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libspqr,
        "lib/libspqr.2.0.9.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libsuitesparseconfig,
        "lib/libsuitesparseconfig.5.10.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libumfpack,
        "lib/libumfpack.5.7.9.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
"""
    rex = atomregex(chemicalsymbol)

Create a regular expression for detecting how many atoms of type `chemicalsymbol` are in a molecular formula.

# Examples

The formula for calcium bicarbonate is Ca(HCO3)2, i.e., C2CaH2O6.

```jldoctest; setup=(using PubChemCrawler)
julia> match(atomregex("C"), "C2CaH2O6")
RegexMatch("C2", 1="2")

julia> match(atomregex("Ca"), "C2CaH2O6")
RegexMatch("Ca", 1="")

julia> match(atomregex("H"), "C2CaH2O6")
RegexMatch("H2", 1="2")

julia> match(atomregex("O"), "C2CaH2O6")
RegexMatch("O6", 1="6")
```

Note that the regex for `"C"` does not match `"Ca"`.
"""
atomregex(str) = Regex("$str(?![a-z])(\\d*)")

"""
    atomcounts = parse_formula(str::AbstractString)

Parse `str` as a chemical formula, return a list of `atom=>multiplicity` pairs.

# Example

```jldoctest; setup=(using PubChemCrawler)
julia> parse_formula("C2CaH2O6")
4-element Array{Pair{String,$Int},1}:
  "C" => 2
 "Ca" => 1
  "H" => 2
  "O" => 6
```
"""
function parse_formula(str::AbstractString)
    prs = Pair{String,Int}[]
    idx, l = 1, length(str)
    while idx <= l
        atom = str[idx:idx]
        idx = nextind(str, idx)
        if idx <= l
            c = str[idx]
            if islowercase(c)
                atom *= c
                idx = nextind(str, idx)
            end
        end
        idxn = idx
        while idx <= l && isnumeric(str[idx])
            idx = nextind(str, idx)
        end
        push!(prs, atom => idx == idxn ? 1 : parse(Int, str[idxn:idx-1]))
    end
    return prs
end

function canonicalize_properties(props)
    endswith(props, ',') && (props = props[1:end-1])
    endswith(props, '/') || (props *= '/')
    return props
end


text:
include("Hi.jl")

import DataFrames
import .Hi

println(DataFrames.DataFrame(A=1:5))

# 相对引用
Hi.greet()


text:
# second pass: collate dataset by individual IDs, forming contiguous records

using AMDB: data_path
using DocStringExtensions: SIGNATURES
import JLD2
using FileIO: save, load
using RaggedData: contiguous_invperm!, contiguous_ranges, ordered_counts
using LargeColumns: MmappedColumns, meta_path, get_columns
using ProgressMeter: @showprogress
using Base.Mmap: mmap, sync!

# load first pass
first_pass = MmappedColumns(data_path("first_pass"));
columns_first = get_columns(first_pass)
N = length(first_pass)

# load metadata
meta = load(meta_path(first_pass, "meta.jld2"))
counts = ordered_counts(columns_first[1], Int32)

# permute to make IDs contiguous
tmp = MmappedColumns(mktempdir(), N, Tuple{Int32}); # permutations are mmapped
ip = get_columns(tmp)[1];
contiguous_invperm!(ip, get_columns(first_pass)[1], counts)

collated = MmappedColumns(data_path("collated"), N, eltype(first_pass));

columns_collated = get_columns(collated)

"""
    $SIGNATURES

Apply inverse permutation `ip` from `source` to `dest`.
"""
function permute_column!(ip, source, dest)
    @assert eltype(source) == eltype(dest)
    println("permuting column of $(eltype(source))s...")
    @showprogress for i in indices(ip, 1)
        dest[ip[i]] = source[i]
    end
    sync!(dest)
end

for (source, dest) in zip(columns_first, columns_collated)
    permute_column!(ip, source, dest)
end

sync!(collated)

# indices are now contiguous, CHECK
ix = contiguous_ranges(counts);

# function check_contiguous(x, ix)
#     _same(x) = all(x[1] .== x[2:end])
#     for r in ix
#         xv = @view(x[r])
#         if !_same(xv)
#             println("non-contiguous at $r")
#             println(xv)
#         end
#     end
# end

# check_contiguous(get_columns(collated)[1], ix)

meta2 = copy(meta)
meta2[AMDB.META_IX] = ix

# save metadata, now it can be reused as is for the third pass and the data analysis
save(meta_path(collated, "meta.jld2"), meta2)


text:
# make sure the following examples don't crash

println("Testing that bad inputs don't cause a crash...")

# bad ranges
display(plot(1:10, xlog=true, xrange=(-10,10)))
display(plot(1:10, ylog=true, yrange=(-10,10)))

# no data in range
display(plot(1:4, xrange=(-10,-3)))
display(loglog(-10:-4))

# only a single point
display(plot([1]))
display(loglog([1]))

# empty table cells
t = Table(2,2)
t[1,1] = plot(1:3)
display(t)

# row & col vectors
x = 1:10
y = 1:10
display(plot(x',y'))
display(plot(x'',y''))

println("Done testing bad inputs")


text:
module MolecularPotentials
    using StaticArrays
    using Parameters
    include("AbstractMolecule.jl")
    include("AbstractPotential.jl")
    include("/home/heindelj/dev/julia_development/wally/src/molecule_tools/read_xyz.jl")
end

max_stars_repo_name: heindelj/MolecularPotentials

max_issues_repo_name: heindelj/MolecularPotentials

max_forks_repo_name: heindelj/MolecularPotentials


text:
using StringParserPEG

lispgrammar = Grammar("""
  start => expr { liftchild }
  expr => lst | atom

  lst => (-('(') & lstcontent & -(')')){ liftchild }
  atom => string | boolean | number | sym

  lstcontent => (expr & *((-(space) & expr){liftchild}) {"arguments"}) {"list"}
  string => (-('"') & r([^"]*)r & -('"')){ (r,v,f,l,c) -> c[1].value}
  boolean => ('#t' | '#f') { (r,v,f,l,c) -> v=="#t" }
  number => (-(space) & float){liftchild} | (-(space) & int){liftchild}
  sym => r([^() ]+)r {(r,v,f,l,c) -> getfield(Main,Symbol(v))}

  space => r([ \\n\\r\\t]*)r
""",standardrules)

toexpr(node, children, ::MatchRule{:arguments}) = children
function toexpr(node, children, ::MatchRule{:list})
  if length(children) == 1
    return children[1]
  else
    return children[1](children[2]...)
  end
end

data = "(println \"test: \" (* 10 (- 5 6)))"

println("AST")
println("===")
(ast, pos, error) = parse(lispgrammar, data)
println(ast)

println("RESULT")
println("======")
evaluated = transform(toexpr, ast)


text:
# z.jl
# Various forms of z-tests
#
# Copyright (C) 2016   John Myles White
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

export OneSampleZTest, TwoSampleZTest, EqualVarianceZTest,
    UnequalVarianceZTest

abstract type ZTest <: HypothesisTest end
abstract type TwoSampleZTest <: ZTest end

pvalue(x::ZTest; tail=:both) = pvalue(Normal(0.0, 1.0), x.z; tail=tail)

default_tail(test::ZTest) = :both

# confidence interval by inversion
function StatsBase.confint(x::ZTest, alpha::Float64=0.05; tail=:both)
    check_alpha(alpha)

    if tail == :left
        (-Inf, StatsBase.confint(x, alpha*2)[2])
    elseif tail == :right
        (StatsBase.confint(x, alpha*2)[1], Inf)
    elseif tail == :both
        q = cquantile(Normal(0.0, 1.0), alpha/2)
        (x.xbar-q*x.stderr, x.xbar+q*x.stderr)
    else
        throw(ArgumentError("tail=$(tail) is invalid"))
    end
end

## ONE SAMPLE Z-TEST

struct OneSampleZTest <: ZTest
    n::Int       # number of observations
    xbar::Real   # estimated mean
    stderr::Real # population standard error
    z::Real      # t-statistic
    μ0::Real     # mean under h_0
end

testname(::OneSampleZTest) = "One sample z-test"
population_param_of_interest(x::OneSampleZTest) = ("Mean", x.μ0, x.xbar) # parameter of interest: name, value under h0, point estimate

function show_params(io::IO, x::OneSampleZTest, ident="")
    println(io, ident, "number of observations:   $(x.n)")
    println(io, ident, "z-statistic:              $(x.z)")
    println(io, ident, "population standard error: $(x.stderr)")
end

"""
    OneSampleZTest(xbar::Real, stddev::Real, n::Int, μ0::Real = 0)

Perform a one sample z-test of the null hypothesis that `n` values with mean `xbar` and
population standard deviation `stddev`  come from a distribution with mean `μ0` against the
alternative hypothesis that the distribution does not have mean `μ0`.

Implements: [`pvalue`](@ref), [`confint`](@ref)
"""
function OneSampleZTest(xbar::Real, stddev::Real, n::Int, μ0::Real=0)
    stderr = stddev/sqrt(n)
    z = (xbar-μ0)/stderr
    OneSampleZTest(n, xbar, stderr, z, μ0)
end

"""
    OneSampleZTest(v::AbstractVector{T<:Real}, μ0::Real = 0)

Perform a one sample z-test of the null hypothesis that the data in vector `v` comes from
a distribution with mean `μ0` against the alternative hypothesis that the distribution
does not have mean `μ0`.

Implements: [`pvalue`](@ref), [`confint`](@ref)
"""
OneSampleZTest(v::AbstractVector{T}, μ0::Real=0) where {T<:Real} = OneSampleZTest(mean(v), std(v), length(v), μ0)

"""
    OneSampleZTest(x::AbstractVector{T<:Real}, y::AbstractVector{T<:Real}, μ0::Real = 0)

Perform a paired sample z-test of the null hypothesis that the differences between pairs of
values in vectors `x` and `y` come from a distribution with mean `μ0` against the
alternative hypothesis that the distribution does not have mean `μ0`.

Implements: [`pvalue`](@ref), [`confint`](@ref)
"""
function OneSampleZTest(x::AbstractVector{T}, y::AbstractVector{S}, μ0::Real=0) where {T<:Real, S<:Real}
    check_same_length(x, y)

    OneSampleZTest(x - y, μ0)
end

## TWO SAMPLE Z-TEST (EQUAL VARIANCE)

struct EqualVarianceZTest <: TwoSampleZTest
    n_x::Int     # number of observations
    n_y::Int     # number of observations
    xbar::Real   # estimated mean difference
    stderr::Real # population standard error
    z::Real      # z-statistic
    μ0::Real     # mean difference under h_0
end

function show_params(io::IO, x::TwoSampleZTest, ident="")
    println(io, ident, "number of observations:   [$(x.n_x),$(x.n_y)]")
    println(io, ident, "z-statistic:              $(x.z)")
    println(io, ident, "population standard error: $(x.stderr)")
end

testname(::EqualVarianceZTest) = "Two sample z-test (equal variance)"
population_param_of_interest(x::TwoSampleZTest) = ("Mean difference", x.μ0, x.xbar) # parameter of interest: name, value under h0, point estimate

"""
    EqualVarianceZTest(x::AbstractVector{T<:Real}, y::AbstractVector{T<:Real})

Perform a two-sample z-test of the null hypothesis that `x` and `y` come from distributions
with equal means and variances against the alternative hypothesis that the distributions
have different means but equal variances.

Implements: [`pvalue`](@ref), [`confint`](@ref)
"""
function EqualVarianceZTest(x::AbstractVector{T}, y::AbstractVector{S}, μ0::Real=0) where {T<:Real,S<:Real}
    nx, ny = length(x), length(y)
    xbar = mean(x) - mean(y)
    stddev = sqrt(((nx - 1) * var(x) + (ny - 1) * var(y)) / (nx + ny - 2))
    stderr = stddev * sqrt(1/nx + 1/ny)
    z = (xbar - μ0) / stderr
    EqualVarianceZTest(nx, ny, xbar, stderr, z, μ0)
end

## TWO SAMPLE Z-TEST (UNEQUAL VARIANCE)

struct UnequalVarianceZTest <: TwoSampleZTest
    n_x::Int     # number of observations
    n_y::Int     # number of observations
    xbar::Real   # estimated mean
    stderr::Real # empirical standard error
    z::Real      # z-statistic
    μ0::Real     # mean under h_0
end

testname(::UnequalVarianceZTest) = "Two sample z-test (unequal variance)"

"""
    UnequalVarianceZTest(x::AbstractVector{T<:Real}, y::AbstractVector{T<:Real})

Perform an unequal variance two-sample z-test of the null hypothesis that `x` and `y` come
from distributions with equal means against the alternative hypothesis that the
distributions have different means.

Implements: [`pvalue`](@ref), [`confint`](@ref)
"""
function UnequalVarianceZTest(x::AbstractVector{T}, y::AbstractVector{S}, μ0::Real=0) where {T<:Real,S<:Real}
    nx, ny = length(x), length(y)
    xbar = mean(x)-mean(y)
    varx, vary = var(x), var(y)
    stderr = sqrt(varx/nx + vary/ny)
    z = (xbar-μ0)/stderr
    UnequalVarianceZTest(nx, ny, xbar, stderr, z, μ0)
end


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

"""
    GitHash(ptr::Ptr{UInt8})

Construct a `GitHash` from a pointer to `UInt8` data containing the bytes of the
SHA-1 hash. The constructor throws an error if the pointer is null, i.e. equal to
`C_NULL`.
"""
function GitHash(ptr::Ptr{UInt8})
    if ptr == C_NULL
        throw(ArgumentError("NULL pointer passed to GitHash() constructor"))
    end
    ensure_initialized()
    oid_ptr = Ref(GitHash())
    ccall((:git_oid_fromraw, :libgit2), Cvoid, (Ptr{GitHash}, Ptr{UInt8}), oid_ptr, ptr)
    return oid_ptr[]
end

"""
    GitHash(id::Vector{UInt8})

Construct a `GitHash` from a vector of $OID_RAWSZ bytes.
"""
function GitHash(id::Array{UInt8,1})
    if length(id) != OID_RAWSZ
        throw(ArgumentError("invalid raw buffer size"))
    end
    return GitHash(pointer(id))
end

"""
    GitHash(id::AbstractString)

Construct a `GitHash` from a string of $OID_HEXSZ hexadecimal digits.
"""
function GitHash(id::AbstractString)
    bstr = String(id)
    len = sizeof(bstr)
    if len < OID_HEXSZ
        throw(ArgumentError("Input string is too short, use `GitShortHash` for partial hashes"))
    end
    ensure_initialized()
    oid_ptr = Ref{GitHash}()
    @check ccall((:git_oid_fromstrn, :libgit2), Cint,
              (Ptr{GitHash}, Ptr{UInt8}, Csize_t), oid_ptr, bstr, len)
    return oid_ptr[]
end

"""
    GitShortHash(buf::Buffer)

Construct a `GitShortHash` from the data stored in the given [`Buffer`](@ref).
"""
function GitShortHash(buf::Buffer)
    ensure_initialized()
    oid_ptr = Ref{GitHash}()
    @check ccall((:git_oid_fromstrn, :libgit2), Cint,
              (Ptr{GitHash}, Ptr{UInt8}, Csize_t), oid_ptr, buf.ptr, buf.size)
    GitShortHash(oid_ptr[], buf.size)
end

"""
    GitShortHash(id::AbstractString)

Construct a `GitShortHash` from a string of at most $OID_HEXSZ hexadecimal digits.
"""
function GitShortHash(id::AbstractString)
    ensure_initialized()
    bstr = String(id)
    len = sizeof(bstr)
    oid_ptr = Ref{GitHash}()
    @check ccall((:git_oid_fromstrn, :libgit2), Cint,
              (Ptr{GitHash}, Ptr{UInt8}, Csize_t), oid_ptr, bstr, len)
    GitShortHash(oid_ptr[], len)
end

"""
    @githash_str -> AbstractGitHash

Construct a git hash object from the given string, returning a `GitShortHash` if
the string is shorter than $OID_HEXSZ hexadecimal digits, otherwise a `GitHash`.

# Examples
```jldoctest
julia> LibGit2.githash"d114feb74ce633"
GitShortHash("d114feb74ce633")

julia> LibGit2.githash"d114feb74ce63307afe878a5228ad014e0289a85"
GitHash("d114feb74ce63307afe878a5228ad014e0289a85")
```
"""
macro githash_str(id)
    bstr = String(id)
    if sizeof(bstr) < OID_HEXSZ
        GitShortHash(id)
    else
        GitHash(id)
    end
end

"""
    GitHash(ref::GitReference)

Get the identifier (`GitHash`) of the object referred to by the direct reference
`ref`. Note: this does not work for symbolic references; in such cases use
`GitHash(repo::GitRepo, ref_name::AbstractString)` instead.
"""
function GitHash(ref::GitReference)
    isempty(ref) && return GitHash()
    reftype(ref) != Consts.REF_OID && return GitHash()
    ensure_initialized()
    GC.@preserve ref begin
        oid_ptr = ccall((:git_reference_target, :libgit2), Ptr{UInt8}, (Ptr{Cvoid},), ref.ptr)
        oid_ptr == C_NULL && return GitHash()
        oid = GitHash(oid_ptr)
    end
    return oid
end

"""
    GitHash(repo::GitRepo, ref_name::AbstractString)

Get the identifier (`GitHash`) of the object referred to by reference specified by
`ref_name`.
"""
function GitHash(repo::GitRepo, ref_name::AbstractString)
    isempty(repo) && return GitHash()
    ensure_initialized()
    oid_ptr  = Ref(GitHash())
    @check ccall((:git_reference_name_to_id, :libgit2), Cint,
                    (Ptr{GitHash}, Ptr{Cvoid}, Cstring),
                     oid_ptr, repo.ptr, ref_name)
    return oid_ptr[]
end

"""
    GitHash(obj::GitObject)

Get the identifier (`GitHash`) of `obj`.
"""
function GitHash(obj::GitObject)
    ensure_initialized()
    GitHash(ccall((:git_object_id, :libgit2), Ptr{UInt8}, (Ptr{Cvoid},), obj.ptr))
end

==(obj1::GitObject, obj2::GitObject) = GitHash(obj1) == GitHash(obj2)

"""
    GitShortHash(obj::GitObject)

Get a shortened identifier (`GitShortHash`) of `obj`. The minimum length (in characters)
is determined by the `core.abbrev` config option, and will be of sufficient length to
unambiuously identify the object in the repository.
"""
function GitShortHash(obj::GitObject)
    ensure_initialized()
    buf_ref = Ref(Buffer())
    @check ccall((:git_object_short_id, :libgit2), Cint,
                 (Ptr{Buffer},Ptr{Cvoid}), buf_ref, obj.ptr)
    sid = GitShortHash(buf_ref[])
    free(buf_ref)
    return sid
end

"""
    raw(id::GitHash) -> Vector{UInt8}

Obtain the raw bytes of the [`GitHash`](@ref) as a vector of length $OID_RAWSZ.
"""
raw(id::GitHash) = collect(id.val)

function Base.print(io::IO, id::GitHash)
    for i in id.val
        print(io, string(i, base = 16, pad = 2))
    end
end
Base.string(id::GitShortHash) = string(id.hash)[1:id.len]

Base.show(io::IO, id::GitHash) = print(io, "GitHash(\"$(string(id))\")")
Base.show(io::IO, id::GitShortHash) = print(io, "GitShortHash(\"$(string(id))\")")

Base.hash(id::GitHash, h::UInt) = hash(id.val, h)

function Base.cmp(id1::GitHash, id2::GitHash)
    ensure_initialized()
    Int(ccall((:git_oid_cmp, :libgit2), Cint,
              (Ptr{GitHash}, Ptr{GitHash}),
              Ref(id1), Ref(id2)))
end
function Base.cmp(id1::GitShortHash, id2::GitShortHash)
    ensure_initialized()
    # shortened hashes appear at the beginning of the order, i.e.
    # 000 < 01 < 010 < 011 < 0112
    c = Int(ccall((:git_oid_ncmp, :libgit2), Cint,
                  (Ptr{GitHash}, Ptr{GitHash}, Csize_t),
                  Ref(id1.hash), Ref(id2.hash), min(id1.len, id2.len)))
    return c == 0 ? cmp(id1.len, id2.len) : c
end
Base.cmp(id1::GitHash, id2::GitShortHash) = cmp(GitShortHash(id1, OID_HEXSZ), id2)
Base.cmp(id1::GitShortHash, id2::GitHash) = cmp(id1, GitShortHash(id2, OID_HEXSZ))

==(id1::GitHash, id2::GitHash) = cmp(id1, id2) == 0
Base.isless(id1::AbstractGitHash, id2::AbstractGitHash)  = cmp(id1, id2) < 0

"""
    iszero(id::GitHash) -> Bool

Determine whether all hexadecimal digits of the given [`GitHash`](@ref) are zero.
"""
function iszero(id::GitHash)
    for i in 1:OID_RAWSZ
        id.val[i] != zero(UInt8) && return false
    end
    return true
end

Base.zero(::Type{GitHash}) = GitHash()

max_stars_repo_name: greimel/julia

max_issues_repo_name: greimel/julia

max_forks_repo_name: greimel/julia


text:
using Documenter, RANSAC

makedocs(sitename="RANSAC.jl",
        pages=["Home" => "index.md",
                "Example" => "example.md",
                "Efficient RANSAC" => "ransac.md",
                "API" => "api.md",
                "New primitive"=>"newprimitive.md"])

deploydocs(repo="github.com/cserteGT3/RANSAC.jl.git")


text:
### A Pluto.jl notebook ###
# v0.11.14

using Markdown
using InteractiveUtils

# ╔═╡ b36fb690-3447-11eb-3629-edbddae4ebd1
arrivalStr,shuttleIds = readlines("./input.txt");

# ╔═╡ 47b5c4a0-3db9-11eb-3576-97091f1bf011
begin
	arrival = parse(Int,arrivalStr)
	shuttles = NamedTuple{(:shuttle, :delay),Tuple{Int64,Int64}}[]
	for (index,value) ∈ enumerate(split(shuttleIds,","))
		if value != "x"
			push!(shuttles, (shuttle = parse(Int,value), delay = mod(-1*(index-1),parse(Int,value))))
		end
	end
end

# ╔═╡ e1b52942-3db9-11eb-0e49-45d055bb09f0
shuttles

# ╔═╡ 3c75c812-3dbc-11eb-23a6-c53244ba0530
# Part 1
function part1(shuttles::Array{NamedTuple{(:shuttle, :delay),Tuple{Int64,Int64}}}, arrival::Int)
	waitTime, shuttleId = minimum(map( b -> (b.shuttle-arrival %b.shuttle, b.shuttle),shuttles))
	waitTime * shuttleId
end

# ╔═╡ d4e0a3d2-3dbd-11eb-19b9-5538a2737649
@show part1(shuttles,arrival)

# ╔═╡ 5edd004e-3dbe-11eb-1eba-477618416d09
# Chinese Reminder
# https://rosettacode.org/wiki/Chinese_remainder_theorem

function part2(shuttles::Array{NamedTuple{(:shuttle, :delay),Tuple{Int64,Int64}}})
	Π = prod(s -> s.shuttle, shuttles)
	sol = sum(s -> s.delay * (Π ÷ s.shuttle) * invmod(Π ÷ s.shuttle, s.shuttle),shuttles)
	mod(sol,Π)
end

# ╔═╡ 3e018d12-3dbf-11eb-1b42-e98c778fbb50
@show part2(shuttles)

# ╔═╡ Cell order:
# ╠═b36fb690-3447-11eb-3629-edbddae4ebd1
# ╠═47b5c4a0-3db9-11eb-3576-97091f1bf011
# ╠═e1b52942-3db9-11eb-0e49-45d055bb09f0
# ╠═3c75c812-3dbc-11eb-23a6-c53244ba0530
# ╠═d4e0a3d2-3dbd-11eb-19b9-5538a2737649
# ╠═5edd004e-3dbe-11eb-1eba-477618416d09
# ╠═3e018d12-3dbf-11eb-1b42-e98c778fbb50


text:
"""
  Define a point type
"""
struct MyPoint
        x::Float64
        y::Float64
end

Base.:+(p1::MyPoint,p2::MyPoint) = MyPoint( p1.x+p2.x, p1.y+p2.y )

Base.:-(p1::MyPoint,p2::MyPoint) = MyPoint( p1.x-p2.x, p1.y-p2.y )

function MyPoint(p::MyPoint)
        MyPoint(p.x, p.y)
end


text:
mutable struct Particle{T<:AbstractFloat}
    x::Vector{T}
    v::Vector{T}
    p::Vector{T}

    fx::T
    fp::T

    function Particle{T}(nDims::Integer) where {T}
        if nDims < 0
            throw(ArgumentError("nDims cannot be less than 0."))
        end
        return new{T}(Vector{T}(undef, nDims),
                      Vector{T}(undef, nDims),
                      Vector{T}(undef, nDims),
                      T(0.0), T(0.0))
    end
 end

function Particle{T}(::UndefInitializer) where {T}
    return Particle{T}(Vector{T}(undef, 0),
                    Vector{T}(undef, 0),
                    Vector{T}(undef, 0),
                    T(0.0), T(0.0))
end

# Particle methods
Base.length(p::Particle) = length(p.x)


text:
# see readme for module documentation
module PyAMG

export RugeStubenSolver,
       SmoothedAggregationSolver,
       solve, solve!, aspreconditioner,
       set_kwargs!

using PyCall

# @pyimport scipy.sparse as scipy_sparse
# @pyimport pyamg
scipy_sparse = pyimport_conda("scipy.sparse", "scipy")
pyamg = pyimport_conda("pyamg", "pyamg")

"""
`py_csc(A::SparseMatrixCSC) -> PyObject`

Takes a Julia CSC matrix and converts it into `PyObject`, which stores a
`scipy.sparse.csc_matrix`.
"""
function py_csc(A::SparseMatrixCSC)
   # create an empty sparse matrix in Python
   Apy = scipy_sparse[:csc_matrix](size(A))
   # write the values and indices
   Apy[:data] = copy(A.nzval)
   Apy[:indices] = A.rowval - 1
   Apy[:indptr] = A.colptr - 1
   return Apy
end

"""
`py_csr(A::SparseMatrixCSC) -> PyObject`

Takes a Julia CSC matrix and converts it into `PyObject`, which stored a
`scipy.sparse.csr_matrix`. (Note: it first converts it to `csc_matrix`, then
calls `tocsr()`)

TODO: this seems extremely inefficient and should at some point be fixed.
"""
py_csr(A::SparseMatrixCSC) = py_csc(A)[:tocsr]()

"""
`type AMGSolver{T}`

Encapsulates the AMG solver types implemented in PyAMG.

Initialise using
```
using PyAMG
amg_rs = RugeStubenSolver(A)
amg_sa = SmoothedAggregationSolver(A)
```
To solve Ax = b:
```
x = solve(amg, b, tol=1e-6, accel="cg")
```
To apply a single MG cycle:
```
x = amg \ b
```
"""
type AMGSolver{T}
    po::PyObject
    id::T
    kwargs::Vector
    A::SparseMatrixCSC
end

type RugeStuben end
type SmoothedAggregation end
typealias RugeStubenSolver AMGSolver{RugeStuben}
typealias SmoothedAggregationSolver AMGSolver{SmoothedAggregation}

function set_kwargs!(amg::AMGSolver; kwargs...)
   amg.kwargs = kwargs
end

"""
`RugeStubenSolver(A::SparseMatrixCSC, kwargs...)`:

Create a Ruge Stuben instance of `AMGSolver`; wraps
`pyamg.ruge_stuben_solver`; see `pyamg.ruge_stuben_solver?`
for keyword arguments.  See `?AMGSolver` for usage.
"""
RugeStubenSolver(A::SparseMatrixCSC; kwargs...) =
   AMGSolver(pyamg[:ruge_stuben_solver](py_csr(A)),
             RugeStuben(), kwargs, A)

"""
`SmoothedAggregationSolver(A::SparseMatrixCSC, kwargs...)`

Wrapper for `pyamg.smoothed_aggregation_solver`; see `pyamg.ruge_stuben_solver?`
for keyword arguments. See `?AMGSolver` for usage.
"""
SmoothedAggregationSolver(A::SparseMatrixCSC; kwargs...) =
   AMGSolver(pyamg[:smoothed_aggregation_solver](py_csr(A)),
             SmoothedAggregation(), kwargs, A)

"""
`solve(A::SparseMatrixCSC, b::Vector; kwargs...)`:

PyAMG's 'blackbox' solver. See `pyamg.solve?` for `kwargs`.
"""
solve(A::SparseMatrixCSC, b::Vector; kwargs...) =
    pyamg[:solve]( py_csr(A), b; kwargs... )

"""
`solve(amg::AMGSolver, b, kwargs...)`

Returns a `Vector` with the result of the AMG solver. The keyword
arguments can either be passed directly, or can be stored in
`amd` via `set_kwargs!`.

### `kwargs`  (copy-pasted from Python docs)

* `x0` : Initial guess.
* `tol` : Stopping criteria: relative residual r[k]/r[0] tolerance.
* `maxiter` : Stopping criteria: maximum number of allowable iterations.
* `cycle` : {"V","W","F","AMLI"}
    Type of multigrid cycle to perform in each iteration.
* `accel` : Defines acceleration method.  Can be a string such as "cg"
    or "gmres" which is the name of an iterative solver in
    `pyamg.krylov` (preferred) or scipy.sparse.linalg.isolve.
    If accel is not a string, it will be treated like a function
    with the same interface provided by the iterative solvers in SciPy.
         (the function version is not tested in Julia!)
* `callback` : User-defined function called after each iteration.  It is
    called as callback(xk) where xk is the k-th iterate vector.
         (also not tested in Julia!)
* `residuals` : List to contain residual norms at each iteration.
"""
solve(amg::AMGSolver, b; kwargs...) = amg.po[:solve](b; amg.kwargs..., kwargs...)

######### Capability to use PyAMG.jl as a preconditioner for
######### nonlinear optimisation, sampling, etc

# TODO: the following 4 methods still need to be tested

import Base.\, Base.*
\(amg::AMGSolver, b::Vector) = solve(amg, b)
*(amg::AMGSolver, x::Vector) = amg.A * x

import Base.A_ldiv_B!, Base.A_mul_B!
Base.A_ldiv_B!(x, amg::AMGSolver, b) = copy!(x, amg \ b)
Base.A_mul_B!(b, amg::AMGSolver, x) = A_mul_B!(b, amg.A, x)

##############################################################################
######### Capability to use PyAMG.jl as a preconditioner for
######### iterative linear algebra

"""
`type AMGPreconditioner`

returned by `aspreconditioner(amg)`, when `amg` is of type `AMGSolver`.
This stores `PyObject` that acts as a linear operator. This type
should be used when `PyAMG` should typically be used as a preconditioner for
iterative linear algebra.

Overloaded methods that can be used with an `AMGPreconditioner` are
`\`, `*`, `A_ldiv_B!`, `A_mul_B!`
"""
type AMGPreconditioner
  po::PyObject
  A::SparseMatrixCSC
end

"""
`aspreconditioner(amg::AMGSolver; kwargs=...)`

returns an `M::AMGPreconditioner` object that is suitable for usage
as a preconditioner for iterative linear algebra.

If `x` is a vector, then `M \ x` denotes application of the
preconditioner (i.e. 1 MG cycle), while `M * x` denotes
multiplication with the original matrix from which `amg` was constructed.

### kwargs:
cycle : {'V','W','F','AMLI'}
    Type of multigrid cycle to perform in each iteration.
"""
aspreconditioner(amg::AMGSolver; kwargs...) =
      AMGPreconditioner(amg.po[:aspreconditioner](kwargs...), amg.A)

\(amg::AMGPreconditioner, b::Vector) = amg.po[:matvec](b)
*(amg::AMGPreconditioner, x::Vector) = amg.A * x

Base.A_ldiv_B!(x, amg::AMGPreconditioner, b) = copy!(x, amg \ b)
Base.A_mul_B!(b, amg::AMGPreconditioner, x) = A_mul_B!(b, amg.A, x)

"""
`diagnostics(A)`:

Wrapper for `solver_diagnostics`, which is part of PyAMG-Examples. To use it
clone [PyAMG-Examples](https://github.com/pyamg/pyamg-examples), then make the
call to `diagnostics` from a directory where `solver_diagnostics.py` is located
(e.g., from `pyamg-examples/solver_diagnostics`, but the file could be copied
anywhere.

If succesful, `diagnostics(A)` will try a variety of parameter combinations for
`smoothed_aggregation_solver`, and write two files 'solver_diagnostic.txt' and
'solver_diagnostic.py' which contain information how to generate the best
solver.

This implementation is a really poor hack, and suggestions how to improve it
would be highly appreciated.
"""
function diagnostics(A::SparseMatrixCSC; kwargs...)
    # try to import solver_diagnostics
    try
        unshift!(PyVector(pyimport("sys")["path"]), "");
        @pyimport solver_diagnostics
    catch
        error("""
              I tried to @pyimport solver_diagnostics, but it fails.
              This is probably because `solver_diagnostics.py` is not
              in the current directory. Please see
              `?PyAMG.diagonistics` on how to use this function.
              """)
    end

    # import has worked against expectations; call the diagnostics
    solver_diagnostics.solver_diagnostics(A; kwargs...)
end

end


text:
T=6
tsname="joMatrixCons"
@testset "$tsname" begin
for t=1:T # start test loop

    if t<=2
        (m,n)=(5,5)
    else
        (m,n)=(rand(3:7),rand(3:7))
    end

    if t%2==1
        tname="loop $t for real($m,$n)"
        a=rand()
        v=rand(n)
    else
        tname="loop $t for complex($m,$n)"
        a=rand(ComplexF64)
        v=rand(ComplexF64,n)
    end

    verbose && println("$tsname $tname")
    @testset "$m x $n" begin

    @test norm(elements(joZeros(m,n))-zeros(m,n)) < joTol
    @test norm(elements(transpose(joZeros(m,n)))-transpose(zeros(m,n))) < joTol
    @test norm(elements(adjoint(joZeros(m,n)))-adjoint(zeros(m,n))) < joTol
    @test norm(elements(conj(joZeros(m,n)))-conj(zeros(m,n))) < joTol

    @test norm(elements(joOnes(m,n))-ones(m,n)) < joTol
    @test norm(elements(transpose(joOnes(m,n)))-transpose(ones(m,n))) < joTol
    @test norm(elements(adjoint(joOnes(m,n)))-adjoint(ones(m,n))) < joTol
    @test norm(elements(conj(joOnes(m,n)))-conj(ones(m,n))) < joTol

    @test norm(elements(joConstants(m,n,a))-(a*ones(m,n))) < joTol
    @test norm(elements(transpose(joConstants(m,n,a)))-transpose(a*ones(m,n))) < joTol
    @test norm(elements(adjoint(joConstants(m,n,a)))-adjoint(a*ones(m,n))) < joTol
    @test norm(elements(conj(joConstants(m,n,a)))-(conj(a*ones(m,n)))) < joTol

    @test norm(elements(joDirac(m))-jo_eye(m)) < joTol
    @test norm(elements(transpose(joDirac(m)))-transpose(jo_eye(m))) < joTol
    @test norm(elements(adjoint(joDirac(m)))-adjoint(jo_eye(m))) < joTol
    @test norm(elements(conj(joDirac(m)))-conj(jo_eye(m))) < joTol

    @test norm(elements(joEye(m,n))-jo_eye(m,n)) < joTol
    @test norm(elements(transpose(joEye(m,n)))-transpose(jo_eye(m,n))) < joTol
    @test norm(elements(adjoint(joEye(m,n)))-adjoint(jo_eye(m,n))) < joTol
    @test norm(elements(conj(joEye(m,n)))-conj(jo_eye(m,n))) < joTol

    @test norm(elements(joDiag(v))-diagm(0=>v)) < joTol
    @test norm(elements(transpose(joDiag(v)))-transpose(diagm(0=>v))) < joTol
    @test norm(elements(adjoint(joDiag(v)))-adjoint(diagm(0=>v))) < joTol
    @test norm(elements(conj(joDiag(v)))-conj(diagm(0=>v))) < joTol

    #joVecConvert.jl
    if t%2==0

        @test norm(joReal(n)*v-real(v)) < joTol
        @test norm(transpose(joReal(n))*real(v)-complex.(real(v))) < joTol
        @test norm(adjoint(joReal(n))*real(v)-complex.(real(v))) < joTol
        @test norm(conj(joReal(n))*v-real(v)) < joTol

        @test norm(joImag(n)*v-imag(v)) < joTol
        @test norm(transpose(joImag(n))*imag(v)-complex.(0.,imag(v))) < joTol
        @test norm(adjoint(joImag(n))*imag(v)-complex.(0.,-imag(v))) < joTol
        @test norm(conj(joImag(n))*v-imag(-v)) < joTol

        @test norm(joConj(n)*v-conj(v)) < joTol
        @test norm(transpose(joConj(n))*v-conj(v)) < joTol
        @test norm(adjoint(joConj(n))*v-conj(v)) < joTol
        @test norm(conj(joConj(n))*v-conj(v)) < joTol

    end

    end

end # end test loop
end


text:
#################### PBDiagMat Block-Diagonal Matrices ####################

module PDMats2

  using SparseArrays: SparseMatrixCSC
  using LinearAlgebra: Cholesky, UpperTriangular
  using PDMats: AbstractPDMat

  import Base: +, *, /, \, size
  import LinearAlgebra: diag, cholesky, inv, logdet
  import PDMats: dim, invquad, invquad!, quad, quad!,
         whiten, whiten!, unwhiten, unwhiten!
  import SparseArrays: sparse

  export PBDiagMat

  #################### Types and Constructors ####################

  struct PBDiagMat <: AbstractPDMat{Float64}
    dim::Int
    mat::SparseMatrixCSC{Float64, Int}
    chol::Vector{Cholesky{Float64}}
    scale::Int
  end

  function PBDiagMat(v::Vector{Matrix{T}}, n::Integer=1) where {T<:Real}
    mat = spbdiagm(v, n)
    chol = map(cholesky, v)
    PBDiagMat(size(mat, 1), mat, chol, n)
  end

  function PBDiagMat(x::Matrix{T}, n::Integer=1) where {T<:Real}
    PBDiagMat(Matrix{T}[x], n)
  end

  #################### Base Methods ####################

  +(a::PBDiagMat, b::Matrix{Float64}) = a.mat + b
  +(a::Matrix{Float64}, b::PBDiagMat) = b + a

  *(a::PBDiagMat, c::Float64) = mapchol(x -> c * Matrix(x), a)
  /(a::PBDiagMat, c::Float64) = a * inv(c)
  *(c::Float64, a::PBDiagMat) = a * c

  *(a::PBDiagMat, x::StridedVecOrMat) = a.mat * x
  *(x::StridedVecOrMat, a::PBDiagMat) = x * a.mat

  \(a::PBDiagMat, x::StridedVecOrMat) = inv(a) * x
  \(a::Matrix{Float64}, b::PBDiagMat) = inv(a) * b

  dim(a::PBDiagMat) = a.dim
  size(a::PBDiagMat) = size(a.mat)
  size(a::PBDiagMat, i) = size(a)[i]

  diag(a::PBDiagMat) = diag(a.mat)
  Matrix(a::PBDiagMat) = Matrix(a.mat)
  inv(a::PBDiagMat) = mapchol(inv, a)
  logdet(a::PBDiagMat) = a.scale * mapreduce(logdet, +, a.chol)

  #################### Whiten and Unwhiten ####################

  function whiten(a::PBDiagMat, x::DenseVecOrMat{Float64})
    au_inv = map(ac -> inv(ac.U), a.chol)
    transpose(spbdiagm(au_inv, a.scale)) * x
  end

  function whiten!(a::PBDiagMat, x::DenseVecOrMat{Float64})
    x[:] = whiten(a, x)
  end

  function unwhiten(a::PBDiagMat, x::DenseVecOrMat{Float64})
    au = map(ac -> ac.U, a.chol)
    #At_mul_B(spbdiagm(au, a.scale), x)
    spbdiagm(au, a.scale)'*x
  end

  function unwhiten!(a::PBDiagMat, x::DenseVecOrMat{Float64})
    x[:] = unwhiten(a, x)
  end

  #################### Quadratic Forms ####################

  function quad!(r::Array{Float64}, a::PBDiagMat, x::Matrix{Float64})
    n = size(x, 2)
    length(r) == n || throw(ArgumentError("incompatible array length"))
    ax = a * x
    for j = 1:n
      r[j] = dot(a[:, j], ax[:, j])
    end
    r
  end

  quad(a::PBDiagMat, x::Vector{Float64}) = dot(x, a * x)

  function invquad!(r::Array{Float64}, a::PBDiagMat, x::Matrix{Float64})
    n = size(x, 2)
    length(r) == n || throw(ArgumentError("incompatible array length"))
    wx = whiten(a, x)
    for j = 1:n
      r[j] = sum(abs2, wx[:, j])
    end
    r
  end

  invquad(a::PBDiagMat, x::Vector{Float64}) = sum(abs2, whiten(a, x))

  #################### Auxiliary Functions ####################

  mapchol(f::Function, a::PBDiagMat) = PBDiagMat(map(f, a.chol), a.scale)

  const VecOfMatsOrUppers{T} = Union{Vector{Matrix{T}}, Vector{UpperTriangular{T, Matrix{T}}}}
  function spbdiagm(v::VecOfMatsOrUppers{T}, n::Integer=1) where {T<:Real}
    vn = [fill(v, n)...;]

    len = mapreduce(splength, +, vn)
    I = Array{Int}(undef, len)
    J = Array{Int}(undef, len)
    V = Array{Float64}(undef, len)

    k = 1
    offset = 0
    for x in vn
      m = size(x, 1)
      size(x, 2) == m || throw(ArgumentError("blocks are not square matrices"))
      for i in 1:m, j in 1:m
        if isnonzero(x, i, j)
          I[k] = offset + i
          J[k] = offset + j
          V[k] = x[i, j]
          k += 1
        end
      end
      offset += m
    end

    sparse(I, J, V, offset, offset)
  end

  splength(x::Matrix) = length(x)
  isnonzero(x::Matrix, i::Integer, j::Integer) = true

  function splength(x::UpperTriangular{T, Matrix{T}}) where {T}
    m, n = minmax(size(x)...)
    Int(m * (m + 1) / 2) + (n - m) * m
  end
  isnonzero(x::UpperTriangular{T, Matrix{T}}, i::Integer, j::Integer) where {T} =
    j >= i

end


text:
### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

# ╔═╡ 5cd8d60a-de87-429d-a851-a35c4a656b50
begin
	using Arrow
	using Chain
	using CSV
	using DataFrameMacros
	using DataFrames
	using PyCall
	using RCall
end

# ╔═╡ 3eb610ba-1470-11ec-3679-5d2c0f46c6a5
md"""
# Creating Arrow files in Julia

Data frames stored in [Arrow](https://arrow.apache.org) can be read using many different languages.
Julia provides very good facilities for creating Arrow files.

This notebook illustrates creating Arrow files from a CSV source and from RData or RDS files.

The conversion itself is straightforward.
It can be worthwhile doing some data cleaning and transformation during these conversion steps.

## Simple conversion

Consider the conversion of the CSV file `full_axb.csv` provided by Patricia Amillos.

First, load the packages to be used.
"""

# ╔═╡ de931048-9d8a-4752-a170-2b6179470dec
md"""
Read the CSV file and convert to a DataFrame.
"""

# ╔═╡ 96d01ab2-7b43-41c8-9780-bbd3bd6053e5
axb_data = CSV.read("../Patricia_Amillos/full_axb.csv", DataFrame)

# ╔═╡ df69d7d5-ea2d-4b37-804b-7c4eb5e3c91c
describe(axb_data)

# ╔═╡ 4432e088-5daa-46c5-b344-e5b28fe8df52
typeof.(eachcol(axb_data))

# ╔═╡ 35ba4c00-9962-46c2-9ad4-8d78e3b2c3b2
md"""
This DataFrame can be written to an Arrow file without modification.
"""

# ╔═╡ b3cfca7a-dd18-4fa9-b010-705458ccafa6
axb_full_arrow = Arrow.write("./data/full_axb.arrow", axb_data; compress=:zstd);

# ╔═╡ 867462e3-11ea-41b8-bc7a-4aa2fc4afe5b
md"""
[Zstandard](https://en.wikipedia.org/wiki/Zstandard) compression is quite effective in this case in reducing the file size.
The Arrow file is about 1/10 the size of the CSV file.
"""

# ╔═╡ 4e954cf5-ba20-497b-8bbc-3d84d5fb624d
filesize(axb_full_arrow)

# ╔═╡ 2983ed07-8790-412d-8ab7-52d1edf913cb
filesize("../Patricia_Amillos/full_axb.csv")

# ╔═╡ beebe630-2c4e-4c4b-9fbf-e97044dc4916
md"""
## Restoring the data frame from the arrow file

The function to read an Arrow file is `Arrow.Table` which produces a column table.
Often this table is converted to a `DataFrame` for further manipulation.
"""

# ╔═╡ b7269fc0-6e8e-4a6d-955e-a35ba2ab3406
axb_from_arrow = Arrow.Table(axb_full_arrow)

# ╔═╡ 3e9cda79-3843-4a8c-af4e-9334d63a982e
md"""
Also, behind the scenes, the `Arrow.write` function has converted some of the categorical covariates to more compact storage formats.
In Arrow terminology the storage type for a factor or categorical variable is called `DictEncoded`.
"""

# ╔═╡ 8429788b-f1a5-4e30-a913-80831c16ebe7
typeof.(axb_from_arrow)

# ╔═╡ 9b5dd88a-e51d-4892-b79e-9a12dd8750c8
md"""
## Conversion from RData or RDS

A `.RData` or `.rda` file can be loaded into R producing a new environment from which we extract the names of the objects that have been loaded.

The `somdemo.Rda` file was contributed by Athanassios Protopapas.
"""

# ╔═╡ e44aec27-78ad-47ce-b071-d1ec698808d5
R"""load("../Athanassios_Protopapas/somdemo.Rda")""";

# ╔═╡ 9a0e4476-df20-4ba3-80dd-d2f202083936
R"ls()"   # list the contents of the environment created in the previous call

# ╔═╡ fa931a48-eec5-4d48-bc38-733585fa6f98
@rget som # import this data frame into Julia under the same name

# ╔═╡ 973007a0-fddc-4aa2-a88d-c5967f3d77b4
som_arrow = Arrow.write("./data/somdemo.arrow", som; compress=:zstd);

# ╔═╡ c9027bc8-87bd-48c6-958e-f9e6f7d4d741
som_from_arrow = DataFrame(Arrow.Table(som_arrow))

# ╔═╡ 81d362bf-589c-4aa6-abab-2097b9a5f60c
md"""
## Basic writing and reading of arrow files

Writing and reading an arrow file in Julia is straightforward.
Typically the file is written as a compressed file and the Zstandard compression is quite effective on such files.
An uncompressed file gives maximal performance when reading an arrow file, because it can be [memory-mapped](https://en.wikipedia.org/wiki/Memory-mapped_file).
However, the uncompressed file requires more storage and more information to be transmitted when download the file, for example.

The structure of these files can be checked in R and in Python.
"""

# ╔═╡ 16418fe6-7fc2-47d2-92fb-2e4949c552fd
feather = pyimport("pyarrow.feather");

# ╔═╡ 62df14a1-52c5-4bcc-93ab-a8646dc67d7c
feather.read_table(som_arrow)

# ╔═╡ 44c58069-8c0f-469c-ad29-263b1174d5ca
md"""
This shows that the `orthaccu` column is being stored as a column of 64-bit integers when, in fact, it is a boolean value.
It would be better to convert it to `Bool` before storing it.

For R, first load the `arrow` and `tibble` packages then read the file.
For historical reasons this format is called "feather" in R.
"""

# ╔═╡ 4098bead-379d-42a4-b906-cc44967e9796
R"""
library("arrow")
library("tibble")
som <- read_feather($som_arrow)  # interpolate the file name from Julia
glimpse(som)
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Chain = "8be319e6-bccf-4806-a6f7-6fae938471bc"
DataFrameMacros = "75880514-38bc-4a95-a458-c2aea5a3a702"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
RCall = "6f49c342-dc21-5d91-9882-a32aef131414"

[compat]
Arrow = "~1.6.2"
CSV = "~0.9.1"
Chain = "~0.4.8"
DataFrameMacros = "~0.1.0"
DataFrames = "~1.2.2"
PyCall = "~1.92.3"
RCall = "~0.13.12"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Arrow]]
deps = ["ArrowTypes", "BitIntegers", "CodecLz4", "CodecZstd", "DataAPI", "Dates", "Mmap", "PooledArrays", "SentinelArrays", "Tables", "TimeZones", "UUIDs"]
git-tree-sha1 = "b00e6eaba895683867728e73af78a00218f0db10"
uuid = "69666777-d1a9-59fb-9406-91d4454c9d45"
version = "1.6.2"

[[ArrowTypes]]
deps = ["UUIDs"]
git-tree-sha1 = "a0633b6d6efabf3f76dacd6eb1b3ec6c42ab0552"
uuid = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
version = "1.2.1"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[BitIntegers]]
deps = ["Random"]
git-tree-sha1 = "f50b5a99aa6ff9db7bf51255b5c21c8bc871ad54"
uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1"
version = "0.2.5"

[[CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"]
git-tree-sha1 = "c907e91e253751f5840135f4c9deb1308273338d"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.9.1"

[[CategoricalArrays]]
deps = ["DataAPI", "Future", "JSON", "Missings", "Printf", "RecipesBase", "Statistics", "StructTypes", "Unicode"]
git-tree-sha1 = "1562002780515d2573a4fb0c3715e4e57481075e"
uuid = "324d7699-5711-5eae-9e2f-1d82baa6b597"
version = "0.10.0"

[[Chain]]
git-tree-sha1 = "cac464e71767e8a04ceee82a889ca56502795705"
uuid = "8be319e6-bccf-4806-a6f7-6fae938471bc"
version = "0.4.8"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "4ce9393e871aca86cc457d9f66976c3da6902ea7"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.4.0"

[[CodecLz4]]
deps = ["Lz4_jll", "TranscodingStreams"]
git-tree-sha1 = "59fe0cb37784288d6b9f1baebddbf75457395d40"
uuid = "5ba52731-8f18-5e0d-9241-30f10d1ec561"
version = "0.4.0"

[[CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

[[CodecZstd]]
deps = ["TranscodingStreams", "Zstd_jll"]
git-tree-sha1 = "d19cd9ae79ef31774151637492291d75194fc5fa"
uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2"
version = "0.7.0"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "4866e381721b30fac8dda4c8cb1d9db45c8d2994"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.37.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[Conda]]
deps = ["JSON", "VersionParsing"]
git-tree-sha1 = "299304989a5e6473d985212c28928899c74e9421"
uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d"
version = "1.5.2"

[[Crayons]]
git-tree-sha1 = "3f71217b538d7aaee0b69ab47d9b7724ca8afa0d"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.0.4"

[[DataAPI]]
git-tree-sha1 = "bec2532f8adb82005476c141ec23e921fc20971b"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.8.0"

[[DataFrameMacros]]
deps = ["DataFrames"]
git-tree-sha1 = "508d57ef7b78551cf69c2837d80af5017ce57217"
uuid = "75880514-38bc-4a95-a458-c2aea5a3a702"
version = "0.1.0"

[[DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "d785f42445b63fc86caa08bb9a9351008be9b765"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.2.2"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[ExprTools]]
git-tree-sha1 = "b7e3d17636b348f005f11040025ae8c6f645fe92"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.6"

[[FilePathsBase]]
deps = ["Dates", "Mmap", "Printf", "Test", "UUIDs"]
git-tree-sha1 = "0f5e8d0cb91a6386ba47bd1527b240bd5725fbae"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.10"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

[[IrrationalConstants]]
git-tree-sha1 = "f76424439413893a832026ca355fe273e93bce94"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.0"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "86197a8ecb06e222d66797b0c2d2f0cc7b69e42b"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.2"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Lz4_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "5d494bc6e85c4c9b626ee0cab05daa4085486ab1"
uuid = "5ced341a-0733-55b8-9ab6-a4889d929147"
version = "1.9.3+0"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "5a5bc6bf062f0f95e62d0fe0a2d99699fed82dd9"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.8"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "2ca267b08821e86c5ef4376cffed98a46c2cb205"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.1"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[Mocking]]
deps = ["ExprTools"]
git-tree-sha1 = "748f6e1e4de814b101911e64cc12d83a6af66782"
uuid = "78c3b35d-d492-501b-9361-3d52fe80e533"
version = "0.7.2"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "438d35d2d95ae2c5e8780b330592b6de8494e779"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.3"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "a193d6ad9c45ada72c14b731a318bedd3c2f00cf"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.3.0"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "0d1245a357cc61c8cd61934c07447aa569ff22e6"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.1.0"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[PyCall]]
deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"]
git-tree-sha1 = "169bb8ea6b1b143c5cf57df6d34d022a7b60c6db"
uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
version = "1.92.3"

[[RCall]]
deps = ["CategoricalArrays", "Conda", "DataFrames", "DataStructures", "Dates", "Libdl", "Missings", "REPL", "Random", "Requires", "StatsModels", "WinReg"]
git-tree-sha1 = "80a056277142a340e646beea0e213f9aecb99caa"
uuid = "6f49c342-dc21-5d91-9882-a32aef131414"
version = "0.13.12"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[Rmath]]
deps = ["Random", "Rmath_jll"]
git-tree-sha1 = "bf3188feca147ce108c76ad82c2792c57abe7b1f"
uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
version = "0.7.0"

[[Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "68db32dff12bb6127bac73c209881191bf0efbb7"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.3.0+0"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "54f37736d8934a12a200edea2f9206b03bdf3159"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.7"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[ShiftedArrays]]
git-tree-sha1 = "22395afdcf37d6709a5a0766cc4a5ca52cb85ea0"
uuid = "1277b4bf-5013-50f5-be3d-901d8477a67a"
version = "1.0.0"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[SpecialFunctions]]
deps = ["ChainRulesCore", "LogExpFunctions", "OpenSpecFun_jll"]
git-tree-sha1 = "a322a9493e49c5f3a10b50df3aedaf1cdb3244b7"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.6.1"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StatsAPI]]
git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.0.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "8cbbc098554648c84f79a463c9ff0fd277144b6c"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.10"

[[StatsFuns]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "46d7ccc7104860c38b11966dd1f72ff042f382e4"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "0.9.10"

[[StatsModels]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Printf", "ShiftedArrays", "SparseArrays", "StatsBase", "StatsFuns", "Tables"]
git-tree-sha1 = "3fa15c1f8be168e76d59097f66970adc86bfeb95"
uuid = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
version = "0.6.25"

[[StructTypes]]
deps = ["Dates", "UUIDs"]
git-tree-sha1 = "8445bf99a36d703a09c601f9a57e2f83000ef2ae"
uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
version = "1.7.3"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "368d04a820fe069f9080ff1b432147a6203c3c89"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.5.1"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[TimeZones]]
deps = ["Dates", "Future", "LazyArtifacts", "Mocking", "Pkg", "Printf", "RecipesBase", "Serialization", "Unicode"]
git-tree-sha1 = "6c9040665b2da00d30143261aea22c7427aada1c"
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
version = "1.5.7"

[[TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[VersionParsing]]
git-tree-sha1 = "80229be1f670524750d905f8fc8148e5a8c4537f"
uuid = "81def892-9a0e-5fdd-b105-ffc91e053289"
version = "1.2.0"

[[WeakRefStrings]]
deps = ["DataAPI", "Parsers"]
git-tree-sha1 = "4a4cfb1ae5f26202db4f0320ac9344b3372136b0"
uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
version = "1.3.0"

[[WinReg]]
deps = ["Test"]
git-tree-sha1 = "808380e0a0483e134081cc54150be4177959b5f4"
uuid = "1b915085-20d7-51cf-bf83-8f477d6f5128"
version = "0.3.1"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─3eb610ba-1470-11ec-3679-5d2c0f46c6a5
# ╠═5cd8d60a-de87-429d-a851-a35c4a656b50
# ╠═de931048-9d8a-4752-a170-2b6179470dec
# ╠═96d01ab2-7b43-41c8-9780-bbd3bd6053e5
# ╠═df69d7d5-ea2d-4b37-804b-7c4eb5e3c91c
# ╠═4432e088-5daa-46c5-b344-e5b28fe8df52
# ╟─35ba4c00-9962-46c2-9ad4-8d78e3b2c3b2
# ╠═b3cfca7a-dd18-4fa9-b010-705458ccafa6
# ╟─867462e3-11ea-41b8-bc7a-4aa2fc4afe5b
# ╠═4e954cf5-ba20-497b-8bbc-3d84d5fb624d
# ╠═2983ed07-8790-412d-8ab7-52d1edf913cb
# ╟─beebe630-2c4e-4c4b-9fbf-e97044dc4916
# ╠═b7269fc0-6e8e-4a6d-955e-a35ba2ab3406
# ╟─3e9cda79-3843-4a8c-af4e-9334d63a982e
# ╠═8429788b-f1a5-4e30-a913-80831c16ebe7
# ╟─9b5dd88a-e51d-4892-b79e-9a12dd8750c8
# ╠═e44aec27-78ad-47ce-b071-d1ec698808d5
# ╠═9a0e4476-df20-4ba3-80dd-d2f202083936
# ╠═fa931a48-eec5-4d48-bc38-733585fa6f98
# ╠═973007a0-fddc-4aa2-a88d-c5967f3d77b4
# ╠═c9027bc8-87bd-48c6-958e-f9e6f7d4d741
# ╟─81d362bf-589c-4aa6-abab-2097b9a5f60c
# ╠═16418fe6-7fc2-47d2-92fb-2e4949c552fd
# ╠═62df14a1-52c5-4bcc-93ab-a8646dc67d7c
# ╟─44c58069-8c0f-469c-ad29-263b1174d5ca
# ╠═4098bead-379d-42a4-b906-cc44967e9796
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002