text:
# Test name methods
@testset "Basics" begin
    # setup data
    m = InfiniteModel()
    @independent_parameter(m, a in [0, 1])
    @independent_parameter(m, b[1:3] in [0, 1])
    @dependent_parameters(m, c[1:2] in [0, 1])
    @independent_parameter(m, d in [0, 1])
    idx = InfiniteVariableIndex(1)
    num = Float64(0)
    func = (x) -> NaN
    info = VariableInfo{Float64, Float64, Float64, Function}(false, num, false,
                                     num, false, num, false, func, false, false)
    new_info = VariableInfo{Float64, Float64, Float64, Function}(true, 0., true,
                                          0., true, 0., true, func, true, false)
    var = InfiniteVariable(info, IC.VectorTuple(a, b[1:2], c, [b[3], d]),
                           [1:7...], [1:6...], true)
    object = VariableData(var, "var")
    vref = InfiniteVariableRef(m, idx)
    gvref = GeneralVariableRef(m, 1, InfiniteVariableIndex)
    bad_vref = InfiniteVariableRef(m, InfiniteVariableIndex(-1))
    # JuMP.owner_model
    @testset "JuMP.owner_model" begin
        @test owner_model(vref) === m
        @test owner_model(gvref) === m
    end
    # JuMP.index
    @testset "JuMP.index" begin
        @test index(vref) == idx
        @test index(gvref) == idx
    end
    # dispatch_variable_ref
    @testset "dispatch_variable_ref" begin
        @test dispatch_variable_ref(m, idx) == vref
        @test dispatch_variable_ref(gvref) == vref
    end
    # _add_data_object
    @testset "_add_data_object" begin
        @test InfiniteOpt._add_data_object(m, object) == idx
    end
    # _data_dictionary
    @testset "_data_dictionary" begin
        @test InfiniteOpt._data_dictionary(m, InfiniteVariable) === m.infinite_vars
        @test InfiniteOpt._data_dictionary(vref) === m.infinite_vars
        @test InfiniteOpt._data_dictionary(gvref) === m.infinite_vars
    end
    # JuMP.is_valid
    @testset "JuMP.is_valid" begin
        @test is_valid(m, vref)
        @test is_valid(m, gvref)
    end
    # _data_object
    @testset "_data_object" begin
        @test InfiniteOpt._data_object(vref) === object
        @test InfiniteOpt._data_object(gvref) === object
        @test_throws ErrorException InfiniteOpt._data_object(bad_vref)
    end
    # _core_variable_object
    @testset "_core_variable_object" begin
        @test InfiniteOpt._core_variable_object(vref) === var
        @test InfiniteOpt._core_variable_object(gvref) === var
    end
    # _set_core_variable_object
    @testset "_set_core_variable_object" begin
        @test InfiniteOpt._set_core_variable_object(vref, var) isa Nothing
    end
    # _object_numbers
    @testset "_object_numbers" begin
        @test InfiniteOpt._object_numbers(vref) == [1:6...]
    end
    # _parameter_numbers
    @testset "_parameter_numbers" begin
        @test InfiniteOpt._parameter_numbers(vref) == [1:7...]
    end
    # test _variable_info
    @testset "_variable_info" begin
        @test InfiniteOpt._variable_info(vref) == info
    end
    # test _is_vector_start
    @testset "_is_vector_start" begin
        @test InfiniteOpt._is_vector_start(vref)
    end
    # _update_variable_info
    @testset "_update_variable_info" begin
        @test isa(InfiniteOpt._update_variable_info(vref, new_info), Nothing)
        @test InfiniteOpt._variable_info(vref) == new_info
    end
    # _measure_dependencies
    @testset "_measure_dependencies" begin
        @test InfiniteOpt._measure_dependencies(vref) == MeasureIndex[]
        @test InfiniteOpt._measure_dependencies(gvref) == MeasureIndex[]
    end
    # _constraint_dependencies
    @testset "_constraint_dependencies" begin
        @test InfiniteOpt._constraint_dependencies(vref) == ConstraintIndex[]
        @test InfiniteOpt._constraint_dependencies(gvref) == ConstraintIndex[]
    end
    # _reduced_variable_dependencies
    @testset "_reduced_variable_dependencies" begin
        @test InfiniteOpt._reduced_variable_dependencies(vref) == ReducedVariableIndex[]
        @test InfiniteOpt._reduced_variable_dependencies(gvref) == ReducedVariableIndex[]
    end
    # _point_variable_dependencies
    @testset "_point_variable_dependencies" begin
        @test InfiniteOpt._point_variable_dependencies(vref) == PointVariableIndex[]
        @test InfiniteOpt._point_variable_dependencies(gvref) == PointVariableIndex[]
    end
    # _derivative_dependencies
    @testset "_derivative_dependencies" begin
        @test InfiniteOpt._derivative_dependencies(vref) == DerivativeIndex[]
        @test InfiniteOpt._derivative_dependencies(gvref) == DerivativeIndex[]
    end
    # JuMP.name
    @testset "JuMP.name" begin
        @test name(vref) == "var"
        @test name(gvref) == "var"
        @test name(bad_vref) == ""
    end
    # raw_parameter_refs
    @testset "raw_parameter_refs" begin
        @test raw_parameter_refs(vref) == IC.VectorTuple(a, b[1:2], c, [b[3], d])
        @test raw_parameter_refs(gvref) == IC.VectorTuple(a, b[1:2], c, [b[3], d])
    end
    # parameter_refs
    @testset "parameter_refs" begin
        @test parameter_refs(vref) == (a, b[1:2], c, [b[3], d])
        @test parameter_refs(gvref) == (a, b[1:2], c, [b[3], d])
    end
    # parameter_list
    @testset "parameter_list" begin
        @test parameter_list(vref) == [a; b[1:2]; c; [b[3], d]]
        @test parameter_list(gvref) == [a; b[1:2]; c; [b[3], d]]
        @test parameter_list(raw_parameter_refs(vref)) == [a; b[1:2]; c; [b[3], d]]
    end
    # JuMP.set_name
    @testset "JuMP.set_name" begin
        # test default
        @test isa(set_name(vref, ""), Nothing)
        @test name(vref) == ""
        @test_throws ErrorException set_name(bad_vref, "")
        # test normal
        @test isa(set_name(gvref, "new"), Nothing)
        @test name(vref) == "new"
    end
    # _make_variable_ref
    @testset "_make_variable_ref" begin
        @test InfiniteOpt._make_variable_ref(m, idx) == gvref
    end
    # _var_name_dict
    @testset "_var_name_dict" begin
        @test InfiniteOpt._var_name_dict(m) isa Nothing
    end
    # _update_var_name_dict
    @testset "_update_var_name_dict" begin
        m.name_to_var = Dict{String, ObjectIndex}()
        dict = InfiniteOpt._data_dictionary(vref)
        @test InfiniteOpt._update_var_name_dict(m, dict) isa Nothing
        @test InfiniteOpt._var_name_dict(m) == Dict(name(vref) => idx)
        m.name_to_var = nothing
    end
    # parameter_by_name
    @testset "JuMP.variable_by_name" begin
        # test normal
        @test variable_by_name(m, "new") == gvref
        @test variable_by_name(m, "test") isa Nothing
        # prepare variable with same name
        idx2 = InfiniteVariableIndex(2)
        @test InfiniteOpt._add_data_object(m, object) == idx2
        vref2 = InfiniteVariableRef(m, idx2)
        @test set_name(vref2, "new") isa Nothing
        # test multiple name error
        @test_throws ErrorException variable_by_name(m, "new")
    end
    # _delete_data_object
    @testset "_delete_data_object" begin
        @test InfiniteOpt._delete_data_object(vref) isa Nothing
        @test length(InfiniteOpt._data_dictionary(vref)) == 1
        @test !is_valid(m, vref)
    end
end

# Test variable definition methods
@testset "Definition" begin
    # initialize model and infinite variable info
    m = InfiniteModel()
    @independent_parameter(m, pref in [0, 1])
    @independent_parameter(m, pref2 in [0, 1])
    @dependent_parameters(m, prefs[1:2] in [0, 1])
    @finite_parameter(m, fin, 42)
    num = Float64(0)
    func1 = (a) -> 2
    info = VariableInfo(false, num, false, num, false, num, false, NaN, false, false)
    info2 = VariableInfo(true, num, true, num, true, num, true, func1, true, false)
    info3 = VariableInfo(true, num, true, num, true, num, true, 42, false, true)
    # _check_tuple_element (IndependentParameterRefs)
    @testset "_check_tuple_element (IndependentParameterRefs)" begin
        iprefs = dispatch_variable_ref.([pref, pref2])
        @test InfiniteOpt._check_tuple_element(error, iprefs) isa Nothing
    end
    # _check_tuple_element (DependentParameterRefs)
    @testset "_check_tuple_element (DependentParameterRefs)" begin
        dprefs = dispatch_variable_ref.(prefs)
        @test InfiniteOpt._check_tuple_element(error, dprefs) isa Nothing
        @test_throws ErrorException InfiniteOpt._check_tuple_element(error, dprefs[1:1])
    end
    # _check_tuple_element (Fallback)
    @testset "_check_tuple_element (Fallback)" begin
        refs = dispatch_variable_ref.([fin, pref])
        @test_throws ErrorException InfiniteOpt._check_tuple_element(error, refs)
    end
    # _check_parameter_tuple
    @testset "_check_parameter_tuple" begin
        # test normal
        tuple = IC.VectorTuple((pref, prefs, pref2))
        @test InfiniteOpt._check_parameter_tuple(error, tuple) isa Nothing
        tuple = IC.VectorTuple(([pref, pref2], prefs))
        @test InfiniteOpt._check_parameter_tuple(error, tuple) isa Nothing
        # test bad
        tuple = IC.VectorTuple((pref, pref))
        @test_throws ErrorException InfiniteOpt._check_parameter_tuple(error, tuple)
        tuple = IC.VectorTuple((pref, [prefs[1], pref2]))
        @test_throws ErrorException InfiniteOpt._check_parameter_tuple(error, tuple)
        tuple = IC.VectorTuple(fin)
        @test_throws ErrorException InfiniteOpt._check_parameter_tuple(error, tuple)
    end
    # _check_and_format_infinite_info (Real)
    @testset "_check_and_format_infinite_info (Real)" begin
        tuple = IC.VectorTuple((pref, prefs, pref2))
        @test !InfiniteOpt._check_and_format_infinite_info(error, info, tuple)[1].has_start
        @test InfiniteOpt._check_and_format_infinite_info(error, info, tuple)[1].start isa Function
        @test InfiniteOpt._check_and_format_infinite_info(error, info, tuple)[2]
        @test InfiniteOpt._check_and_format_infinite_info(error, info3, tuple)[1].has_start
        @test InfiniteOpt._check_and_format_infinite_info(error, info3, tuple)[1].start isa Function
        @test InfiniteOpt._check_and_format_infinite_info(error, info3, tuple)[2]
    end
    # _check_and_format_infinite_info (Function)
    @testset "_check_and_format_infinite_info (Function)" begin
        tuple = IC.VectorTuple((pref,))
        # test normal
        @test InfiniteOpt._check_and_format_infinite_info(error, info2, tuple)[1].has_start
        @test InfiniteOpt._check_and_format_infinite_info(error, info2, tuple)[1].start == func1
        @test !InfiniteOpt._check_and_format_infinite_info(error, info2, tuple)[2]
        # test multiple methods
        mult_func(a,b,c) = 2
        mult_func(a) = 2
        mult_info = VariableInfo(true, num, true, num, true, num, true, mult_func, true, false)
        @test InfiniteOpt._check_and_format_infinite_info(error, mult_info, tuple)[1] isa VariableInfo
        # test bad method
        bad_func(a, b) = 2
        bad_info = VariableInfo(true, num, true, num, true, num, true, bad_func, true, false)
        @test_throws ErrorException InfiniteOpt._check_and_format_infinite_info(error, bad_info, tuple)
        # test bad args
        func = (a, b) -> a + sum(b)
        bad_info = VariableInfo(true, num, true, num, true, num, true, func, true, false)
        @test_throws ErrorException InfiniteOpt._check_and_format_infinite_info(error, bad_info, tuple)
    end
    # _check_and_format_infinite_info (Fallback)
    @testset "_check_and_format_infinite_info (Fallback)" begin
        tuple = IC.VectorTuple((pref, prefs, pref2))
        bad_info = VariableInfo(true, num, true, num, true, num, true, Complex(1), true, false)
        @test_throws ErrorException InfiniteOpt._check_and_format_infinite_info(error, bad_info, tuple)
    end
    # _make_variable
    @testset "_make_variable" begin
        # test for each error message
        @test_throws ErrorException InfiniteOpt._make_variable(error, info, Infinite,
                                                   bob = 42)
        @test_throws ErrorException InfiniteOpt._make_variable(error, info, Val(:bad))
        @test_throws ErrorException InfiniteOpt._make_variable(error, info, Infinite)
        @test_throws ErrorException InfiniteOpt._make_variable(error, info, Infinite,
                                                   parameter_refs = (pref, fin))
        @test_throws ErrorException InfiniteOpt._make_variable(error, info, Infinite,
                                                  parameter_refs = (pref, pref))
        func = (a, b) -> a + sum(b)
        bad_info = VariableInfo(true, num, true, num, true, num, true, func, true, false)
        @test_throws ErrorException InfiniteOpt._make_variable(error, bad_info, Infinite,
                                                               parameter_refs = (pref))
        # test for expected output
        @test InfiniteOpt._make_variable(error, info, Infinite,
                             parameter_refs = pref).info isa JuMP.VariableInfo{Float64, Float64, Float64, Function}
        @test InfiniteOpt._make_variable(error, info, Infinite,
                parameter_refs = pref).parameter_refs == IC.VectorTuple(pref)
        @test InfiniteOpt._make_variable(error, info, Infinite,
                parameter_refs = pref).parameter_nums == [1]
        @test InfiniteOpt._make_variable(error, info, Infinite,
                parameter_refs = pref).object_nums == [1]
        # test various types of param tuples
        tuple = IC.VectorTuple(pref, pref2)
        @test InfiniteOpt._make_variable(error, info, Infinite,
                 parameter_refs = (pref, pref2)).parameter_refs == tuple
        tuple = IC.VectorTuple(pref, prefs)
        @test InfiniteOpt._make_variable(error, info, Infinite,
                         parameter_refs = (pref, prefs)).parameter_refs == tuple
        @test InfiniteOpt._make_variable(error, info, Infinite,
                         parameter_refs = (pref, prefs)).parameter_nums == [1, 3, 4]
        @test InfiniteOpt._make_variable(error, info, Infinite,
                                         parameter_refs = (pref, prefs)).is_vector_start
        @test sort!(InfiniteOpt._make_variable(error, info, Infinite,
                         parameter_refs = (pref, prefs)).object_nums) == [1, 3]
        tuple = IC.VectorTuple(prefs)
        @test InfiniteOpt._make_variable(error, info, Infinite,
                             parameter_refs = prefs).parameter_refs == tuple
    end
    # build_variable
    @testset "JuMP.build_variable" begin
        # test for each error message
        @test_throws ErrorException build_variable(error, info, Infinite,
                                                   bob = 42)
        @test_throws ErrorException build_variable(error, info, Point,
                                                   parameter_refs = pref)
        @test_throws ErrorException build_variable(error, info, Infinite)
        @test_throws ErrorException build_variable(error, info, Infinite,
                                                   parameter_refs = (pref, fin))
        @test_throws ErrorException build_variable(error, info, Infinite,
                                                  parameter_refs = (pref, pref),
                                                  error = error)
        # test for expected output
        @test build_variable(error, info, Infinite,
                             parameter_refs = pref).info isa JuMP.VariableInfo{Float64, Float64, Float64, Function}
        @test build_variable(error, info, Infinite,
                parameter_refs = pref).parameter_refs == IC.VectorTuple(pref)
        # test various types of param tuples
        tuple = IC.VectorTuple(pref, prefs)
        @test build_variable(error, info, Infinite,
                         parameter_refs = (pref, prefs)).parameter_refs == tuple
        tuple = IC.VectorTuple(prefs)
        @test build_variable(error, info, Infinite,
                             parameter_refs = prefs).parameter_refs == tuple
        @test build_variable(error, info, Infinite,
                             parameter_refs = prefs).parameter_nums == [3, 4]
        @test build_variable(error, info, Infinite,
                             parameter_refs = prefs).object_nums == [3]
    end
    # _check_parameters_valid
    @testset "_check_parameters_valid" begin
        # prepare param tuple
        @independent_parameter(InfiniteModel(), pref3 in [0, 1])
        tuple = IC.VectorTuple((pref, prefs, pref3))
        # test that catches error
        @test_throws VariableNotOwned{GeneralVariableRef} InfiniteOpt._check_parameters_valid(m, tuple)
        # test normal
        tuple = IC.VectorTuple(pref, pref2)
        @test InfiniteOpt._check_parameters_valid(m, tuple) isa Nothing
    end
    # _update_param_var_mapping
    @testset "_update_param_var_mapping" begin
        # prepare tuple
        tuple = IC.VectorTuple(pref, prefs)
        idx = InfiniteVariableIndex(1)
        ivref = InfiniteVariableRef(m, idx)
        # test normal
        @test InfiniteOpt._update_param_var_mapping(ivref, tuple) isa Nothing
        @test InfiniteOpt._infinite_variable_dependencies(pref) == [idx]
        @test InfiniteOpt._infinite_variable_dependencies(prefs[1]) == [idx]
        @test InfiniteOpt._infinite_variable_dependencies(prefs[2]) == [idx]
        # undo changes
        empty!(InfiniteOpt._infinite_variable_dependencies(pref))
        empty!(InfiniteOpt._infinite_variable_dependencies(prefs[1]))
        empty!(InfiniteOpt._infinite_variable_dependencies(prefs[2]))
    end
    # _check_and_make_variable_ref
    @testset "_check_and_make_variable_ref" begin
        # prepare secondary model and parameter and variable
        m2 = InfiniteModel()
        @independent_parameter(m2, pref3 in [0, 1])
        v = build_variable(error, info, Infinite, parameter_refs = pref3)
        # test for error of invalid variable
        @test_throws VariableNotOwned{GeneralVariableRef} InfiniteOpt._check_and_make_variable_ref(m, v, "")
        # test normal
        idx = InfiniteVariableIndex(1)
        vref = InfiniteVariableRef(m2, idx)
        @test InfiniteOpt._check_and_make_variable_ref(m2, v, "") == vref
        @test InfiniteOpt._infinite_variable_dependencies(pref3) == [idx]
        # test with other variable object
        @test_throws ArgumentError InfiniteOpt._check_and_make_variable_ref(m, :bad, "")
    end
    # add_variable
    @testset "JuMP.add_variable" begin
        # prepare secondary model and parameter and variable
        m2 = InfiniteModel()
        @independent_parameter(m2, pref3 in [0, 1])
        v = build_variable(error, info, Infinite, parameter_refs = pref3)
        # test for error of invalid variable
        @test_throws VariableNotOwned{GeneralVariableRef} add_variable(m, v)
        # prepare normal variable
        v = build_variable(error, info, Infinite, parameter_refs = pref)
        # test normal
        idx = InfiniteVariableIndex(1)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test add_variable(m, v, "name") == gvref
        @test haskey(InfiniteOpt._data_dictionary(vref), idx)
        @test InfiniteOpt._core_variable_object(vref) == v
        @test InfiniteOpt._infinite_variable_dependencies(pref) == [idx]
        @test name(vref) == "name"
        # prepare infinite variable with all the possible info additions
        v = build_variable(error, info2, Infinite, parameter_refs = pref)
        # test info addition functions
        idx = InfiniteVariableIndex(2)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test add_variable(m, v, "name") == gvref
        @test !optimizer_model_ready(m)
        @test !InfiniteOpt._is_vector_start(vref)
        @test InfiniteOpt._variable_info(vref).start == func1
        # lower bound
        cindex = ConstraintIndex(1)
        cref = InfOptConstraintRef(m, cindex)
        @test has_lower_bound(vref)
        @test InfiniteOpt._lower_bound_index(vref) == cindex
        @test constraint_object(cref) isa ScalarConstraint{GeneralVariableRef,
                                                           MOI.GreaterThan{Float64}}
        @test InfiniteOpt._data_object(cref).is_info_constraint
        # upper bound
        cindex = ConstraintIndex(2)
        cref = InfOptConstraintRef(m, cindex)
        @test has_upper_bound(vref)
        @test InfiniteOpt._upper_bound_index(vref) == cindex
        @test constraint_object(cref) isa ScalarConstraint{GeneralVariableRef,
                                                           MOI.LessThan{Float64}}
        @test InfiniteOpt._data_object(cref).is_info_constraint
        # fix
        cindex = ConstraintIndex(3)
        cref = InfOptConstraintRef(m, cindex)
        @test is_fixed(vref)
        @test InfiniteOpt._fix_index(vref) == cindex
        @test constraint_object(cref) isa ScalarConstraint{GeneralVariableRef,
                                                           MOI.EqualTo{Float64}}
        @test InfiniteOpt._data_object(cref).is_info_constraint
        # binary
        cindex = ConstraintIndex(4)
        cref = InfOptConstraintRef(m, cindex)
        @test is_binary(vref)
        @test InfiniteOpt._binary_index(vref) == cindex
        @test constraint_object(cref) isa ScalarConstraint{GeneralVariableRef,
                                                           MOI.ZeroOne}
        @test InfiniteOpt._data_object(cref).is_info_constraint
        @test InfiniteOpt._constraint_dependencies(vref) == [ConstraintIndex(i)
                                                             for i = 1:4]
        # prepare infinite variable with integer info addition
        v = build_variable(error, info3, Infinite, parameter_refs = pref)
        # test integer addition functions
        idx = InfiniteVariableIndex(3)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test add_variable(m, v, "name") == gvref
        @test !optimizer_model_ready(m)
        @test InfiniteOpt._is_vector_start(vref)
        @test InfiniteOpt._variable_info(vref).start isa Function
        cindex = ConstraintIndex(8)
        cref = InfOptConstraintRef(m, cindex)
        @test is_integer(vref)
        @test InfiniteOpt._integer_index(vref) == cindex
        @test constraint_object(cref) isa ScalarConstraint{GeneralVariableRef,
                                                           MOI.Integer}
        @test InfiniteOpt._data_object(cref).is_info_constraint
        @test InfiniteOpt._constraint_dependencies(vref) == [ConstraintIndex(i)
                                                             for i = 5:8]
    end
end

# Test helper functions for infinite variable macro
@testset "Macro Helpers" begin
    # _check_rhs
    @testset "_check_rhs" begin
        # test with reversed sides
        arg1 = :(data[i]); arg2 = :(x[i=1:2])
        @test InfiniteOpt._check_rhs(arg1, arg2) == (arg2, arg1)
        # test with normal case that shouldn't be swapped
        arg1 = :((x[i=1:2])(t)); arg2 = :(data[i])
        @test InfiniteOpt._check_rhs(arg1, arg2) == (arg1, arg2)
        # test reversed case that stays reversed because cannot be distinguished
        arg1 = :(data(i)); arg2 = :((x[i=1:2])(t))
        @test InfiniteOpt._check_rhs(arg1, arg2) == (arg1, arg2)
    end
    # _less_than_parse
    @testset "_less_than_parse" begin
        # test with reversed sides
        arg1 = :(data[i]); arg2 = :(x[i=1:2])
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:($(arg2) >= $(arg1)),
                                                           nothing)
        # test normal reference with parameter tuple
        arg1 = :(x[i=1:2](t)); arg2 = :(data[i])
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:(x[i=1:2] <= $(arg2)),
                                                           :((t,)))
        # test normal with parameter tuple
        arg1 = :(x(t)); arg2 = :(data[i])
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:(x <= $(arg2)),
                                                           :((t,)))
        # test normal without tuple
        arg1 = :(x); arg2 = :(data[i])
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:(x <= $(arg2)),
                                                           nothing)
        # test normal without tuple
        arg1 = :(x); arg2 = 1
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:(x <= $(arg2)),
                                                           nothing)
    end
    # _greater_than_parse
    @testset "_greater_than_parse" begin
        # test with reversed sides
        arg1 = :(data[i]); arg2 = :(x[i=1:2])
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:($(arg2) <= $(arg1)),
                                                              nothing)
        # test normal reference with parameter tuple
        arg1 = :(x[i=1:2](t)); arg2 = :(data[i])
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:(x[i=1:2] >= $(arg2)),
                                                              :((t,)))
        # test normal with parameter tuple
        arg1 = :(x(t)); arg2 = :(data[i])
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:(x >= $(arg2)),
                                                              :((t,)))
        # test normal without tuple
        arg1 = :(x); arg2 = :(data[i])
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:(x >= $(arg2)),
                                                              nothing)
        # test normal without tuple
        arg1 = :(x); arg2 = 1
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:(x >= $(arg2)),
                                                              nothing)
    end
    # _less_than_parse (number on lhs)
    @testset "_less_than_parse (reversed)" begin
        # test with reference
        arg1 = 1; arg2 = :(x[i=1:2])
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:($(arg2) >= $(arg1)),
                                                           nothing)
        # test with reference and parameter tuple
        arg1 = 1; arg2 = :(x[i=1:2](t))
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:(x[i=1:2] >= $(arg1)),
                                                           :((t,)))
        # test normal without tuple
        arg1 = 1; arg2 = :(x)
        @test InfiniteOpt._less_than_parse(arg1, arg2) == (:(x >= $(arg1)),
                                                           nothing)
    end
    # _greater_than_parse (number on lhs)
    @testset "_greater_than_parse (reversed)" begin
        # test with reference
        arg1 = 1; arg2 = :(x[i=1:2])
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:($(arg2) <= $(arg1)),
                                                              nothing)
        # test with reference and parameter tuple
        arg1 = 1; arg2 = :(x[i=1:2](t))
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:(x[i=1:2] <= $(arg1)),
                                                              :((t,)))
        # test normal without tuple
        arg1 = 1; arg2 = :(x)
        @test InfiniteOpt._greater_than_parse(arg1, arg2) == (:(x <= $(arg1)),
                                                              nothing)
    end
    # _equal_to_parse
    @testset "_equal_to_parse" begin
        # test with reversed sides
        arg1 = :(data[i]); arg2 = :(x[i=1:2])
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:($(arg2) == $(arg1)),
                                                          nothing)
        # test normal reference with parameter tuple
        arg1 = :(x[i=1:2](t)); arg2 = :(data[i])
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:(x[i=1:2] == $(arg2)),
                                                          :((t,)))
        # test normal with parameter tuple
        arg1 = :(x(t)); arg2 = :(data[i])
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:(x == $(arg2)),
                                                          :((t,)))
        # test normal without tuple
        arg1 = :(x); arg2 = :(data[i])
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:(x == $(arg2)),
                                                          nothing)
        # test normal without tuple
        arg1 = :(x); arg2 = 1
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:(x == $(arg2)),
                                                          nothing)
    end
    # _equal_to_parse (number on lhs)
    @testset "_equal_to_parse (reversed)" begin
        # test with reference
        arg1 = 1; arg2 = :(x[i=1:2])
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:($(arg2) == $(arg1)),
                                                          nothing)
        # test with reference and parameter tuple
        arg1 = 1; arg2 = :(x[i=1:2](t))
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:(x[i=1:2] == $(arg1)),
                                                          :((t,)))
        # test normal without tuple
        arg1 = 1; arg2 = :(x)
        @test InfiniteOpt._equal_to_parse(arg1, arg2) == (:(x == $(arg1)),
                                                          nothing)
    end
    # _parse_parameters (call)
    @testset "_parse_parameters (call)" begin
        # test less than parse
        expr = :(x(t, x) <= 1)
        @test InfiniteOpt._parse_parameters(error, Val(expr.head),
                                            expr.args) == (:(x <= 1), :((t, x)))
        # test greater than parse
        expr = :(x[1:2](t) >= 1)
        @test InfiniteOpt._parse_parameters(error, Val(expr.head),
                                         expr.args) == (:(x[1:2] >= 1), :((t,)))
        # test equal to parse
        expr = :(x(t) == d)
        @test InfiniteOpt._parse_parameters(error, Val(expr.head),
                                            expr.args) == (:(x == d), :((t,)))
        # test only variable parse
        expr = :(x(t))
        @test InfiniteOpt._parse_parameters(error, Val(expr.head),
                                            expr.args) == (:(x), :((t,)))
        # test invalid use of operator
        expr = :(x(t) in 1)
        @test_throws ErrorException InfiniteOpt._parse_parameters(error,
                                                                  Val(expr.head),
                                                                  expr.args)
    end
    # _parse_parameters (comparison)
    @testset "_parse_parameters (compare)" begin
        # test with parameters
        expr = :(0 <= x(t, x) <= 1)
        @test InfiniteOpt._parse_parameters(error, Val(expr.head),
                                       expr.args) == (:(0 <= x <= 1), :((t, x)))
        # test with parameters and references
        expr = :(0 <= x[1:2](t, x) <= 1)
        @test InfiniteOpt._parse_parameters(error, Val(expr.head),
                                  expr.args) == (:(0 <= x[1:2] <= 1), :((t, x)))
        # test without parameters
        expr = :(0 <= x <= 1)
        @test InfiniteOpt._parse_parameters(error, Val(expr.head),
                                         expr.args) == (:(0 <= x <= 1), nothing)
    end
end

# Test the infinite variable macro
@testset "Macro Definition" begin
    # initialize model and infinite parameters
    m = InfiniteModel()
    @independent_parameter(m, t in [0, 1])
    @dependent_parameters(m, x[1:2] in [-1, 1])
    # test single variable definition
    @testset "Single" begin
        # test basic defaults
        idx = InfiniteVariableIndex(1)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test @infinite_variable(m, parameter_refs = t) == gvref
        @test name(vref) == ""
        # test more tuple input and variable details
        idx = InfiniteVariableIndex(2)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test @infinite_variable(m, parameter_refs = (t, x), base_name = "test",
                                 binary = true) == gvref
        @test name(vref) == "test"
        @test is_binary(vref)
        # test nonanonymous with simple single arg
        idx = InfiniteVariableIndex(3)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test @infinite_variable(m, a(x)) == gvref
        @test name(vref) == "a"
        # test nonanonymous with complex single arg
        idx = InfiniteVariableIndex(4)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test @infinite_variable(m, 0 <= b(x) <= 1) == gvref
        @test name(vref) == "b"
        @test lower_bound(vref) == 0
        @test upper_bound(vref) == 1
        # test nonanonymous with reversed single arg
        idx = InfiniteVariableIndex(5)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test @infinite_variable(m, 0 <= c(t)) == gvref
        @test name(vref) == "c"
        @test lower_bound(vref) == 0
        # test multi-argument expr 1
        idx = InfiniteVariableIndex(6)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test @infinite_variable(m, d(t) == 0, Int, base_name = "test") == gvref
        @test name(vref) == "test"
        @test fix_value(vref) == 0
        # test single start value
        idx = InfiniteVariableIndex(7)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        @test @infinite_variable(m, aa(t), Int, start = 42) == gvref
        @test name(vref) == "aa"
        @test start_value_function(vref) isa Function
        @test start_value_function(vref)([0]) == 42
        @test_throws MethodError start_value_function(vref)(0)
        # test function start value
        idx = InfiniteVariableIndex(8)
        vref = InfiniteVariableRef(m, idx)
        gvref = InfiniteOpt._make_variable_ref(m, idx)
        func = (a) -> a
        @test @infinite_variable(m, ab(t), Int, start = func) == gvref
        @test name(vref) == "ab"
        @test start_value_function(vref) isa Function
        @test start_value_function(vref)(0) == 0
    end
    # test array variable definition
    @testset "Array" begin
        # test anonymous array
        idxs = [InfiniteVariableIndex(9), InfiniteVariableIndex(10)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        @test @infinite_variable(m, [1:2], parameter_refs = t) == gvrefs
        @test name(vrefs[1]) == ""
        # test basic param expression
        idxs = [InfiniteVariableIndex(11), InfiniteVariableIndex(12)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        @test @infinite_variable(m, e[1:2], parameter_refs = (t, x)) == gvrefs
        @test name(vrefs[2]) == "e[2]"
        # test comparison without params
        idxs = [InfiniteVariableIndex(13), InfiniteVariableIndex(14)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        @test @infinite_variable(m, 0 <= f[1:2] <= 1,
                                 parameter_refs = (t, x)) == gvrefs
        @test name(vrefs[2]) == "f[2]"
        @test lower_bound(vrefs[1]) == 0
        @test upper_bound(vrefs[2]) == 1
        # test comparison with call
        idxs = [InfiniteVariableIndex(15), InfiniteVariableIndex(16)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        @test @infinite_variable(m, 0 <= g[1:2](t) <= 1) == gvrefs
        @test name(vrefs[1]) == "g[1]"
        @test lower_bound(vrefs[1]) == 0
        @test upper_bound(vrefs[2]) == 1
        # test fixed
        idxs = [InfiniteVariableIndex(17), InfiniteVariableIndex(18)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        @test @infinite_variable(m, h[i = 1:2](t) == ones(2)[i]) == gvrefs
        @test name(vrefs[1]) == "h[1]"
        @test fix_value(vrefs[1]) == 1
        # test containers
        idxs = [InfiniteVariableIndex(19), InfiniteVariableIndex(20)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        svrefs = convert(JuMP.Containers.SparseAxisArray, gvrefs)
        @test @infinite_variable(m, [1:2](t),
                                 container = SparseAxisArray) isa JuMPC.SparseAxisArray
        @test name(vrefs[1]) == ""
        # test scalar starts
        idxs = [InfiniteVariableIndex(21), InfiniteVariableIndex(22)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        starts = [2, 5]
        @test @infinite_variable(m, w[i = 1:2](t), start = starts[i]) == gvrefs
        @test start_value_function(vrefs[1])([0]) == 2
        @test start_value_function(vrefs[2])([0]) == 5
        # test mixed starts
        idxs = [InfiniteVariableIndex(23), InfiniteVariableIndex(24)]
        vrefs = [InfiniteVariableRef(m, idx) for idx in idxs]
        gvrefs = [InfiniteOpt._make_variable_ref(m, idx) for idx in idxs]
        starts = [(a) -> 2, 5]
        @test @infinite_variable(m, cat[i = 1:2](t), start = starts[i]) == gvrefs
        @test start_value_function(vrefs[1])(0) == 2
        @test start_value_function(vrefs[2])([0]) == 5
    end
    # test errors
    @testset "Errors" begin
        # test model assertion errors
        m2 = Model()
        @test_throws AssertionError @infinite_variable(m2, parameter_refs = t)
        @test_throws AssertionError @infinite_variable(m2, i(t))
        @test_throws AssertionError @infinite_variable(m2, i, Int)
        @test_throws AssertionError @infinite_variable(m2, i(t), Bin)
        # test double specification
        @test_macro_throws ErrorException @infinite_variable(m, i(t),
                                                             parameter_refs = x)
        # test undefined parameter error
        @test_macro_throws ErrorException @infinite_variable(m, i, Int)
        # test invalid keyword arguments
        @test_macro_throws ErrorException @infinite_variable(m, i(t),
                                                           parameter_values = 1)
        gvref = InfiniteOpt._make_variable_ref(m, InfiniteVariableIndex(1))
        @test_macro_throws ErrorException @infinite_variable(m, i(t),
                                                  infinite_variable_ref = gvref)
        @test_macro_throws ErrorException @infinite_variable(m, i(t), bad = 42)
        # test name duplication
        @test_macro_throws ErrorException @infinite_variable(m, a(t), Int)
        # test bad start function
        @test_macro_throws ErrorException @infinite_variable(m, zz(t), start = (a, b) -> 2)
    end
end

# test usage methods
@testset "Usage" begin
    # initialize model and stuff
    m = InfiniteModel()
    @independent_parameter(m, t in [0, 1])
    @dependent_parameters(m, x[1:2] in [-1, 1])
    @infinite_variable(m, y(t, x))
    vref = dispatch_variable_ref(y)
    # test used_by_reduced_variable
    @testset "used_by_reduced_variable" begin
        @test !used_by_reduced_variable(vref)
        push!(InfiniteOpt._reduced_variable_dependencies(vref),
              ReducedVariableIndex(1))
        @test used_by_reduced_variable(y)
        @test used_by_reduced_variable(vref)
        empty!(InfiniteOpt._reduced_variable_dependencies(vref))
    end
    # test used_by_point_variable
    @testset "used_by_point_variable" begin
        @test !used_by_point_variable(vref)
        push!(InfiniteOpt._point_variable_dependencies(vref), PointVariableIndex(1))
        @test used_by_point_variable(y)
        @test used_by_point_variable(vref)
        empty!(InfiniteOpt._point_variable_dependencies(vref))
    end
    # test used_by_derivative
    @testset "used_by_derivative" begin
        @test !used_by_derivative(vref)
        push!(InfiniteOpt._derivative_dependencies(vref), DerivativeIndex(1))
        @test used_by_derivative(y)
        @test used_by_derivative(vref)
        empty!(InfiniteOpt._derivative_dependencies(vref))
    end
    # test used_by_measure
    @testset "used_by_measure" begin
        @test !used_by_measure(vref)
        push!(InfiniteOpt._measure_dependencies(vref), MeasureIndex(1))
        @test used_by_measure(y)
        @test used_by_measure(vref)
        empty!(InfiniteOpt._measure_dependencies(vref))
    end
    # test used_by_constraint
    @testset "used_by_constraint" begin
        @test !used_by_constraint(vref)
        push!(InfiniteOpt._constraint_dependencies(vref), ConstraintIndex(1))
        @test used_by_constraint(y)
        @test used_by_constraint(vref)
        empty!(InfiniteOpt._constraint_dependencies(vref))
    end
    # test used_by_objective
    @testset "used_by_objective" begin
        @test !used_by_objective(y)
        @test !used_by_objective(vref)
    end
    # test is_used
    @testset "is_used" begin
        # test not used
        @test !is_used(vref)
        # test used by constraint and/or measure
        push!(InfiniteOpt._constraint_dependencies(vref), ConstraintIndex(1))
        @test is_used(y)
        empty!(InfiniteOpt._constraint_dependencies(vref))
        # test used by point variable
        num = Float64(0)
        info = VariableInfo(false, num, false, num, false, num, false, num, false, false)
        var = PointVariable(info, y, [0., 0., 0.])
        object = VariableData(var, "var")
        idx = PointVariableIndex(1)
        pvref = PointVariableRef(m, idx)
        @test InfiniteOpt._add_data_object(m, object) == idx
        push!(InfiniteOpt._point_variable_dependencies(vref), idx)
        @test !is_used(vref)
        push!(InfiniteOpt._constraint_dependencies(pvref), ConstraintIndex(2))
        @test is_used(vref)
        empty!(InfiniteOpt._point_variable_dependencies(vref))
        # test used by reduced variable
        eval_supps = Dict{Int, Float64}(1 => 0.5, 3 => 1)
        var = ReducedVariable(y, eval_supps, [2], [2])
        object = VariableData(var, "var")
        idx = ReducedVariableIndex(1)
        rvref = ReducedVariableRef(m, idx)
        @test InfiniteOpt._add_data_object(m, object) == idx
        push!(InfiniteOpt._reduced_variable_dependencies(vref), idx)
        @test !is_used(vref)
        push!(InfiniteOpt._constraint_dependencies(rvref), ConstraintIndex(2))
        @test is_used(vref)
        empty!(InfiniteOpt._reduced_variable_dependencies(vref))
        # test used by derivative
        func = (x) -> NaN
        num = 0.
        info = VariableInfo{Float64, Float64, Float64, Function}(true, num, true,
                                         num, true, num, false, func, true, true)
        deriv = Derivative(info, true, y, t)
        object = VariableData(deriv)
        idx = DerivativeIndex(1)
        dref = DerivativeRef(m, idx)
        @test InfiniteOpt._add_data_object(m, object) == idx
        push!(InfiniteOpt._derivative_dependencies(vref), idx)
        @test !is_used(vref)
        push!(InfiniteOpt._constraint_dependencies(dref), ConstraintIndex(2))
        @test is_used(vref)
        empty!(InfiniteOpt._derivative_dependencies(vref))
    end
end


text:
raw = open(f->read(f, String), "input.txt")
shipLocation = [0; 0]
shipOrientation = [1; 0]

N = [0; 1];
S = [0; -1];
E = [1; 0];
W = [-1; 0];
L = [0 -1;
     1 0];
R = [0 1;
    -1 0];

function f((shipL, shipO), s)
    action = s[1]
    n = parse(Int64, s[2:end])

    if action == 'N'
        (shipL + n * N, shipO)
    elseif action == 'S'
        (shipL + n * S, shipO)
    elseif action == 'E'
        (shipL + n * E, shipO)
    elseif action == 'W'
        (shipL + n * W, shipO)
    elseif action == 'F'
        (shipL + n * shipO, shipO)
    elseif action == 'L'
        k = div(n, 90)
        (shipL, L^k * shipO)
    elseif action == 'R'
        k = div(n, 90)
        (shipL, R^k * shipO)
    end
end

manhattanNorm(x, y) = abs.(x - y)' * ones(size(x))

println(foldl(f, split(raw); init=(shipLocation, shipOrientation)) |> (ship -> manhattanNorm(ship[1], [0; 0])))

ext: jl


text:
Transducers.transduce(xf, rf, init, xs, ex::WorkStealingEx) =
    _transduce_ws(xf, rf, init, xs; ex.kwargs...)

function _transduce_ws(
    xf::Transducer,
    rf,
    init,
    xs;
    simd = Val(false),
    basesize::Union{Nothing,Integer} = nothing,
    stoppable::Union{Bool,Nothing} = nothing,
    # ntasks::Int = Threads.nthreads(),
    # background::Bool = false,
)
    rf0 = _reducingfunction(xf, rf; init = init, simd = simd)
    rf1, xs0 = retransform(rf0, xs)
    if basesize === nothing
        basesize = amount(xs0) ÷ Threads.nthreads()
    end
    xs1 = SizedReducible(xs0, basesize)
    if stoppable === nothing
        stoppable = _might_return_reduced(rf1, init, xs0)
    end
    if stoppable
        ctx = CancellableDACContext()
    else
        ctx = NoopDACContext()
    end
    return transduce_ws(ctx, rf1, init, xs1)
end

const WorkUnit = FunctionWrapper{Nothing,Tuple{}}
const WorkQueue = LockedLinkedList{WorkUnit}

struct WorkerPool
    tasks::Vector{Vector{Task}}
    queues::Vector{WorkQueue}
    occupied::Vector{Bool}
    all_scheduled::Threads.Atomic{Bool}
end

function WorkerPool()
    ntasks = Threads.nthreads()
    tasks = [Task[] for _ in 1:ntasks]
    queues = [WorkQueue() for _ in 1:ntasks]
    occupied = zeros(Bool, ntasks)
    all_scheduled = Threads.Atomic{Bool}(false)
    return WorkerPool(tasks, queues, occupied, all_scheduled)
end

struct WSScheduler
    pool::WorkerPool
    queue::WorkQueue
end

WSScheduler(pool::WorkerPool = WorkerPool()) =
    WSScheduler(pool, pool.queues[Threads.threadid()])

function get_scheduler(sch::WSScheduler)
    pool = sch.pool
    pool.occupied[Threads.threadid()] && return
    pool.occupied[Threads.threadid()] = true
    return WSScheduler(pool, pool.queues[Threads.threadid()])
end
# TODO: Prepare for task migration; use local worker id instead of
# Threads.threadid().

push_task!(sch, task) = push!(sch.pool.tasks[Threads.threadid()], task)
is_all_scheduled(sch::WSScheduler) = sch.pool.all_scheduled[]

""" Work-stealing (WS) divide-and-conquer (DAC) context """
struct WSDACContext{C <: DACContext}
    ctx::C
    output::Promise
    all_scheduled::Threads.Atomic{Bool}
    isright::Bool
    ntasks::Int
end

WSDACContext(ctx::DACContext, output::Promise, sch::WSScheduler) =
    WSDACContext(ctx, output, sch.pool.all_scheduled, true, Threads.nthreads() - 1)

function task_spawn!(f, sch::WSScheduler, ctx::WSDACContext)
    output = ctx.output
    task = Threads.@spawn try
        sch2 = get_scheduler(sch)
        # if sch2 === nothing
        #     @info "occupied!" Threads.threadid()
        # end
        sch2 === nothing && return # retry?
        f(sch2)
        help_others(sch2)
    catch err
        tryput!(output, Err(err))
        rethrow()
    end
    push_task!(sch, task)
    pool = sch.pool
    w = WorkUnit() do
        f(WSScheduler(pool))
        return
    end
    cons = listof(WorkUnit, w)
    setcdr!(sch.queue, cons)
    queue = setlist(sch.queue, cons)
    return @set sch.queue = queue
end

function spawn!(f, sch::WSScheduler)
    pool = sch.pool
    w = WorkUnit() do
        f(WSScheduler(pool))
        return
    end
    cons = listof(WorkUnit, w)
    setcdr!(sch.queue, cons)
    queue = setlist(sch.queue, cons)
    return @set sch.queue = queue
end

should_abort(ctx::WSDACContext) = should_abort(ctx.ctx)
cancel!(ctx::WSDACContext) = cancel!(ctx.ctx)
function splitcontext(ctx::WSDACContext)
    fg, bg = splitcontext(ctx.ctx)
    if ctx.ntasks <= 1
        nl = nr = 0
    else
        nl = ctx.ntasks ÷ 2
        nr = ctx.ntasks - nl
    end
    return (
        WSDACContext(fg, ctx.output, ctx.all_scheduled, false, nl),
        WSDACContext(bg, ctx.output, ctx.all_scheduled, ctx.isright, nr),
    )
end

function on_basecase!(ctx::WSDACContext)
    if ctx.isright
        ctx.all_scheduled[] = true
    end
    return
end

function transduce_ws(ctx, rf, init, xs)
    sch = WSScheduler()
    output = Promise()
    try
        transduce_ws_root(sch, WSDACContext(ctx, output, sch), rf, init, xs)
    finally
        close(sch.pool)
    end
    # let nused = sum(sch.pool.occupied)
    #     @info "done" nused Threads.nthreads() nused == Threads.nthreads()
    # end
    return something(tryfetch(output))[]
end

function Base.close(pool::WorkerPool)
    pool.all_scheduled[] = true
    foreach(empty!, pool.queues)
    for tasks in pool.tasks
        # TODO: make this thread-safe & merge all tasks
        for t in tasks
            wait(t)
        end
    end
end

function transduce_ws_root(sch::WSScheduler, ctx::WSDACContext, rf, init, xs)
    output = ctx.output
    chain0 = and_finally() do acc
        tryput!(output, acc)
    end
    chain, thunk = transduce_ws_cps_dac1(chain0, sch, ctx, rf, init, xs)
    trampoline(chain, thunk())
    while true
        result = tryfetch(output)
        result === nothing || return something(result)
        while help_others_nonblocking(sch)
        end
        yield()
    end
end

function transduce_ws_cps_dac1(
    chain::Cons{Function},
    sch::WSScheduler,
    ctx::WSDACContext,
    rf::RF,
    init,
    xs,
) where {RF}
    if ctx.ntasks <= Int(!ctx.isright) || issmall(xs)
        # TODO: fix ctx.ntasks check
        return transduce_ws_cps_dac2(chain, sch, ctx, rf, init, xs)
    end
    # @show ctx.ntasks

    left, right = _halve(xs)
    fg, bg = splitcontext(ctx)
    started = Threads.Atomic{Int}(0)
    bridge = Promise()
    function continuation(sch, accl0)
        chainr = before(chain) do accr
            if accl0 === nothing
                accl1 = tryput!(bridge, accr)
                accl1 === nothing && return
                accl = something(accl1)
            else
                accl = something(accl0)
            end
            return Some(_combine(ctx, rf, accl, accr))
        end
        chain2, thunk2 = transduce_ws_cps_dac1(chainr, sch, bg, rf, init, right)
        return (chain2, thunk2())
    end
    sch1 = task_spawn!(sch, ctx) do sch
        Threads.atomic_xchg!(started, 2) != 0 && return
        chain2, x2 = continuation(sch, nothing)
        trampoline(chain2, x2)
    end
    chainl = before(chain) do accl
        if Threads.atomic_xchg!(started, 1) == 0  # self-steal
            # Using `sch` instead of `sch1` here would "pop" the task that
            # we just stole:
            return continuation(sch, Some(accl))
        else
            accr0 = tryput!(bridge, accl)
            accr0 === nothing && return
            accr = something(accr0)
            return Some(_combine(ctx, rf, accl, accr))
        end
    end
    return transduce_ws_cps_dac1(chainl, sch1, fg, rf, init, left)
end

"""
Descend into left nodes, try self-steal right nodes as much as possible.

Invariance: Once a function call of `transduce_ws_cps_dac` returns, all the
spawned tasks (the immediate right nodes) are already started. Thus, we can
safely "pop" the queue (i.e., use `setcdr!` in `spawn!`).
"""
function transduce_ws_cps_dac2(
    chain::Cons{Function},
    sch::WSScheduler,
    ctx::WSDACContext,
    rf::RF,
    init,
    xs,
) where {RF}
    if issmall(xs)
        on_basecase!(ctx)
        thunk() = try
            if should_abort(ctx)
                Ok(init)
            else
                acc = _reduce_basecase(rf, init, xs)
                if acc isa Reduced
                    cancel!(ctx)
                end
                Ok(acc)
            end
        catch err
            cancel!(ctx)
            Err(err)
        end
        return (chain, thunk)
    else
        left, right = _halve(xs)
        fg, bg = splitcontext(ctx)
        started = Threads.Atomic{Int}(0)
        bridge = Promise()
        function continuation(sch, accl0)
            chainr = before(chain) do accr
                if accl0 === nothing
                    accl1 = tryput!(bridge, accr)
                    accl1 === nothing && return
                    accl = something(accl1)
                else
                    accl = something(accl0)
                end
                return Some(_combine(ctx, rf, accl, accr))
            end
            chain2, thunk2 = transduce_ws_cps_dac2(chainr, sch, bg, rf, init, right)
            return (chain2, thunk2())
        end
        sch1 = spawn!(sch) do sch
            Threads.atomic_xchg!(started, 2) != 0 && return
            chain2, x2 = continuation(sch, nothing)
            trampoline(chain2, x2)
        end
        chainl = before(chain) do accl
            if Threads.atomic_xchg!(started, 1) == 0  # self-steal
                # Using `sch` instead of `sch1` here would "pop" the task that
                # we just stole:
                return continuation(sch, Some(accl))
            else
                accr0 = tryput!(bridge, accl)
                accr0 === nothing && return
                accr = something(accr0)
                return Some(_combine(ctx, rf, accl, accr))
            end
        end
        return transduce_ws_cps_dac2(chainl, sch1, fg, rf, init, left)
    end
end

function help_others(sch::WSScheduler)
    while !is_all_scheduled(sch)
        while help_others_nonblocking(sch)
        end
        yield()
    end
end

"""
    help_others_nonblocking(sch, queue) -> should_continue::Bool
"""
function help_others_nonblocking(sch::WSScheduler)
    queues = sch.pool.queues
    for _ in 1:8*length(queues)
        other = queues[Int(mod1(time_ns(), length(queues)))]
        isempty(other) && continue  # racy lock-free check
        f = trypopfirst!(other)
        f === nothing && continue
        # @info "RACY: $(Threads.threadid()) stole $(objectid(f))"
        something(f)()
        return true
    end
    for other in queues
        f = trypopfirst!(other)
        f === nothing && continue
        # @info "SEQ: $(Threads.threadid()) stole $(objectid(f))"
        something(f)()
        return true
    end
    return false
end


text:
using Weave
fileset = readdir(@__DIR__)
for file in fileset
    if occursin(".jmd", file)
        Weave.notebook(file, :pwd, -1, "--allow-errors")
        Weave.weave(file, out_path = :pwd, doctype = "md2html", template = "our_template.tpl")
        println("Weaved $file")
    end
end


text:
cd(@__DIR__)
using Pkg
Pkg.activate(".")
Pkg.instantiate()
using JSON, Statistics, GitHub, Base64, SHA, Downloads, Dates, CairoMakie

include("benchmark-library.jl")

ctx = github_context()

project = "current-pr-project/"
# It seems, that between julia versions, the manifest must be deleted to not get problems
isdir(project) && rm(project; force=true, recursive=true)
mkdir(project)

Pkg.activate(project)
Pkg.develop([(;path="../../MakieCore"), (;path="../../"), (;path="../../CairoMakie")])
this_pr = BenchInfo(
    project=project,
    branch="current-pr"
)

Pkg.activate(".")

plot_url = run_benchmarks(ctx, [GitHub.branch(ctx.repo, "master"), this_pr])


text:
using AdFem

for degree in [1, 2]
    m = 1; n = 1; h = 1/n
    mesh = Mesh(m, n, h, degree = degree)

    function f(x, y)
        2 * x ^ 2 + y
    end

    f_dof = eval_f_on_dof_pts(f, mesh)
    f_gauss = dof_to_gauss_points(constant(f_dof), mesh)
    f_gauss0 = dof_to_gauss_points(f_dof, mesh)
    sess = Session(); init(sess)
    f_gauss = run(sess, f_gauss)
    @show maximum(abs.(f_gauss0 - f_gauss))

    f_gauss2 = eval_f_on_gauss_pts(f, mesh)

    @show maximum(abs.(f_gauss .- f_gauss2))
end


text:
using Base.Meta
import Base: +, -, convert, isless, get_world_counter
using Core: CodeInfo, SimpleVector, LineInfoNode, GotoNode, Slot,
            GeneratedFunctionStub, MethodInstance, NewvarNode, TypeName

using UUIDs
using Random
# The following are for circumventing #28, memcpy invalid instruction error,
# in Base and stdlib
using Random.DSFMT
using InteractiveUtils

export @interpret, Compiled, Frame, root, leaf, ExprSplitter,
       BreakpointRef, breakpoint, @breakpoint, breakpoints, enable, disable, remove, toggle,
       debug_command, @bp, break_on, break_off, on_breakpoints_updated

module CompiledCalls
# This module is for handling intrinsics that must be compiled (llvmcall) as well as ccalls
end

const SlotNamesType = Vector{Symbol}

append_any(@nospecialize x...) = append!([], Core.svec((x...)...))

if isdefined(Base, :mapany)
    const mapany = Base.mapany
else
    mapany(f, itr) = map!(f, Vector{Any}(undef, length(itr)::Int), itr)  # convenient for Expr.args
end

if isdefined(Base, :ntupleany)
    const ntupleany = Base.ntupleany
else
    @noinline function ntupleany(f, n)
        (n >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", n)))
        (Any[f(i) for i = 1:n]...,)
    end
end

if !isdefined(Base, Symbol("@something"))
    macro something(x...)
        :(something($(map(esc, x)...)))
    end
end

include("types.jl")
include("utils.jl")
include("construct.jl")
include("localmethtable.jl")
include("interpret.jl")
include("builtins.jl")
include("optimize.jl")
include("commands.jl")
include("breakpoints.jl")

function set_compiled_methods()
    ###########
    # Methods #
    ###########
    # Work around #28 by preventing interpretation of all Base methods that have a ccall to memcpy
    push!(compiled_methods, which(vcat, (Vector,)))
    push!(compiled_methods, first(methods(Base._getindex_ra)))
    push!(compiled_methods, first(methods(Base._setindex_ra!)))
    push!(compiled_methods, which(Base.decompose, (BigFloat,)))
    push!(compiled_methods, which(DSFMT.dsfmt_jump, (DSFMT.DSFMT_state, DSFMT.GF2X)))
    @static if Sys.iswindows()
        push!(compiled_methods, which(InteractiveUtils.clipboard, (AbstractString,)))
    end
    # issue #76
    push!(compiled_methods, which(unsafe_store!, (Ptr{Any}, Any, Int)))
    push!(compiled_methods, which(unsafe_store!, (Ptr, Any, Int)))
    # issue #92
    push!(compiled_methods, which(objectid, Tuple{Any}))
    # issue #106 --- anything that uses sigatomic_(begin|end)
    push!(compiled_methods, which(flush, Tuple{IOStream}))
    push!(compiled_methods, which(disable_sigint, Tuple{Function}))
    push!(compiled_methods, which(reenable_sigint, Tuple{Function}))
    # Signal-handling in the `print` dispatch hierarchy
    push!(compiled_methods, which(Base.unsafe_write, Tuple{Base.LibuvStream,Ptr{UInt8},UInt}))
    push!(compiled_methods, which(print, Tuple{IO,Any}))
    push!(compiled_methods, which(print, Tuple{IO,Any,Any}))
    # Libc.GetLastError()
    @static if Sys.iswindows()
        push!(compiled_methods, which(Base.access_env, Tuple{Function,AbstractString}))
        push!(compiled_methods, which(Base._hasenv, Tuple{Vector{UInt16}}))
    end
    # These are currently extremely slow to interpret (https://github.com/JuliaDebug/JuliaInterpreter.jl/issues/193)
    push!(compiled_methods, which(subtypes, Tuple{Module,Type}))
    push!(compiled_methods, which(subtypes, Tuple{Type}))
    push!(compiled_methods, which(match, Tuple{Regex,String,Int,UInt32}))

    # Anything that ccalls jl_typeinf_begin cannot currently be handled
    for finf in (Core.Compiler.typeinf_code, Core.Compiler.typeinf_ext, Core.Compiler.typeinf_type)
        for m in methods(finf)
            push!(compiled_methods, m)
        end
    end

    # Does an atomic operation via llvmcall (this fixes #354)
    if isdefined(Base, :load_state_acquire)
        for m in methods(Base.load_state_acquire)
            push!(compiled_methods, m)
        end
    end

    # This is about performance, not safety (issue #462)
    push!(compiled_methods, which(nameof, (Module,)))
    push!(compiled_methods, which(Base.binding_module, (Module, Symbol)))
    push!(compiled_methods, which(Base.unsafe_pointer_to_objref, (Ptr,)))
    push!(compiled_methods, which(Vector{Int}, (UndefInitializer, Int)))
    push!(compiled_methods, which(fill!, (Vector{Int8}, Int)))

    ###########
    # Modules #
    ###########
    push!(compiled_modules, Base.Threads)
end

function __init__()
    set_compiled_methods()
    COVERAGE[] = Base.JLOptions().code_coverage
    # If we interpret into Core.Compiler, we need to take precautions to avoid needing
    # inference of JuliaInterpreter methods in the middle of a `ccall(:jl_typeinf_begin, ...)`
    # block.
    # for (sym, RT, AT) in ((:jl_typeinf_begin, Cvoid, ()),
    #                       (:jl_typeinf_end, Cvoid, ()),
    #                       (:jl_isa_compileable_sig, Int32, (Any, Any)),
    #                       (:jl_compress_ast, Any, (Any, Any)),
    #                       # (:jl_set_method_inferred, Ref{Core.CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt)),
    #                       (:jl_method_instance_add_backedge, Cvoid, (Any, Any)),
    #                       (:jl_method_table_add_backedge, Cvoid, (Any, Any, Any)),
    #                       (:jl_new_code_info_uninit, Ref{CodeInfo}, ()),
    #                       (:jl_uncompress_argnames, Vector{Symbol}, (Any,)),
    #                       (:jl_get_tls_world_age, UInt, ()),
    #                       (:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint)),
    #                       (:jl_value_ptr, Any, (Ptr{Cvoid},)),
    #                       (:jl_value_ptr, Ptr{Cvoid}, (Any,)))
    #     fname = Symbol(:ccall_, sym)
    #     qsym = QuoteNode(sym)
    #     argnames = [Symbol(:arg_, string(i)) for i = 1:length(AT)]
    #     TAT = Expr(:tuple, [parametric_type_to_expr(t) for t in AT]...)
    #     def = :($fname($(argnames...)) = ccall($qsym, $RT, $TAT, $(argnames...)))
    #     f = Core.eval(Core.Compiler, def)
    #     compiled_calls[(qsym, RT, Core.svec(AT...), Core.Compiler)] = f
    #     precompile(f, AT)
    # end
end

include("precompile.jl")
_precompile_()


text:
@testset "SGNHT" begin
    #-------------------
    # test inplace addition vs reference
    #-------------------
    function sample_reference!(s::SGNHTState,grad::Function)
        # increment iterations counter
        s.t += 1

        # improve readability
        nparams = length(s.x)
        stepsize = s.stepsize(s.t)
        x = s.x
        p = s.p
        xi = s.xi
        D = s.D
        mu = s.mu

        p[:] += .5*stepsize*grad(x)
        x[:] += .5*stepsize*p
        xi[:] += .5*stepsize/mu * (sum(p.*p) - nparams)
        if abs(stepsize*xi)[1] > 1e-6
        p[:] = exp(-stepsize*xi).*p + sqrt(.5*(1-exp(-2.0*stepsize.*xi))./xi * D) .* randn(nparams)
        else
        p[:] += sqrt(stepsize*D)*randn(nparams)
        end
        xi[:] += .5*stepsize/mu * (sum(p.*p) - nparams)
        x[:] += .5*stepsize*p
        p[:] += .5*stepsize*grad(x)
        s
    end

    srand(1)
    s = SGNHTState(ones(2),0.01)
    srand(1)
    ref_s = SGNHTState(ones(2),0.01)
    sgnhtgrad(x) = x.*x

    srand(2)
    sample!(s,sgnhtgrad)

    srand(2)
    sample_reference!(ref_s,sgnhtgrad)

    @test s.x == ref_s.x

    #------------------
    # Gaussian sanity check.
    #------------------
    dm = GaussianMixtureModel([0.],[1.],[1.])

    gradient = DataModel.getgrad(dm)
    s = SGNHTState(zeros(1),0.1)

    x_avg = [0.]
    x2_avg = [0.]
    for i in 1:50000
        sample!(s,gradient)
        x_avg =(i-1)/i*x_avg + s.x/i
        x2_avg = (i-1)/i*x2_avg + s.x.^2/i
    end

    @test abs(x_avg-0)[1] < 0.1
    @test abs(x2_avg-1)[1] < 0.2
end


text:
module SpringCollab2020MusicLayerFadeTrigger

using ..Ahorn, Maple

@mapdef Trigger "SpringCollab2020/MusicLayerFadeTrigger" MusicLayerFadeTrigger(x::Integer, y::Integer, width::Integer=Maple.defaultTriggerWidth, height::Integer=Maple.defaultTriggerHeight,
    layers::String="", fadeA::Number=0.0, fadeB::Number=1.0, direction::String="LeftToRight")

const placements = Ahorn.PlacementDict(
    "Music Layer Fade (Spring Collab 2020)" => Ahorn.EntityPlacement(
        MusicLayerFadeTrigger,
        "rectangle"
    )
)

Ahorn.editingOptions(entity::MusicLayerFadeTrigger) = Dict{String, Any}(
    "direction" => Maple.trigger_position_modes
)

end


text:
"""
    Module Basis2DTri

Includes DG core functions.
"""

module Basis2DTri

using Basis1D
using LinearAlgebra
using DelimitedFiles

export vandermonde_2D, grad_vandermonde_2D
export nodes_2D, equi_nodes_2D, quad_nodes_2D

"""
    simplex_2D(a, b, i, j)

Evaluate 2D "Legendre" basis phi_ij at (a,b) returned by rstoab

# Examples
```jldoctest
"""
function simplex_2D(a, b, i, j)
    h₁ = jacobiP(a, 0, 0, i)
    h₂ = jacobiP(b, 2i+1, 0, j)
    return @. sqrt(2.0)*h₁*h₂*(1-b)^i
end

"""
    grad_simplex_2D(a, b, id, jd)

Evalute the partial derivative w.r.t. (r,s) of 2D "Legendre" polynomial with
index, or order, (id,jd) at (a,b)

# Examples
```jldoctest
"""

function grad_simplex_2D(a, b, id, jd)
    fa = jacobiP(a, 0, 0, id)
    gb = jacobiP(b, 2*id+1, 0, jd)
    dfa = grad_jacobiP(a, 0, 0, id)
    dgb = grad_jacobiP(b, 2*id+1, 0, jd)

    dmodedr = @. dfa*gb
    if id > 0
        dmodedr = dmodedr.*((0.5*(1 .- b)).^(id-1))
    end

    dmodeds = @. dfa*(gb*(0.5*(1+a)))
    if id > 0
        dmodeds = dmodeds.*((0.5*(1 .- b)).^(id-1))
    end

    tmp = @. dgb*((0.5*(1-b))^id)
    if id > 0
        tmp = tmp-0.5*id*gb.*((0.5*(1 .- b)).^(id-1))
    end
    dmodeds = dmodeds+fa.*tmp

    dmodedr = 2^(id+0.5)*dmodedr
    dmodeds = 2^(id+0.5)*dmodeds
    return dmodedr, dmodeds
end

"""
    rstoab(r, s)

Transfer from reference element coordinate (r,s) to polynomial basis evaluation
coordinate (a,b)

# Examples
```jldoctest
"""
function rstoab(r, s)
    a = zeros(length(r), 1)
    for n = 1:length(r)
        if s[n] != 1
            a[n] = 2*(1+r[n])/(1-s[n])-1
        else
            a[n] = -1
        end
    end
    return a, s
end

"""
    vandermonde_2D(N, r)

Initialize the 2D Vandermonde matrix of order N "Legendre" polynomials at
nodes (r,s)

# Examples
```jldoctest
"""
function vandermonde_2D(N, r, s)
    Np = convert(Int,(N+1)*(N+2)/2)
    V2D = zeros(length(r), Np)
    a, b = rstoab(r, s)
    sk = 1
    for i = 0:N
        for j = 0:N-i
            V2D[:,sk] = simplex_2D(a,b,i,j)
            sk = sk+1
        end
    end
    return V2D
end

"""
    gradV_2D(N, Np, r, s)

Initilize 2D gradient Vandermonde matrix of derivatives 2D "Legendre" polynomials
of order N at (r,s)

# Examples
```jldoctest
"""
function grad_vandermonde_2D(N, r, s)
    Np = convert(Int,(N+1)*(N+2)/2)
    V2Dr = zeros(length(r), Np)
    V2Ds = zeros(length(r), Np)
    a, b = rstoab(r, s)

    sk = 1
    for i = 0:N
        for j = 0:N-i
            V2Dr[:,sk], V2Ds[:,sk] = grad_simplex_2D(a, b, i, j)
            sk = sk+1
        end
    end

    return V2Dr, V2Ds
end

# ===================================================

"""
    xytors(x, y)

Transfer from equilateral triangle coordinate (a,b) to reference element
coordinate (r,s)

# Examples
```jldoctest
"""
function xytors(x, y)
    L₁ = @. (sqrt(3.0)y+1.0)/3.0
    L₂ = @. (-3.0x-sqrt(3.0)y+2.0)/6.0
    L₃ = @. (3.0x-sqrt(3.0)y+2.0)/6.0
    r = -L₂+L₃-L₁
    s = -L₂-L₃+L₁
    return r, s
end

"""
    function warp_factor(N, rout)

Compute warp factor for triangular interp nodes.

# Examples
```jldoctest
"""

function warp_factor(N, rout)
    LGLr,w1D = gauss_lobatto_quad(0, 0, N)
    # LGLr = transpose(LGLr[:])
    req = LinRange(-1,1,N+1)
    Veq = vandermonde_1D(N, req)
    Nr = length(rout)
    Pmat = zeros(N+1, Nr)
    for i = 1:N+1
        Pmat[i,:] = transpose(jacobiP(rout, 0, 0, i-1))
    end
    Lmat = transpose(Veq)\Pmat
    # warp = transpose(Lmat)*transpose(LGLr.-req)
    warp = transpose(Lmat)*(LGLr.-req[:])
    zerof = @. (abs(rout) < 1.0 - 1.0e-10)
    sf = @. 1.0 - (zerof*rout)^2
    warp = @. warp/sf + warp*(zerof-1)
    return warp
end

"""
    nodes_2D(N)

Compute optimized interpolation nodes using blend & warp method on equilateral
triangles for polynomial of order N, with Np points

# Examples
```jldoctest
"""

function nodes_2D(N)

    Np = convert(Int,(N+1)*(N+2)/2)

    alpopt = [0.0 0.0 1.4152 0.1001 0.2751 0.98 1.0999 1.2832 1.3648 1.4773 1.4959 1.5743 1.577 1.6223 1.6258]
    α = N < 16 ? alpopt[N] : 5/3

    L1 = zeros(Np, 1)
    L2 = zeros(Np, 1)
    L3 = zeros(Np, 1)
    sk = 1
    for n = 1:N+1
        for m = 1:N+2-n
            L1[sk] = (n-1)/N
            L3[sk] = (m-1)/N
            sk += 1;
        end
    end
    L2 = @. 1.0-L1-L3
    x = -L2+L3
    y = (-L2-L3+2*L1)/sqrt(3.0)
    blend1 = 4.0*L2.*L3
    blend2 = 4.0*L1.*L3
    blend3 = 4.0*L1.*L2
    warpf1 = warp_factor(N, L3-L2)
    warpf2 = warp_factor(N, L1-L3)
    warpf3 = warp_factor(N, L2-L1)
    warp1 = @. blend1*warpf1*(1+(α*L1).^2)
    warp2 = @. blend2*warpf2*(1+(α*L2).^2)
    warp3 = @. blend3*warpf3*(1+(α*L3).^2)

    x = x + 1*warp1 + cos(2*pi/3)*warp2 + cos(4*pi/3)*warp3
    y = y + 0*warp1 + sin(2*pi/3)*warp2 + sin(4*pi/3)*warp3

    r, s = xytors(x, y)
    return r, s
end

"""
    equi_nodes_2D(N)

Compute optimized interpolation nodes using blend & warp method on equilateral
triangles for polynomial of order N, with Np points

# Examples
```jldoctest
"""

function equi_nodes_2D(N)
    Np = convert(Int,(N+1)*(N+2)/2)

    r = zeros(Np,1)
    s = zeros(Np,1)

    r1D = LinRange(-1,1,N+1)
    sk = 1
    for i = 0:N
        for j = 0:N-i
            r[sk] = r1D[i+1]
            s[sk] = r1D[j+1]
            sk += 1
        end
    end

    return r, s
end

"""
    quad_nodes_tri(N)

Returns quadrature nodeswhich exactly integrate degree N polynomials

# Examples
```jldoctest
"""

function quad_nodes_tri(N)

    if N<28
        rsw = readdlm(string("./src/QuadratureData/quad_nodes_tri_N", N, ".txt"),' ', Float64, '\n')
        r = rsw[:,1]
        s = rsw[:,2]
        w = rsw[:,3]
    else
        cubNA = convert(Int,ceil( (N+1)/2))
        cubNB = convert(Int,ceil( (N+1)/2))
        cubA,cubWA = gauss_quad(0,0, cubNA-1)
        cubB,cubWB = gauss_quad(1,0, cubNB-1)

        cubA = ones(cubNB,1)*cubA'
        cubB = cubB*ones(1,cubNA)

        r = @. 0.5*(1+cubA)*(1-cubB)-1
        s = cubB
        w = 0.5*cubWB*(cubWA')
    end

    return r[:], s[:], w[:]
end

"""
    quad_nodes_2D(N)

# Examples
```jldoctest
"""

function quad_nodes_2D(N)
    r,s,w = quad_nodes_tri(N)
    return r,s,w
end

end

max_stars_repo_name: yiminllin/ESDG-Fourier

max_issues_repo_name: yiminllin/ESDG-Fourier

max_forks_repo_name: yiminllin/ESDG-Fourier


text:
"""
    ProductOperation <: AbstractGroupOperation

Direct product group operation.
"""
struct ProductOperation <: AbstractGroupOperation end

const ProductGroup{𝔽,T} = GroupManifold{𝔽,ProductManifold{𝔽,T},ProductOperation}

"""
    ProductGroup{𝔽,T} <: GroupManifold{𝔽,ProductManifold{T},ProductOperation}

Decorate a product manifold with a [`ProductOperation`](@ref).

Each submanifold must also be an [`AbstractGroupManifold`](@ref) or a decorated instance of
one. This type is mostly useful for equipping the direct product of group manifolds with an
[`Identity`](@ref) element.

# Constructor
    ProductGroup(manifold::ProductManifold)
"""
function ProductGroup(manifold::ProductManifold{𝔽}) where {𝔽}
    if !all(is_group_decorator, manifold.manifolds)
        error("All submanifolds of product manifold must be or decorate groups.")
    end
    op = ProductOperation()
    return GroupManifold(manifold, op)
end

function decorator_transparent_dispatch(::typeof(group_exp!), M::ProductGroup, q, X)
    return Val(:transparent)
end
function decorator_transparent_dispatch(::typeof(group_log!), M::ProductGroup, X, q)
    return Val(:transparent)
end

function Base.show(io::IO, ::MIME"text/plain", G::ProductGroup)
    print(
        io,
        "ProductGroup with $(length(base_manifold(G).manifolds)) subgroup$(length(base_manifold(G).manifolds) == 1 ? "" : "s"):",
    )
    return _show_product_manifold_no_header(io, base_manifold(G))
end

function Base.show(io::IO, G::ProductGroup)
    M = base_manifold(G)
    return print(io, "ProductGroup(", join(M.manifolds, ", "), ")")
end

submanifold(G::ProductGroup, i) = submanifold(base_manifold(G), i)

function submanifold_component(
    e::Identity{GT},
    ::Val{I},
) where {I,MT<:ProductManifold,𝔽,GT<:GroupManifold{𝔽,MT}}
    return Identity(submanifold(e.group, I), submanifold_component(e.p, I))
end

function submanifold_components(
    e::Identity{GT},
) where {MT<:ProductManifold,𝔽,GT<:GroupManifold{𝔽,MT}}
    M = base_manifold(e.group)
    return map(Identity, M.manifolds, submanifold_components(e.group, e.p))
end

Base.inv(G::ProductGroup, p) = inv(G.manifold, p)
Base.inv(::GT, e::Identity{GT}) where {GT<:ProductGroup} = e
function Base.inv(M::ProductManifold, x::ProductRepr)
    return ProductRepr(map(inv, M.manifolds, submanifold_components(M, x))...)
end
function Base.inv(M::ProductManifold, p)
    q = allocate_result(M, inv, p)
    return inv!(M, q, p)
end

inv!(G::ProductGroup, q, p) = inv!(G.manifold, q, p)
function inv!(M::ProductManifold, q, p)
    map(inv!, M.manifolds, submanifold_components(M, q), submanifold_components(M, p))
    return q
end

Base.identity(G::ProductGroup, p) = identity(G.manifold, p)
Base.identity(::GT, e::Identity{GT}) where {GT<:ProductGroup} = e
function Base.identity(M::ProductManifold, p::ProductRepr)
    return ProductRepr(map(identity, M.manifolds, submanifold_components(M, p))...)
end
function Base.identity(M::ProductManifold, p)
    q = allocate_result(M, identity, p)
    return identity!(M, q, p)
end

identity!(G::ProductGroup, q, p) = identity!(G.manifold, q, p)
function identity!(M::ProductManifold, q, p)
    map(identity!, M.manifolds, submanifold_components(M, q), submanifold_components(M, p))
    return q
end

compose(G::ProductGroup, p, q) = compose(G.manifold, p, q)
compose(G::GT, ::Identity{GT}, p) where {GT<:ProductGroup} = p
compose(G::GT, p, ::Identity{GT}) where {GT<:ProductGroup} = p
compose(G::GT, e::E, ::E) where {GT<:ProductGroup,E<:Identity{GT}} = e
function compose(M::ProductManifold, p::ProductRepr, q::ProductRepr)
    return ProductRepr(
        map(
            compose,
            M.manifolds,
            submanifold_components(M, p),
            submanifold_components(M, q),
        )...,
    )
end
function compose(M::ProductManifold, p, q)
    x = allocate_result(M, compose, p, q)
    return compose!(M, x, p, q)
end

compose!(G::ProductGroup, x, p, q) = compose!(G.manifold, x, p, q)
function compose!(M::ProductManifold, x, p, q)
    map(
        compose!,
        M.manifolds,
        submanifold_components(M, x),
        submanifold_components(M, p),
        submanifold_components(M, q),
    )
    return x
end

translate(G::ProductGroup, p, q, conv::ActionDirection) = translate(G.manifold, p, q, conv)
function translate(
    M::ProductManifold,
    p::ProductRepr,
    q::ProductRepr,
    conv::ActionDirection,
)
    return ProductRepr(
        map(
            translate,
            M.manifolds,
            submanifold_components(M, p),
            submanifold_components(M, q),
            repeated(conv),
        )...,
    )
end
function translate(M::ProductManifold, p, q, conv::ActionDirection)
    x = allocate_result(M, translate, p, q)
    return translate!(M, x, p, q, conv)
end

function translate!(G::ProductGroup, x, p, q, conv::ActionDirection)
    return translate!(G.manifold, x, p, q, conv)
end
function translate!(M::ProductManifold, x, p, q, conv::ActionDirection)
    map(
        translate!,
        M.manifolds,
        submanifold_components(M, x),
        submanifold_components(M, p),
        submanifold_components(M, q),
        repeated(conv),
    )
    return x
end

function inverse_translate(G::ProductGroup, p, q, conv::ActionDirection)
    return inverse_translate(G.manifold, p, q, conv)
end
function inverse_translate(
    M::ProductManifold,
    p::ProductRepr,
    q::ProductRepr,
    conv::ActionDirection,
)
    return ProductRepr(
        map(
            inverse_translate,
            M.manifolds,
            submanifold_components(M, p),
            submanifold_components(M, q),
            repeated(conv),
        )...,
    )
end
function inverse_translate(M::ProductManifold, p, q, conv::ActionDirection)
    x = allocate_result(M, inverse_translate, p, q)
    return inverse_translate!(M, x, p, q, conv)
end

function inverse_translate!(G::ProductGroup, x, p, q, conv::ActionDirection)
    return inverse_translate!(G.manifold, x, p, q, conv)
end
function inverse_translate!(M::ProductManifold, x, p, q, conv::ActionDirection)
    map(
        inverse_translate!,
        M.manifolds,
        submanifold_components(M, x),
        submanifold_components(M, p),
        submanifold_components(M, q),
        repeated(conv),
    )
    return x
end

function translate_diff(G::ProductGroup, p, q, X, conv::ActionDirection)
    return translate_diff(G.manifold, p, q, X, conv)
end
function translate_diff(
    M::ProductManifold,
    p::ProductRepr,
    q::ProductRepr,
    X::ProductRepr,
    conv::ActionDirection,
)
    return ProductRepr(
        map(
            translate_diff,
            M.manifolds,
            submanifold_components(M, p),
            submanifold_components(M, q),
            submanifold_components(M, X),
            repeated(conv),
        )...,
    )
end
function translate_diff(M::ProductManifold, p, q, X, conv::ActionDirection)
    Y = allocate_result(M, translate_diff, X, p, q)
    return translate_diff!(M, Y, p, q, X, conv)
end

function translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirection)
    return translate_diff!(G.manifold, Y, p, q, X, conv)
end
function translate_diff!(M::ProductManifold, Y, p, q, X, conv::ActionDirection)
    map(
        translate_diff!,
        M.manifolds,
        submanifold_components(M, Y),
        submanifold_components(M, p),
        submanifold_components(M, q),
        submanifold_components(M, X),
        repeated(conv),
    )
    return Y
end

function inverse_translate_diff(G::ProductGroup, p, q, X, conv::ActionDirection)
    return inverse_translate_diff(G.manifold, p, q, X, conv)
end
function inverse_translate_diff(
    M::ProductManifold,
    p::ProductRepr,
    q::ProductRepr,
    X::ProductRepr,
    conv::ActionDirection,
)
    return ProductRepr(
        map(
            inverse_translate_diff,
            M.manifolds,
            submanifold_components(M, p),
            submanifold_components(M, q),
            submanifold_components(M, X),
            repeated(conv),
        )...,
    )
end
function inverse_translate_diff(M::ProductManifold, p, q, X, conv::ActionDirection)
    Y = allocate_result(M, inverse_translate_diff, X, p, q)
    return inverse_translate_diff!(M, Y, p, q, X, conv)
end

function inverse_translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirection)
    return inverse_translate_diff!(G.manifold, Y, p, q, X, conv)
end
function inverse_translate_diff!(M::ProductManifold, Y, p, q, X, conv::ActionDirection)
    map(
        inverse_translate_diff!,
        M.manifolds,
        submanifold_components(M, Y),
        submanifold_components(M, p),
        submanifold_components(M, q),
        submanifold_components(M, X),
        repeated(conv),
    )
    return Y
end

function group_exp(M::ProductManifold, X::ProductRepr)
    return ProductRepr(map(group_exp, M.manifolds, submanifold_components(M, X))...)
end
function group_exp(M::ProductManifold, X)
    q = allocate_result(M, group_exp, X)
    return group_exp!(M, q, X)
end

function group_exp!(M::ProductManifold, q, X)
    map(group_exp!, M.manifolds, submanifold_components(M, q), submanifold_components(M, X))
    return q
end

function group_log(M::ProductManifold, q::ProductRepr)
    return ProductRepr(map(group_log, M.manifolds, submanifold_components(M, q))...)
end
function group_log(M::ProductManifold, q)
    X = allocate_result(M, group_log, q)
    return group_log!(M, X, q)
end

function group_log!(M::ProductManifold, X, q)
    map(group_log!, M.manifolds, submanifold_components(M, X), submanifold_components(M, q))
    return X
end


text:
using ClassicAlgorithmsCollections
using Documenter

makedocs(
    modules = [ClassicAlgorithmsCollections],
    authors = "Anselm Hahn <Anselm.Hahn@gmail.com>",
    repo = "https://github.com/Anselmoo/ClassicAlgorithmsCollections/blob/{commit}{path}#L{line}",
    sitename = "ClassicAlgorithmsCollections.jl",
    format = Documenter.HTML(;
        prettyurls = get(ENV, "CI", "false") == "true",
        canonical = "https://Anselmoo.github.io/ClassicAlgorithmsCollections",
        assets = String[],
    ),
    pages = [
        "Home" => "index.md",
        "Guide" => "man/guide.md",
        "Algorithms" => Any[
            "Sorting and Searching"=>"man/SortingAndSearching.md",
            "String and Arrays"=>"man/StringsAndArrays.md",
            "Graph"=>"man/Graph.md",
            "BITS"=>"man/Bits.md",
            "Dynamic Programming"=>"man/DynamicProgramming.md",
            "Number Theory"=>"man/NumberTheory.md",
        ],
    ],
)

deploydocs(;
    repo = "github.com/Anselmoo/ClassicAlgorithmsCollections",
    versions = ["stable" => "v^", "v#.#", "dev" => "dev"],
    push_preview    = true,
)

max_stars_repo_name: Anselmoo/ClassicAlgorithmsCollections

max_issues_repo_name: Anselmoo/ClassicAlgorithmsCollections

max_forks_repo_name: Anselmoo/ClassicAlgorithmsCollections


text:
#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Tests related to IGRF model.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

# File: ./src/earth/geomagnetic_field_models/igrf
# ===============================================

# Load the test files.
#
# The test values were created using the original `igrfXXsyn` functions, where
# `XX` is the IGRF version. The source code of the program that generated those
# files can be seen on:
#
#   https://github.com/SatelliteToolbox/IGRF_Test
#

const igrf12_geocentric_test = readdlm("./IGRF12_test_geocentric.txt")
const igrf12_geodetic_test   = readdlm("./IGRF12_test_geodetic.txt")
const igrf13_geocentric_test = readdlm("./IGRF13_test_geocentric.txt")
const igrf13_geodetic_test   = readdlm("./IGRF13_test_geodetic.txt")

# Function igrf12syn
# ------------------

@testset "Function igrf12syn" begin
    # Testing the geocentric part of the algorithm.
    for i = 1:size(igrf12_geocentric_test, 1)
        date  = igrf12_geocentric_test[i,1]
        r     = igrf12_geocentric_test[i,2]
        colat = igrf12_geocentric_test[i,3]
        elong = igrf12_geocentric_test[i,4]
        xt    = igrf12_geocentric_test[i,5]
        yt    = igrf12_geocentric_test[i,6]
        zt    = igrf12_geocentric_test[i,7]
        ft    = igrf12_geocentric_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        if date <= 2020
            (x, y, z, f) = igrf12syn(0, date, 2, r, colat, elong)
        else
            (x, y, z, f) = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2020.") #=
                =# igrf12syn(0, date, 2, r, colat, elong)
        end

        # Test the values.
        @test x ≈ xt atol=3e-1
        @test y ≈ yt atol=3e-1
        @test z ≈ zt atol=3e-1
        @test f ≈ ft atol=3e-1
    end

    # Testing the geodetic part of the algorithm.
    for i = 1:size(igrf12_geodetic_test, 1)
        date  = igrf12_geodetic_test[i,1]
        h     = igrf12_geodetic_test[i,2]
        colat = igrf12_geodetic_test[i,3]
        elong = igrf12_geodetic_test[i,4]
        xt    = igrf12_geodetic_test[i,5]
        yt    = igrf12_geodetic_test[i,6]
        zt    = igrf12_geodetic_test[i,7]
        ft    = igrf12_geodetic_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        if date <= 2020
            (x, y, z, f) = igrf12syn(0, date, 1, h, colat, elong)
        else
            (x, y, z, f) = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2020.") #=
                =# igrf12syn(0, date, 1, h, colat, elong)
        end

        # Test the values.
        @test x ≈ xt atol=3e-1
        @test y ≈ yt atol=3e-1
        @test z ≈ zt atol=3e-1
        @test f ≈ ft atol=3e-1
    end
end

# Function igrf13syn
# ------------------

@testset "Function igrf13syn" begin
    # Testing the geocentric part of the algorithm.
    for i = 1:size(igrf13_geocentric_test, 1)
        date  = igrf13_geocentric_test[i,1]
        r     = igrf13_geocentric_test[i,2]
        colat = igrf13_geocentric_test[i,3]
        elong = igrf13_geocentric_test[i,4]
        xt    = igrf13_geocentric_test[i,5]
        yt    = igrf13_geocentric_test[i,6]
        zt    = igrf13_geocentric_test[i,7]
        ft    = igrf13_geocentric_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        if date <= 2025
            (x, y, z, f) = igrf13syn(0, date, 2, r, colat, elong)
        else
            (x, y, z, f) = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                =# igrf13syn(0, date, 2, r, colat, elong)
        end

        # Test the values.
        @test x ≈ xt atol=3e-1
        @test y ≈ yt atol=3e-1
        @test z ≈ zt atol=3e-1
        @test f ≈ ft atol=3e-1
    end

    # Testing the geodetic part of the algorithm.
    for i = 1:size(igrf13_geodetic_test, 1)
        date  = igrf13_geodetic_test[i,1]
        h     = igrf13_geodetic_test[i,2]
        colat = igrf13_geodetic_test[i,3]
        elong = igrf13_geodetic_test[i,4]
        xt    = igrf13_geodetic_test[i,5]
        yt    = igrf13_geodetic_test[i,6]
        zt    = igrf13_geodetic_test[i,7]
        ft    = igrf13_geodetic_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        if date <= 2025
            (x, y, z, f) = igrf13syn(0, date, 1, h, colat, elong)
        else
            (x, y, z, f) = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                =# igrf13syn(0, date, 1, h, colat, elong)
        end

        # Test the values.
        @test x ≈ xt atol=3e-1
        @test y ≈ yt atol=3e-1
        @test z ≈ zt atol=3e-1
        @test f ≈ ft atol=3e-1
    end
end

# Function igrf
# -------------

@testset "Function igrf" begin
    # Auxiliary variables to use the version without allocations.
    P  = Matrix{Float64}(undef, 14, 14)
    dP = similar(P)

    # Testing the geocentric part of the algorithm.
    for i = 1:size(igrf13_geocentric_test, 1)
        date  = igrf13_geocentric_test[i,1]
        r     = igrf13_geocentric_test[i,2]
        colat = igrf13_geocentric_test[i,3]
        elong = igrf13_geocentric_test[i,4]
        xt    = igrf13_geocentric_test[i,5]
        yt    = igrf13_geocentric_test[i,6]
        zt    = igrf13_geocentric_test[i,7]
        ft    = igrf13_geocentric_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        (elong > 180) && (elong = elong-360)

        if date <= 2025
            Ba = igrf(date, r*1000, (90-colat)*pi/180, elong*pi/180)
            Bn = igrf(date, r*1000, (90-colat)*pi/180, elong*pi/180, P, dP)
        else
            Ba = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrf(date, r*1000, (90-colat)*pi/180, elong*pi/180)
            Bn = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrf(date, r*1000, (90-colat)*pi/180, elong*pi/180, P, dP)
        end

        x, y, z = Ba[:]
        f       = norm(Ba)

        # Test the values.
        @test x   ≈ xt atol=3e-1
        @test y   ≈ yt atol=3e-1
        @test z   ≈ zt atol=3e-1
        @test f   ≈ ft atol=3e-1
        @test Ba == Bn
    end

    # Testing the geodetic part of the algorithm.
    for i = 1:size(igrf13_geodetic_test, 1)
        date  = igrf13_geodetic_test[i,1]
        h     = igrf13_geodetic_test[i,2]
        colat = igrf13_geodetic_test[i,3]
        elong = igrf13_geodetic_test[i,4]
        xt    = igrf13_geodetic_test[i,5]
        yt    = igrf13_geodetic_test[i,6]
        zt    = igrf13_geodetic_test[i,7]
        ft    = igrf13_geodetic_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        (elong > 180) && (elong = elong-360)

        if date <= 2025
            Ba = igrf(date, h*1000, (90-colat)*pi/180, elong*pi/180, Val(:geodetic))
            Bn = igrf(date, h*1000, (90-colat)*pi/180, elong*pi/180, Val(:geodetic))
        else
            Ba = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrf(date, h*1000, (90-colat)*pi/180, elong*pi/180, Val(:geodetic))
            Bn = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrf(date, h*1000, (90-colat)*pi/180, elong*pi/180, Val(:geodetic), P, dP)
        end

        x, y, z = Ba[:]
        f       = norm(Ba)

        # Test the values.
        @test x   ≈ xt atol=3e-1
        @test y   ≈ yt atol=3e-1
        @test z   ≈ zt atol=3e-1
        @test f   ≈ ft atol=3e-1
        @test Ba == Bn
    end
end

# Function igrfd
# --------------

@testset "Function igrfd" begin
        # Auxiliary variables to use the version without allocations.
    P  = Matrix{Float64}(undef, 14, 14)
    dP = similar(P)

    # Testing the geocentric part of the algorithm.
    for i = 1:size(igrf13_geocentric_test, 1)
        date  = igrf13_geocentric_test[i,1]
        r     = igrf13_geocentric_test[i,2]
        colat = igrf13_geocentric_test[i,3]
        elong = igrf13_geocentric_test[i,4]
        xt    = igrf13_geocentric_test[i,5]
        yt    = igrf13_geocentric_test[i,6]
        zt    = igrf13_geocentric_test[i,7]
        ft    = igrf13_geocentric_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        (elong > 180) && (elong = elong-360)

        if date <= 2025
            Ba = igrfd(date, r*1000, 90-colat, elong)
            Bn = igrfd(date, r*1000, 90-colat, elong, P, dP)
        else
            Ba = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrfd(date, r*1000, 90-colat, elong)
            Bn = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrfd(date, r*1000, 90-colat, elong, P, dP)
        end

        x, y, z = Ba[:]
        f       = norm(Ba)

        # Test the values.
        @test x   ≈ xt atol=3e-1
        @test y   ≈ yt atol=3e-1
        @test z   ≈ zt atol=3e-1
        @test f   ≈ ft atol=3e-1
        @test Ba == Bn
    end

    # Testing the geodetic part of the algorithm.
    for i = 1:size(igrf13_geodetic_test, 1)
        date  = igrf13_geodetic_test[i,1]
        h     = igrf13_geodetic_test[i,2]
        colat = igrf13_geodetic_test[i,3]
        elong = igrf13_geodetic_test[i,4]
        xt    = igrf13_geodetic_test[i,5]
        yt    = igrf13_geodetic_test[i,6]
        zt    = igrf13_geodetic_test[i,7]
        ft    = igrf13_geodetic_test[i,8]

        # Call IGRF with the same inputs as those in the test.
        (elong > 180) && (elong = elong-360)

        if date <= 2025
            Ba = igrfd(date, h*1000, 90-colat, elong, Val(:geodetic))
            Bn = igrfd(date, h*1000, 90-colat, elong, Val(:geodetic), P, dP)
        else
            Ba = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrfd(date, h*1000, 90-colat, elong, Val(:geodetic))
            Bn = @test_logs (:warn, "The magnetic field computed with this IGRF version may be of reduced accuracy for years greater than 2025.") #=
                 =# igrfd(date, h*1000, 90-colat, elong, Val(:geodetic), P, dP)
        end

        x, y, z = Ba[:]
        f       = norm(Ba)

        # Test the values.
        @test x   ≈ xt atol=3e-1
        @test y   ≈ yt atol=3e-1
        @test z   ≈ zt atol=3e-1
        @test f   ≈ ft atol=3e-1
        @test Ba == Bn
    end

    # Errors
    # --------------------------------------------------------------------------

    @test_throws ErrorException igrfd(2010, 7000, -91,    0)
    @test_throws ErrorException igrfd(2010, 7000, +91,    0)
    @test_throws ErrorException igrfd(2010, 7000, 0, -181)
    @test_throws ErrorException igrfd(2010, 7000, 0, +181)
end


text:
# Autogenerated wrapper script for raptor_jll for x86_64-linux-musl
export RAPTOR

using GSL_jll
using libpng_jll
using CompilerSupportLibraries_jll
JLLWrappers.@generate_wrapper_header("raptor")
JLLWrappers.@declare_executable_product(RAPTOR)
function __init__()
    JLLWrappers.@generate_init_header(GSL_jll, libpng_jll, CompilerSupportLibraries_jll)
    JLLWrappers.@init_executable_product(
        RAPTOR,
        "bin/RAPTOR",
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
using Test

using DesignAutomation

@testset "golden_section" begin
    # basic quadratics
    fn(x) = (x-4)^2
    expected = DesignAutomation.Response(
        4.000009596893628,
        9.210036731151505e-11,
        23,
        true
    )

    @test golden_section(fn) == expected

    fn(x) = 2 - 4*x + exp(x)
    expected = DesignAutomation.Response(
        1.3863011401228174,
        0.4548225556123473,
        22,
        true
    )

    @test golden_section(fn; δ=.5) == expected

    # quadratic
    fn(x) = 3*x^2 + 12/x^3 - 5
    expected = DesignAutomation.Response(
        1.4309864897989497,
        5.238362559941962,
        22,
        true
    )

    @test golden_section(fn; δ=.5) == expected

    # quadratic, with bounds
    expected = DesignAutomation.Response(
        1.4309790387227168,
        5.238362556883395,
        24,
        true
    )

    @test golden_section(
        fn; bounds=[.5, 6], δ=.5) == expected

    # sin
    fn(x) = sin(x)
    expected = DesignAutomation.Response(
        4.712370276668626, # 3π/2
        -0.9999999998250855,
        24,
        true
    )

    @test golden_section(fn) == expected
end

max_stars_repo_name: camirmas/DesignAutomation

max_issues_repo_name: camirmas/DesignAutomation

max_forks_repo_name: camirmas/DesignAutomation


text:
@testset "ACSF Descriptor with Tanh Cutoff function" begin

@info("Testing ACSF Descriptor for dc Si with Tanh cutoff function.")
using ACSF, JuLIP, Test

at = bulk(:Si, cubic=true)
desc = acsf(at, cutfunc="Tanh")
#round.(desc[1], digit=7) == acsfpy

acsf_ref = [1.7007052856995, 1.1673457218371,
            0.8056685651973, 0.4959433476485,
            0.2531071275919, 0.1033559431748,
            0.0138370429717, 0.0002664727401,
            0.0338991567076, 0.0890719236666,
            0.0156854176408, 0.0708581845999,
            0.0240759835270, 0.0630065104164,
            0.0113341745307, 0.0502647014201,
            0.0136132348530, 0.0353997671701,
            0.0065836992180, 0.0283702315352,
            0.0063508468825, 0.0163914850457,
            0.0031689489094, 0.0132095870726,
            0.0012299277014, 0.0098887900622,
            0.0000106088e-5, 0.0029245491351,
            0.0022580079204, 0.0057823240740,
            0.0011636531625, 0.0046879693161,
            0.0004696015908, 0.0035649802825,
            3.7013292128e-6, 0.0010783907380,
            0.0003206000003, 0.0008143046788,
            0.0001708095102, 0.0006645141887,
            7.1667175764e-5, 0.0005136395522,
            5.4718112317e-7, 0.0001587403432,
            1.2136187945e-5, 3.0709048356e-5,
            6.5665761557e-6, 2.5139436567e-5,
            2.8048095043e-6, 1.9579283755e-5,
            6.1054165366e-6]
acsf_now = vcat(desc[1,:]...)
println("ACSF.jl returns:",acsf_now)
println("Reference:",acsf_ref)
println(@test acsf_now  ≈  acsf_ref)
end


text:
@testset "Interpolation" begin
    println("INTERPOLATION TEST")

    test_set = [
        (input="T/polar", freqrange=(1kHz, 12kHz), max_mag=30.0, mae_mag=5.0, max_phi=180.0, mae_phi=60.0),
        (input="T/polar_noXO", freqrange=(1kHz, 12kHz), max_mag=30.0, mae_mag=5.0, max_phi=180.0, mae_phi=60.0),

        (input="M/polar", freqrange=(200Hz, 12kHz), max_mag=35.0, mae_mag=3.0, max_phi=180.0, mae_phi=35.0),
        (input="M/polar_noXO", freqrange=(200Hz, 12kHz), max_mag=35.0, mae_mag=3.0, max_phi=180.0, mae_phi=35.0),

        (input="W/polar", freqrange=(0Hz, 1kHz), max_mag=2.0, mae_mag=0.5, max_phi=20.0, mae_phi=3.0),
        (input="W/polar_noXO", freqrange=(0Hz, 1kHz), max_mag=2.0, mae_mag=0.5, max_phi=20.0, mae_phi=3.0),
    ]
    # NOTE: errors are quite large, mainly for higher frequencies
    for e in test_set
        println("  \"$(e.input)\"")
        input_dir = joinpath(test_dir, e.input)
        print("\tA:")
        @test test_interpolation(input_dir, e.freqrange, compare_magdb, e.max_mag, e.mae_mag)
        print("\tΦ:")
        @test test_interpolation(input_dir, e.freqrange, compare_phasedeg, e.max_phi, e.mae_phi)
    end
end