# Test variable reference makers
@testset "Variable Reference Makers/Deleters" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, par1 in [0, 2])
    @infinite_parameter(m, par2 in [0, 2])
    @infinite_variable(m, inf1(par1))
    @infinite_variable(m, inf2(par1, par2))
    d1 = @deriv(inf1, par1)
    d2 = @deriv(inf2, par1)
    # test make_point_variable_ref (InfiniteModel)
    @testset "make_point_variable_ref (InfiniteModel)" begin
        # test with inf1
        pvref = GeneralVariableRef(m, 1, PointVariableIndex)
        @test make_point_variable_ref(m, inf1, Float64[0]) == pvref
        @test parameter_values(pvref) == (0.,)
        # test with inf2
        pvref = GeneralVariableRef(m, 2, PointVariableIndex)
        @test make_point_variable_ref(m, inf2, Float64[0, 0]) == pvref
        @test parameter_values(pvref) == (0., 0.)
        # test with d1
        pvref = GeneralVariableRef(m, 3, PointVariableIndex)
        @test make_point_variable_ref(m, d1, Float64[0]) == pvref
        @test parameter_values(pvref) == (0.,)
        # test with d2
        pvref = GeneralVariableRef(m, 4, PointVariableIndex)
        @test make_point_variable_ref(m, d2, Float64[0, 0]) == pvref
        @test parameter_values(pvref) == (0., 0.)
    end
    # test make_point_variable_ref with infinite parameter functions
    @testset "make_point_variable_ref (Parameter Function)" begin
        f = parameter_function(sin, par1)
        @test make_point_variable_ref(m, f, [0.]) == 0
        @test make_point_variable_ref(Model(), f, [0.]) == 0
    end
    # test add_measure_variable
    @testset "add_measure_variable" begin
        @test_throws ErrorException add_measure_variable(Model(), Bad(), Val(:some_key))
    end
    # test make_point_variable_ref (optmizer_model)
    @testset "make_point_variable_ref (optimizer_model)" begin
        opt_m = Model()
        opt_m.ext[:my_key] = 42
        @test_throws ErrorException make_point_variable_ref(opt_m, inf1, Float64[0])
    end
    # test make_reduced_variable_ref (InfiniteModel)
    @testset "make_reduced_variable_ref (InfiniteModel)" begin
        # test first addition
        rvref1 = GeneralVariableRef(m, 1, ReducedVariableIndex)
        @test make_reduced_variable_ref(m, inf2, [1], Float64[1]) == rvref1
        @test infinite_variable_ref(rvref1) == inf2
        @test eval_supports(rvref1) == Dict(1 => Float64(1))
        # test second addition
        rvref2 = GeneralVariableRef(m, 2, ReducedVariableIndex)
        @test make_reduced_variable_ref(m, inf2, [1], Float64[0]) == rvref2
        @test infinite_variable_ref(rvref2) == inf2
        @test eval_supports(rvref2) == Dict(1 => Float64(0))
        # test first addition with derivative
        rvref1 = GeneralVariableRef(m, 3, ReducedVariableIndex)
        @test make_reduced_variable_ref(m, d2, [1], Float64[1]) == rvref1
        @test infinite_variable_ref(rvref1) == d2
        @test eval_supports(rvref1) == Dict(1 => Float64(1))
        # test second addition with derivative
        rvref2 = GeneralVariableRef(m, 4, ReducedVariableIndex)
        @test make_reduced_variable_ref(m, d2, [1], Float64[0]) == rvref2
        @test infinite_variable_ref(rvref2) == d2
        @test eval_supports(rvref2) == Dict(1 => Float64(0))
    end
    # test make_reduced_variable_ref (optimizer_model)
    @testset "make_reduced_variable_ref (optimizer_model)" begin
        opt_m = Model()
        opt_m.ext[:my_key] = 42
        @test_throws ErrorException make_reduced_variable_ref(opt_m, inf2, [1], Float64[1])
    end
    # test delete_internal_reduced_variable (InfiniteModel)
    @testset "delete_internal_reduced_variable (InfiniteModel)" begin
        rvref1 = ReducedVariableRef(m, ReducedVariableIndex(1))
        rvref2 = ReducedVariableRef(m, ReducedVariableIndex(2))
        rvref3 = ReducedVariableRef(m, ReducedVariableIndex(3))
        rvref4 = ReducedVariableRef(m, ReducedVariableIndex(4))
        # test cannot delete
        push!(InfiniteOpt._constraint_dependencies(rvref1), ConstraintIndex(1))
        @test delete_internal_reduced_variable(m, rvref1) isa Nothing
        @test is_valid(m, rvref1)
        empty!(InfiniteOpt._constraint_dependencies(rvref1))
        # test can delete
        @test delete_internal_reduced_variable(m, rvref1) isa Nothing
        @test delete_internal_reduced_variable(m, rvref2) isa Nothing
        @test delete_internal_reduced_variable(m, rvref3) isa Nothing
        @test delete_internal_reduced_variable(m, rvref4) isa Nothing
        @test !is_valid(m, rvref1)
        @test !is_valid(m, rvref2)
        @test !is_valid(m, rvref3)
        @test !is_valid(m, rvref4)
    end
    # test delete_reduced_variable
    @testset "delete_reduced_variable" begin
        rvref = ReducedVariableRef(m, ReducedVariableIndex(1))
        warn = "'delete_reduced_variable' not extended for reduced variable type " *
              "`ReducedVariableRef` and optimizer model with key `bad`."
        @test_logs (:warn, warn) delete_reduced_variable(Model(), rvref, Val(:bad))
    end
    # test delete_internal_reduced_variable (optimizer_model)
    @testset "delete_internal_reduced_variable (optimizer_model)" begin
        rvref = ReducedVariableRef(m, ReducedVariableIndex(1))
        @test !is_valid(m, rvref)
        opt_m = Model()
        opt_m.ext[:my_key] = 42
        warn = "'delete_reduced_variable' not extended for reduced variable type " *
              "`ReducedVariableRef` and optimizer model with key `my_key`."
        @test_logs (:warn, warn) delete_internal_reduced_variable(opt_m, rvref)
    end
end

# Test measure expansion methods
@testset "expand_measure" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 1 <= par1 <= 2)
    @infinite_parameter(m, 1 <= par2 <= 2)
    @infinite_parameter(m, 1 <= pars1[1:2] <= 2, independent = true)
    @infinite_parameter(m, 1 <= pars2[1:2] <= 2)
    @infinite_variable(m, inf1(par1))
    @infinite_variable(m, inf2(par1, par2))
    @infinite_variable(m, inf3(par2))
    @infinite_variable(m, inf4(pars1))
    @infinite_variable(m, inf5(pars1, pars2))
    @infinite_variable(m, inf6(pars2))
    @infinite_variable(m, inf7(par1, par2, pars1))
    @infinite_variable(m, inf8(pars1[1]))
    @infinite_variable(m, inf9(pars1[1], par1))
    @hold_variable(m, x)
    d1 = @deriv(inf1, par1)
    d2 = @deriv(inf2, par1)
    d3 = @deriv(inf3, par2)
    d4 = @deriv(inf4, pars1[1])
    d5 = @deriv(inf5, pars1[1])
    d6 = @deriv(inf6, pars2[1])
    d7 = @deriv(inf7, par1)
    d8 = @deriv(inf8, pars1[1])
    d9 = @deriv(inf9, pars1[1])
    f1 = parameter_function(sin, par1)
    f2 = parameter_function((a,b) -> 1, (par1, par2))
    f3 = parameter_function(cos, par2)
    f4 = parameter_function(a -> -1, (pars1,))
    f5 = parameter_function((a,b) -> 1, (pars1, pars2))
    f6 = parameter_function(a -> -1, (pars2,))
    f7 = parameter_function((a,b,c) -> 1, (par1, par2, pars1))
    f8 = parameter_function(cos, pars1[1])
    f9 = parameter_function((a,b) -> 1, (pars1[1], par1))
    # prepare measures
    w(t) = 3
    data1 = DiscreteMeasureData(par1, [0.5, 0.5], [1, 2])
    data2 = DiscreteMeasureData(par2, [0.5, 0.5], [1, 1], weight_function = w)
    data3 = DiscreteMeasureData(pars1, [1, 1], [[1, 1], [2, 2]])
    data4 = DiscreteMeasureData(pars2, [2, 2], [[1, 1], [1, 1]])
    data5 = DiscreteMeasureData([pars1[2], pars1[1]], [1, 1], [[1, 1.2], [2, 2]])
    data6 = DiscreteMeasureData([pars1[2]], [1, 1], [[1], [2]])
    data7 = DiscreteMeasureData(pars1[1], [0.5, 0.5], [1, 2])
    meas1 = measure(inf1, data1)
    meas2 = measure(2 * inf3 * x - par1, data2)
    # test expand_measure (infinite variable) with DiscreteMeasureData
    @testset "Infinite Variable (1D DiscreteMeasureData)" begin
        # test single param infinite var with measure param
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(inf1, data1, m) == 0.5 * (pts[1] + pts[2])
        @test parameter_values.(pts) == [(1.,), (2.,)]
        # test single param infinite var without measure param
        @test InfiniteOpt.expand_measure(inf1, data2, m) == 3inf1
        # test single param infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(inf2, data1, m) == 0.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2.)
        # test multi param infinite var with single element evaluation
        rv1 = GeneralVariableRef(m, idx + 2, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 3, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(inf7, data7, m) == 0.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(3 => 2.)
    end
    # test expand_measure (derivatives) with DiscreteMeasureData
    @testset "Derivative (1D DiscreteMeasureData)" begin
        # test single param infinite var with measure param
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(d1, data1, m) == 0.5 * (pts[1] + pts[2])
        @test parameter_values.(pts) == [(1.,), (2.,)]
        # test single param infinite var without measure param
        @test InfiniteOpt.expand_measure(d1, data2, m) == 3d1
        # test single param infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(d2, data1, m) == 0.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2.)
        # test multi param infinite var with single element evaluation
        rv1 = GeneralVariableRef(m, idx + 2, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 3, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(d7, data7, m) == 0.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(3 => 2.)
    end
    # test expand_measure (infinite parameter functions) with DiscreteMeasureData
    @testset "Parameter Function (1D DiscreteMeasureData)" begin
        # test single param infinite var with measure param
        @test InfiniteOpt.expand_measure(f1, data1, m) == 0.5 * (sin(1) + sin(2))
        # test single param infinite var without measure param
        @test InfiniteOpt.expand_measure(f1, data2, m) == 3 * f1
        # test single param infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(f2, data1, m) == 0.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2.)
        # test multi param infinite var with single element evaluation
        rv1 = GeneralVariableRef(m, idx + 2, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 3, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(f7, data7, m) == 0.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(3 => 2.)
    end
    # test expand_measure (infinite variable) with Multi DiscreteMeasureData
    @testset "Infinite Variable (Multi DiscreteMeasureData)" begin
        # test infinite var with measure param
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(inf4, data3, m) == pts[1] + pts[2]
        @test parameter_values(pts[1]) == (Float64[1., 1.],)
        # test infinite var without measure param
        @test InfiniteOpt.expand_measure(inf4, data4, m) == 4inf4
        # test infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(inf5, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2, 2 => 2)
        # test infinite var with measure param that is out of order
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(inf4, data5, m) == pts[1] + pts[2]
        @test parameter_values(pts[1]) == (Float64[1.2, 1],)
        # test infinite var with measure param that doesn't overlap
        @test InfiniteOpt.expand_measure(inf8, data6, m) == 2inf8
        # test infinite var with measure param that doesn't overlap and there are others
        @test InfiniteOpt.expand_measure(inf9, data6, m) == 2inf9
        # test infinite variable has subset of multi params
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(inf8, data3, m) == pts[1] + pts[2]
        @test parameter_values(pts[2]) == (2.,)
        # test making reduced vars with variable not containing all the measure prefs
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(inf9, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2)
    end
    # test expand_measure (derivatives) with Multi DiscreteMeasureData
    @testset "Derivative (Multi DiscreteMeasureData)" begin
        # test infinite var with measure param
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(d4, data3, m) == pts[1] + pts[2]
        @test parameter_values(pts[1]) == (Float64[1., 1.],)
        # test infinite var without measure param
        @test InfiniteOpt.expand_measure(d4, data4, m) == 4d4
        # test infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(d5, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2, 2 => 2)
        # test infinite var with measure param that is out of order
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(d4, data5, m) == pts[1] + pts[2]
        @test parameter_values(pts[1]) == (Float64[1.2, 1],)
        # test infinite var with measure param that doesn't overlap
        @test InfiniteOpt.expand_measure(d8, data6, m) == 2d8
        # test infinite var with measure param that doesn't overlap and there are others
        @test InfiniteOpt.expand_measure(d9, data6, m) == 2d9
        # test infinite variable has subset of multi params
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(d8, data3, m) == pts[1] + pts[2]
        @test parameter_values(pts[2]) == (2.,)
        # test making reduced vars with variable not containing all the measure prefs
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(d9, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2)
    end
    # test expand_measure (infinite parameter functions) with Multi DiscreteMeasureData
    @testset "Parameter Function (Multi DiscreteMeasureData)" begin
        # test infinite var with measure param
        @test InfiniteOpt.expand_measure(f4, data3, m) == -2
        # test infinite var without measure param
        @test InfiniteOpt.expand_measure(f4, data4, m) == 4 * f4
        # test infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(f5, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2, 2 => 2)
        # test infinite var with measure param that is out of order
        @test InfiniteOpt.expand_measure(f4, data5, m) == -2
        # test infinite var with measure param that doesn't overlap
        @test InfiniteOpt.expand_measure(f8, data6, m) == 2 * f8
        # test infinite var with measure param that doesn't overlap and there are others
        @test InfiniteOpt.expand_measure(f9, data6, m) == 2 * f9
        # test infinite variable has subset of multi params
        @test InfiniteOpt.expand_measure(f8, data3, m) == cos(1) + cos(2)
        # test making reduced vars with variable not containing all the measure prefs
        idx = length(InfiniteOpt._data_dictionary(m, ReducedVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(f9, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 2)
    end
    @testset "_make_point_support (1D DiscreteMeasureData)" begin
        orig_prefs = parameter_list(inf2)
        support_dict = Dict{Int, Float64}(2 => 42)
        expected = Float64[23, 42]
        @test InfiniteOpt._make_point_support(orig_prefs, support_dict, 1, Float64(23)) == expected
    end
    # test expand_measure (reduced infinite variable)
    @testset "Reduced Variable (1D DiscreteMeasureData)" begin
        # test single param reduced var without measure param
        rv = make_reduced_variable_ref(m, inf2, [1], Float64[1])
        @test InfiniteOpt.expand_measure(rv, data1, m) == 0.5 * (rv + rv)
        # test single param reduced var with measure param
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(rv, data2, m) == 1.5 * (pts[1] + pts[2])
        @test parameter_values(pts[1]) == (1., 1.)
        @test !is_valid(m, rv)
        # test single param reduced var with measure param and others
        rv = make_reduced_variable_ref(m, inf7, [1], Float64[1])
        idx = index(rv).value + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(rv, data2, m) == 1.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 1, 2 => 1)
        @test !is_valid(m, rv)
        # test single param reduced var partially from array element
        rv = make_reduced_variable_ref(m, inf7, [1], Float64[1])
        idx = index(rv).value + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(rv, data7, m) == 0.5 * (rv1 + rv2)
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 1, 3 => 2)
        @test !is_valid(m, rv)
    end
    @testset "_make_point_support (Multi DiscreteMeasureData)" begin
        orig_prefs = parameter_list(inf7)
        support_dict = Dict{Int, Float64}(1 => 23, 2 => 42)
        expected = Float64[23, 42, 3, 2]
        vals = Float64[3, 2]
        @test InfiniteOpt._make_point_support(orig_prefs, support_dict, [3, 4], vals) == expected
    end
    # test expand_measure (reduced infinite variable)
    @testset "Reduced Variable (Multi DiscreteMeasureData)" begin
        # test array param reduced var without measure param
        rv = make_reduced_variable_ref(m, inf5, [1, 2], Float64[1, 1])
        @test InfiniteOpt.expand_measure(rv, data3, m) == rv + rv
        # test array param reduced var with measure param
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(rv, data4, m) == 2 * (pts[1] + pts[2])
        @test parameter_values(pts[2]) == (Float64[1, 1], Float64[1, 1])
        @test !is_valid(m, rv)
        # test array param reduced var with measure param and others
        rv = make_reduced_variable_ref(m, inf7, [1], Float64[1])
        idx = index(rv).value + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(rv, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(1 => 1, 3 => 2, 4 => 2)
        @test !is_valid(m, rv)
        # test array param that is out of order and should make point variable
        rv = make_reduced_variable_ref(m, inf5, [3, 4], Float64[1, 1])
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        @test InfiniteOpt.expand_measure(rv, data5, m) == pts[1] + pts[2]
        @test parameter_values(pts[1]) == (Float64[1.2, 1], Float64[1, 1])
        @test !is_valid(m, rv)
        # test array param that with no others that partially overlap
        rv = make_reduced_variable_ref(m, inf5, [2, 3, 4], Float64[1, 1, 1])
        @test InfiniteOpt.expand_measure(rv, data6, m) == 2rv
        # test array param with others that partially overlap
        rv = make_reduced_variable_ref(m, inf5, [2], Float64[1])
        @test InfiniteOpt.expand_measure(rv, data6, m) == 2rv
        # test partial overlap
        rv = make_reduced_variable_ref(m, inf5, [2], Float64[1])
        idx = index(rv).value + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        @test InfiniteOpt.expand_measure(rv, data3, m) == rv1 + rv2
        @test eval_supports(rv2) == Dict{Int, Float64}(2 => 1, 1 => 2)
        @test !is_valid(m, rv)
    end
    # test expand_measure (finite variable)
    @testset "Finite Variable (1D DiscreteMeasureData)" begin
        # test with single parameter measure
        @test InfiniteOpt.expand_measure(x, data1, m) == 0.5 * (x + x)
    end
    # test expand_measure (finite variable)
    @testset "Finite Variable (Multi DiscreteMeasureData)" begin
        # test with multi parameter measure
        @test InfiniteOpt.expand_measure(x, data3, m) == x + x
    end
    # test expand_measure (parameter)
    @testset "Infinite Parameter (1D DiscreteMeasureData)" begin
        # test with different parameter
        @test InfiniteOpt.expand_measure(par1, data2, m) == 1.5 * (par1 + par1)
        # test with same parameter
        @test InfiniteOpt.expand_measure(par1, data1, m) == 0.5 * (1. + 2.)
    end
    # test expand_measure (multi measure data with parameter)
    @testset "Infinite Parameter (Multi DiscreteMeasureData)" begin
        # test with different parameter
        @test InfiniteOpt.expand_measure(par1, data3, m) == par1 + par1
        # test with same parameter
        @test InfiniteOpt.expand_measure(pars1[2], data3, m) == 3.
    end
    # test expand_measure (Finite Expr) with DiscreteMeasureData
    @testset "Finite Expression (1D DiscreteMeasureData)"  begin
        # test AffExpr
        expr = 2x +3
        @test InfiniteOpt.expand_measure(expr, data2, m) == expr * 3
        # test QuadExpr
        expr = x^2 + 2x +3
        @test InfiniteOpt.expand_measure(expr, data2, m) == expr * 3
    end
    # test expand_measure (Finite Expr) with DiscreteMeasureData
    @testset "Finite Expression (Multi DiscreteMeasureData)"  begin
        # test AffExpr
        expr = 2x +3
        @test InfiniteOpt.expand_measure(expr, data3, m) == expr * 2
        # test QuadExpr
        expr = x^2 + 2x +3
        @test InfiniteOpt.expand_measure(expr, data4, m) == expr * 4
    end
    # test expand_measure (AffExpr)
    @testset "AffExpr (1D DiscreteMeasureData)" begin
        # test single param AffExpr, no measures
        expr = 2inf1 + par1 - x + 3par2 - 3
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 0.5 * (2pts[1] + 2pts[2] + 1 + 2 - x - x + 3par2 + 3par2 - 6)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
        # test single param AffExpr, with measures
        expr = meas2 - x + par1
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 0.5 * (6 * (pts[1] * x + pts[2] * x) - 2x - 6)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
    end
    # test expand_measure (AffExpr)
    @testset "AffExpr (Multi DiscreteMeasureData)" begin
        # test array param AffExpr, no measures
        expr = inf4 + par1 - x + 3pars1[2] - 1
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = pts[1] + pts[2] + 2par1 - 2x + 9 - 2
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
        # test array param AffExpr, with measures
        expr = meas2 - x + par1 - inf4
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex),
               GeneralVariableRef(m, idx + 2, PointVariableIndex),
               GeneralVariableRef(m, idx + 3, PointVariableIndex)]
        expected = 6 * (pts[1] * x + pts[2] * x) - 4par1 - 2x - pts[3] - pts[4]
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
    end
    # test expand_measure (QuadExpr)
    @testset "QuadExpr (1D DiscreteMeasureData)" begin
        # test single param QuadExpr with both variables integrated or not
        expr = 2 * inf1 * inf2 - inf3 * inf4 + x + 2
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        expected = 0.5 * (2 * pts[1] * rv1 + - 2 * inf3 * inf4 + 2 * pts[2] *
                          rv2 + 2x + 4)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
        # test single param QuadExpr with first variable not integrated
        expr = 3 * inf3 * inf1 + pars1[1] - 1
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 0.5 * (3 * inf3 * pts[1] + 3 * inf3 * pts[2] + 2pars1[1] - 2)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
        # test single param QuadExpr with first variable integrated
        expr = 3 * inf2 * inf1 + pars1[1] + 1
        idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        expected = 1.5 * (3 * rv1 * inf1 + 3 * rv2 * inf1 + 2pars1[1] + 2)
        @test InfiniteOpt.expand_measure(expr, data2, m) == expected
        # test single parameter with first quadratic term becomes number
        expr = par1 * inf1 + 2
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 0.5 * (pts[1] + 2 * pts[2] + 4)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
        # test single parameter with first quadratic term becomes number, 2nd constant
        expr = par1 * x + 2
        expected = 0.5 * (x + 2 * x + 4)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
        # test single parameter with both quadratic terms become numbers
        expr = par1 * par1 + 2
        expected = 0.5 * (1 + 2 * 2 + 4)
        @test InfiniteOpt.expand_measure(expr, data1, m) == Float64(expected)
        # test single parameter with 2nd quadratic term becomes number
        expr = inf1 * par1 + 2
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 0.5 * (pts[1] + 2 * pts[2] + 4)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
        # test single parameter with 2nd quadratic term becomes number, 1st constant
        expr = x * par1 + 2
        expected = 0.5 * (x + 2 * x + 4)
        @test InfiniteOpt.expand_measure(expr, data1, m) == expected
    end
    # test expand_measure (QuadExpr)
    @testset "QuadExpr (Multi DiscreteMeasureData)" begin
        # test array param QuadExpr with both variables integrated
        expr = 2 * inf4 * inf5 - inf1 * inf2 + x + 2
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
               idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
               rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
               rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        expected = 2 * pts[1] * rv1 - 2 * inf1 * inf2 + 2 * pts[2] * rv2 + 2x + 4
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
        # test array param QuadExpr with first variable not integrated
        expr = 3 * inf1 * inf4 + par1 - 1
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 3 * inf1 * pts[1] + 3 * inf1 * pts[2] + 2par1 - 2
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
        # test array param QuadExpr with first variable integrated
        expr = 3 * inf5 * inf1 + pars1[1] + 1
        idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        expected = 2 * (3 * rv1 * inf1 + 3 * rv2 * inf1 + 2pars1[1] + 2)
        @test InfiniteOpt.expand_measure(expr, data4, m) == expected
        # test array parameter with first quadratic term becomes number
        expr = pars1[1] * inf4 + 2
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = pts[1] + 2 * pts[2] + 4
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
        # test array parameter with first quadratic term becomes number, 2nd constant
        expr = pars1[1] * x + 2
        expected = x + 2 * x + 4
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
        # test array parameter with both quadratic terms become numbers
        expr = pars1[1] * pars1[2] + 2
        expected = 1 + 2 * 2 + 4
        @test InfiniteOpt.expand_measure(expr, data3, m) == Float64(expected)
        # test array parameter with 2nd quadratic term becomes number
        expr = inf4 * pars1[1] + 2
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = pts[1] + 2 * pts[2] + 4
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
        # test array parameter with 2nd quadratic term becomes number, 1st constant
        expr = x * pars1[1] + 2
        expected = x + 2 * x + 4
        @test InfiniteOpt.expand_measure(expr, data3, m) == expected
    end
    # test expand_measure (measure)
    @testset "Measure" begin
        # test single param measure
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 0.5 * (pts[1] + pts[2])
        @test InfiniteOpt.expand_measure(meas1, data1, m) == expected
        # test another single param
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 0.5 * (6 * pts[1] * x + 6 * pts[2] * x - 3 * (1 + 2))
        @test InfiniteOpt.expand_measure(meas2, data1, m) == expected
        # test array param measure
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = pts[1] + pts[2]
        @test InfiniteOpt.expand_measure(meas1, data3, m) == expected
    end
    # test expand_measure (FunctionalDiscreteMeasureData)
    @testset "FunctionalDiscreteMeasureData" begin
        # test without generative supports
        coef(a) = ones(length(a))
        data = FunctionalDiscreteMeasureData(par1, coef, 2, All, is_expect = true)
        @test InfiniteOpt.expand_measure(x, data, m) == 2x
        # test with generative supports
        coef2(a) = ones(length(a) + 1)
        info = UniformGenerativeInfo([0.5], InternalLabel)
        data = FunctionalDiscreteMeasureData(par1, coef2, 2, All, generative_support_info = info)
        InfiniteOpt._set_generative_support_info(dispatch_variable_ref(par1), info)
        @test InfiniteOpt.expand_measure(x, data, m) == 3x
    end
    # test expand_measure (other)
    @testset "Other" begin
        # prepare test
        @variable(Model(), y)
        # test it
        @test_throws ErrorException InfiniteOpt.expand_measure(y, BadData(), m)
    end
end

# Test analytic_expansion
@testset "analytic_expansion" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 1 <= par1 <= 2)
    @infinite_parameter(m, 1 <= par2 <= 2)
    @infinite_parameter(m, 1 <= pars1[1:2] <= 2, num_supports = 2)
    @infinite_variable(m, inf3(par2))
    @hold_variable(m, x)
    # test with 1D data
    @testset "1D Discrete Data" begin
        # test with bounds
        data = DiscreteMeasureData(par1, [1], [1], lower_bound = 1,
                                   upper_bound = 1.5)
        @test InfiniteOpt.analytic_expansion(2x + inf3, data, m) == 0.5 * (2x + inf3)
        # test as expectation
        coef(a) = ones(length(a))
        data = FunctionalDiscreteMeasureData(par2, coef, 0, WeightedSample,
                                             is_expect = true)
        @test InfiniteOpt.analytic_expansion(inf3, data, m) == inf3
        # test other
        data = DiscreteMeasureData(par1, [1, 0.5], [1, 1.5])
        @test InfiniteOpt.analytic_expansion(x, data, m) == 1.5 * x
    end
    # test with multi dimensional data
    @testset "Multi Discrete Data" begin
        # test with bounds
        coef(a) = ones(length(a))
        data = FunctionalDiscreteMeasureData(pars1, coef, 0, All,
                    lower_bounds = [1, 1], upper_bounds = [1.5, 2])
        @test InfiniteOpt.analytic_expansion(x, data, m) == 0.5x
        # test as expectation
        data = DiscreteMeasureData(pars1, [1], [[1, 1]], is_expect = true)
        @test InfiniteOpt.analytic_expansion(inf3, data, m) == inf3
        # test other
        coef(a) = ones(size(a, 2))
        data = FunctionalDiscreteMeasureData(pars1, coef, 2, All)
        @test InfiniteOpt.analytic_expansion(x, data, m) == 2x
    end
    # test with fallback
    @testset "Fallback" begin
        data = TestData(par1, 1, 2)
        @test_throws ErrorException InfiniteOpt.analytic_expansion(x, data, m)
    end
end

# Test expand_measures
@testset "expand_measures" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 1 <= par1 <= 2)
    @infinite_parameter(m, 1 <= par2 <= 2)
    @infinite_parameter(m, 1 <= pars1[1:2] <= 2)
    @infinite_variable(m, inf1(par1) >= 1)
    @infinite_variable(m, inf2(par1, par2))
    @infinite_variable(m, inf3(par2))
    @infinite_variable(m, inf4(pars1))
    @hold_variable(m, x)
    # prepare measures
    data1 = DiscreteMeasureData(par1, [0.5, 0.5], [1, 2], is_expect = true)
    data2 = DiscreteMeasureData(pars1, [1, 1], [[1, 1], [2, 2]])
    meas1 = measure(inf1 + 3x - inf2 + inf3 - 2, data1)
    meas2 = measure(2 * inf4 * x - pars1[2] + inf2, data2)
    # test expand_measures (measure)
    @testset "MeasureRef" begin
        # test the first measure
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        expected = 0.5 * (pts[1] + pts[2] + 6x - rv1 - rv2 + 2inf3 - 4)
        @test InfiniteOpt.expand_measures(meas1, m) == expected
        # test the second measure
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 2 * pts[1] * x + 2 * pts[2] * x - 3 + 2inf2
        @test InfiniteOpt.expand_measures(meas2, m) == expected
        # test analytic expansion
        meas3 = measure(x, data1)
        @test InfiniteOpt.expand_measures(meas3, m) == x
    end
    # test expand_measures (GeneralVariableRef)
    @testset "GeneralVariableRef" begin
        @test InfiniteOpt.expand_measures(x, m) == x
        @test InfiniteOpt.expand_measures(par1, m) == par1
        @test InfiniteOpt.expand_measures(inf1, m) == inf1
    end
    # test expand_measures (Finite Expression)
    @testset "Expression (Finite)" begin
        expr = 2x + 2
        @test InfiniteOpt.expand_measures(expr, m) == expr
        expr = x^2 + 2x + 2
        @test InfiniteOpt.expand_measures(expr, m) == expr
    end
    # test expand_measures (GenericAffExpr)
    @testset "AffExpr (General)" begin
        # test returning AffExpr
        expr = 2inf4 + x + 2meas1
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        expected = 2inf4 + 7x + pts[1] + pts[2] - rv1 - rv2 + 2inf3 - 4
        @test InfiniteOpt.expand_measures(expr, m) == expected
        # test returning QuadExpr
        expr = x + 3par1 + meas2
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = x + 3par1 + 2 * pts[1] * x + 2 * pts[2] * x - 3 + 2inf2
        @test InfiniteOpt.expand_measures(expr, m) == expected
    end
    # test expand_measures (GenericQuadExpr)
    @testset "QuadExpr (General)" begin
        # prepare first measure expansion
        expr = 2 * meas1 * x - x + 1
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        m1 = 0.5 * (pts[1] + pts[2] + 6x - rv1 - rv2 + 2inf3 - 4)
        # test with this thorough expression
        @test InfiniteOpt.expand_measures(expr, m) == 2 * m1 * x - x + 1
    end
    # test expand_measures (Fallback)
    @testset "Fallback" begin
        @test_throws ErrorException InfiniteOpt.expand_measures(1, m)
    end
end

# Test user expansion methods
@testset "User Methods" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 1 <= par1 <= 2)
    @infinite_parameter(m, 1 <= par2 <= 2)
    @infinite_parameter(m, 1 <= pars1[1:2] <= 2)
    @infinite_variable(m, inf1(par1) >= 1)
    @infinite_variable(m, inf2(par1, par2) >= 0)
    @infinite_variable(m, inf3(par2))
    @infinite_variable(m, inf4(pars1))
    @hold_variable(m, x >= 0)
    # prepare measures
    data1 = DiscreteMeasureData(par1, [0.5, 0.5], [1, 2], lower_bound = 1, upper_bound = 2)
    data2 = DiscreteMeasureData(pars1, [1, 1], [[1, 1], [2, 2]])
    data3 = DiscreteMeasureData(par2, [2, 2], [1, 2])
    meas1 = measure(inf1 + 3x - inf2 + inf3 - 2, data1)
    meas2 = measure(2 * inf4 * x - pars1[2] + inf2, data2)
    # test expand
    @testset "expand" begin
        # test the first measure
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        idx = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 1
        rv1 = GeneralVariableRef(m, idx, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, ReducedVariableIndex)
        expected = 0.5 * (pts[1] + pts[2] + 6x - rv1 - rv2 + 2inf3 - 4)
        @test expand(meas1) == expected
        @test parameter_values(pts[2]) == (2.,)
        @test eval_supports(rv1) == Dict{Int, Float64}(1 => 1)
        # test the second measure
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        expected = 2 * pts[1] * x + 2 * pts[2] * x - 3 + 2inf2
        @test expand(meas2) == expected
        @test parameter_values(pts[2]) == (Float64[2, 2],)
        # test analytic
        meas = measure(x, data1)
        @test is_analytic(meas)
        @test expand(meas) == 1*x
        # test integral with only 1 support
        delete(m, meas1)
        delete(m, meas)
        delete_supports(par1)
        add_supports(par1, 1)
        @test_throws ErrorException expand(integral(inf1, par1))
    end

    # test expand_all_measures!
    @testset "expand_all_measures!" begin
        # prepare the model for testing
        @hold_variable(m, y, parameter_bounds = (par2 == 1))
        set_objective_function(m, x + measure(inf1 * par1 + 3, data1))
        @constraint(m, c1, inf1 + x >= 42.)
        @constraint(m, c2, 2x - measure(measure(inf1 * x + par1 + inf2, data1),
                                        data3) == 0.)
        @BDconstraint(m, c3(par2 in [1, 1.5]), measure(inf2, data1) + inf1 >= 0.)
        @constraint(m, c4, measure(inf4 + y, data2) <= 3.)
        # prepare comparison expressions
        idx = length(InfiniteOpt._data_dictionary(m, PointVariable)) + 1
        pts = [GeneralVariableRef(m, idx, PointVariableIndex),
               GeneralVariableRef(m, idx + 1, PointVariableIndex)]
        obj = x + 0.5pts[1] + pts[2] + 3
        pts = [GeneralVariableRef(m, idx + 2, PointVariableIndex),
               GeneralVariableRef(m, idx + 3, PointVariableIndex),
               GeneralVariableRef(m, idx + 4, PointVariableIndex),
               GeneralVariableRef(m, idx + 5, PointVariableIndex),
               GeneralVariableRef(m, idx + 6, PointVariableIndex),
               GeneralVariableRef(m, idx + 7, PointVariableIndex)]
        c2_expected = 2x - (2 * pts[1] * x + 2 * pts[2] * x + pts[3] + pts[4] +
                      pts[5] + pts[6])
        idx2 = InfiniteOpt._data_dictionary(m, ReducedVariable).last_index + 3
        rv1 = GeneralVariableRef(m, idx2, ReducedVariableIndex)
        rv2 = GeneralVariableRef(m, idx2 + 1, ReducedVariableIndex)
        c3_expected = 0.5rv1 + 0.5rv2 + inf1
        pts = [GeneralVariableRef(m, idx + 8, PointVariableIndex),
               GeneralVariableRef(m, idx + 9, PointVariableIndex)]
        c4_expected = pts[1] + pts[2] + 2y
        # test the expansion
        @test isa(expand_all_measures!(m), Nothing)
        @test objective_function(m) == obj
        @test is_valid(m, c2)
        @test is_valid(m, c3)
        @test is_valid(m, c4)
        @test name(c1) == "c1"
        @test name(c2) == "c2"
        @test name(c3) == "c3"
        @test name(c4) == "c4"
        @test constraint_object(c1).func == inf1 + x
        @test constraint_object(c2).func == c2_expected
        @test constraint_object(c3).func == c3_expected
        @test constraint_object(c4).func == c4_expected
        @test constraint_object(c2).func isa JuMP.GenericQuadExpr{Float64, GeneralVariableRef}
        @test constraint_object(c3).func isa JuMP.GenericAffExpr{Float64, GeneralVariableRef}
        @test constraint_object(c4).func isa JuMP.GenericAffExpr{Float64, GeneralVariableRef}
        @test constraint_object(c1).set == MOI.GreaterThan(42.)
        @test constraint_object(c2).set == MOI.EqualTo(6.)
        @test constraint_object(c3).set == MOI.GreaterThan(0.)
        @test constraint_object(c4).set == MOI.LessThan(3.)
        @test isa(constraint_object(c1), ScalarConstraint)
        @test isa(constraint_object(c2), ScalarConstraint)
        @test isa(constraint_object(c3), BoundedScalarConstraint)
        @test isa(constraint_object(c4), BoundedScalarConstraint)
        @test constraint_object(c3).bounds.intervals == Dict(par2 => IntervalSet(1, 1.5))
        @test constraint_object(c4).bounds.intervals == Dict(par2 => IntervalSet(1, 1))
    end
end


#__precompile__(false)

module AlgamesRoundabout

greet() = print("Hello World!")

using Algames
using StaticArrays
using LinearAlgebra
using RobotDynamics
using Plots

# Dynamics
export
    VehiclePath,
    RoundaboutGame,
    dynamics
include("dynamics.jl")

# Game Problem Initialization
export
    setup_problem,
    Options
include("initialization.jl")

# Solver functions
export
    action,
    newton_solve!,
    controls,
    state
include("solve.jl")

# Plotting
export
    plot_trajectory!,
    trajectory
include("plot.jl")

end


println("HELLO from ", ENV["SERVER_ID"])

server_id = parse(Integer, ENV["SERVER_ID"])

using Logging

logger = SimpleLogger(stdout, Logging.Debug)
global_logger(logger)

using Pkg
Pkg.status()
Pkg.activate(".")
Pkg.instantiate()

using Pidoh
using LightGraphs, SimpleWeightedGraphs
using SparseArrays

experiment = loadexperiment("./data.jld2")
run(experiment, server_id)


############################################################################################
"""
$(TYPEDEF)

Default arguments for PMCMC constructor.

# Fields
$(TYPEDFIELDS)
"""
struct ParticleGibbs{P<:ParticleFilter,M<:MCMC} <: PMCMCKernel
    "Particle Filter kernel to estimate latent trajectory."
    pf::P
    "MCMC kernel to sample continuous model parameter."
    mcmc::M
    function ParticleGibbs(pf::P, mcmc::M) where {P<:ParticleFilter,M<:MCMC}
        @argcheck !isa(pf.tune.referencing, Marginal) "Cannot use Marginal Filter in Conditional setting, use Ancestral or Conditional referencing"
        return new{P,M}(pf, mcmc)
    end
end
function ParticleGibbs(
    filter::F, mcmc::M; kwargs...
) where {F<:ParticleFilterConstructor,M<:MCMCConstructor}
    return PMCMC(ParticleGibbs, filter, mcmc; kwargs...)
end

############################################################################################
"""
$(SIGNATURES)
Propose new parameter with mcmc psampler. If update=true, objective function will be updated with input model and data.

# Examples
```julia
```

"""
function propose!(
    _rng::Random.AbstractRNG,
    pmcmc::ParticleGibbs,
    model::ModelWrapper,
    data::D,
    temperature::F = model.info.reconstruct.default.output(1.0),
    update::U=BaytesCore.UpdateTrue(),
) where {D,F<:AbstractFloat, U<:BaytesCore.UpdateBool}
    ## Get trajectory via PF - always update data.latent in model
    _, pf_diagnostics = propose!(_rng, pmcmc.pf, model, data, temperature, update)
    ## Propose new θₜ - if accepted, model is updated accordingly
    _, mcmc_diagnostics = propose!(_rng, pmcmc.mcmc, model, data, temperature, update)
    ## Assign base diagnostics - ℓobjective and predictions are taken from particle filter
    diagnostics = BaytesCore.BaseDiagnostics(
        pf_diagnostics.base.ℓobjective, pf_diagnostics.base.temperature, pf_diagnostics.base.prediction,
        mcmc_diagnostics.base.iter
    )
    ## Return pmcmc output
    return model.val, PMCMCDiagnostics(diagnostics, pf_diagnostics, mcmc_diagnostics)
end

############################################################################################
#export
export ParticleGibbs, propose!


module MPFR

export
    BigFloat,
    get_bigfloat_precision,
    set_bigfloat_precision,
    with_bigfloat_precision

import
    Base: (*), +, -, /, <, <=, ==, >, >=, ^, besselj, besselj0, besselj1, bessely,
        bessely0, bessely1, ceil, convert, copysign, deg2rad,
        exp, exp2, exponent, factorial, floor, hypot, isinteger, iround,
        isfinite, isinf, isnan, ldexp, log, log2, log10, max, min, mod, modf,
        nextfloat, prevfloat, promote_rule, rad2deg, rem, round, show,
        showcompact, sum, sqrt, string, print, trunc, precision, exp10, expm1,
        gamma, lgamma, digamma, erf, erfc, zeta, log1p, airyai, iceil, ifloor,
        itrunc, eps, signbit, sin, cos, tan, sec, csc, cot, acos, asin, atan,
        cosh, sinh, tanh, sech, csch, coth, acosh, asinh, atanh, atan2,
        serialize, deserialize, inf, nan, cbrt, typemax, typemin,
        realmin, realmax, get_rounding, set_rounding, maxintfloat, widen

import Base.GMP: ClongMax, CulongMax

import Base.Math.lgamma_r

const ROUNDING_MODE = [0]
const DEFAULT_PRECISION = [256]

# Basic type and initialization definitions

type BigFloat <: FloatingPoint
    prec::Clong
    sign::Cint
    exp::Clong
    d::Ptr{Culong}
    function BigFloat()
        N = get_bigfloat_precision()
        z = new(zero(Clong), zero(Cint), zero(Clong), C_NULL)
        ccall((:mpfr_init2,:libmpfr), Void, (Ptr{BigFloat}, Clong), &z, N)
        finalizer(z, Base.GMP._mpfr_clear_func)
        return z
    end
    # Not recommended for general use
    function BigFloat(prec::Clong, sign::Cint, exp::Clong, d::Ptr{Void})
        new(prec, sign, exp, d)
    end
end

widen(::Type{Float64}) = BigFloat
widen(::Type{BigFloat}) = BigFloat

BigFloat(x::BigFloat) = x

for (fJ, fC) in ((:si,:Clong), (:ui,:Culong), (:d,:Float64))
    @eval begin
        function BigFloat(x::($fC))
            z = BigFloat()
            ccall(($(string(:mpfr_set_,fJ)), :libmpfr), Int32, (Ptr{BigFloat}, ($fC), Int32), &z, x, ROUNDING_MODE[end])
            return z
        end
    end
end

function BigFloat(x::BigInt)
    z = BigFloat()
    ccall((:mpfr_set_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigInt}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function BigFloat(x::String, base::Int)
    z = BigFloat()
    err = ccall((:mpfr_set_str, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{Uint8}, Int32, Int32), &z, x, base, ROUNDING_MODE[end])
    if err != 0; error("incorrectly formatted number"); end
    return z
end
BigFloat(x::String) = BigFloat(x, 10)

BigFloat(x::Integer) = BigFloat(BigInt(x))

BigFloat(x::Union(Bool,Int8,Int16,Int32)) = BigFloat(convert(Clong,x))
BigFloat(x::Union(Uint8,Uint16,Uint32)) = BigFloat(convert(Culong,x))

BigFloat(x::Union(Float16,Float32)) = BigFloat(float64(x))
BigFloat(x::Rational) = BigFloat(num(x)) / BigFloat(den(x))

convert(::Type{Rational}, x::BigFloat) = convert(Rational{BigInt}, x)
convert{S}(::Type{BigFloat}, x::Rational{S}) = BigFloat(x) # to resolve ambiguity
convert(::Type{BigFloat}, x::Real) = BigFloat(x)
convert(::Type{FloatingPoint}, x::BigInt) = BigFloat(x)

for to in (Int8, Int16, Int32, Int64)
    @eval begin
        function convert(::Type{$to}, x::BigFloat)
            (isinteger(x) && (typemin($to) <= x <= typemax($to))) || throw(InexactError())
            convert($to, ccall((:mpfr_get_si,:libmpfr),
                               Clong, (Ptr{BigFloat}, Int32), &x, 0))
        end
    end
end

for to in (Uint8, Uint16, Uint32, Uint64)
    @eval begin
        function convert(::Type{$to}, x::BigFloat)
            (isinteger(x) && (typemin($to) <= x <= typemax($to))) || throw(InexactError())
            convert($to, ccall((:mpfr_get_ui,:libmpfr),
                               Culong, (Ptr{BigFloat}, Int32), &x, 0))
        end
    end
end

function Base.BigInt(x::BigFloat)
    !isinteger(x) && throw(InexactError())
    return itrunc(x)
end
convert(::Type{Float64}, x::BigFloat) =
    ccall((:mpfr_get_d,:libmpfr), Float64, (Ptr{BigFloat},Int32), &x, ROUNDING_MODE[end])
convert(::Type{Float32}, x::BigFloat) =
    ccall((:mpfr_get_flt,:libmpfr), Float32, (Ptr{BigFloat},Int32), &x, ROUNDING_MODE[end])

convert(::Type{Integer}, x::BigFloat) = convert(BigInt, x)

promote_rule{T<:Real}(::Type{BigFloat}, ::Type{T}) = BigFloat
promote_rule{T<:FloatingPoint}(::Type{BigInt},::Type{T}) = BigFloat
promote_rule{T<:FloatingPoint}(::Type{BigFloat},::Type{T}) = BigFloat

rationalize(x::BigFloat; tol::Real=eps(x)) = rationalize(BigInt, x, tol=tol)

# serialization

function serialize(s, n::BigFloat)
    Base.serialize_type(s, BigFloat)
    serialize(s, string(n))
end

deserialize(s, ::Type{BigFloat}) = BigFloat(deserialize(s))

# Basic arithmetic without promotion
# Unsigned addition
function +(x::BigFloat, c::Culong)
    z = BigFloat()
    ccall((:mpfr_add_ui, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Culong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
+(c::Culong, x::BigFloat) = x + c
+(c::CulongMax, x::BigFloat) = x + convert(Culong, c)
+(x::BigFloat, c::CulongMax) = x + convert(Culong, c)

# Signed addition
function +(x::BigFloat, c::Clong)
    z = BigFloat()
    ccall((:mpfr_add_si, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Clong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
+(c::Clong, x::BigFloat) = x + c
+(x::BigFloat, c::ClongMax) = x + convert(Clong, c)
+(c::ClongMax, x::BigFloat) = x + convert(Clong, c)

# Float64 addition
function +(x::BigFloat, c::Float64)
    z = BigFloat()
    ccall((:mpfr_add_d, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Float64, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
+(c::Float64, x::BigFloat) = x + c
+(c::Float32, x::BigFloat) = x + convert(Float64, c)
+(x::BigFloat, c::Float32) = x + convert(Float64, c)

# BigInt addition
function +(x::BigFloat, c::BigInt)
    z = BigFloat()
    ccall((:mpfr_add_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigInt}, Int32), &z, &x, &c, ROUNDING_MODE[end])
    return z
end
+(c::BigInt, x::BigFloat) = x + c

# Unsigned subtraction
function -(x::BigFloat, c::Culong)
    z = BigFloat()
    ccall((:mpfr_sub_ui, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Culong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
function -(c::Culong, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_ui_sub, :libmpfr), Int32, (Ptr{BigFloat}, Culong, Ptr{BigFloat}, Int32), &z, c, &x, ROUNDING_MODE[end])
    return z
end
-(x::BigFloat, c::CulongMax) = -(x, convert(Culong, c))
-(c::CulongMax, x::BigFloat) = -(convert(Culong, c), x)

# Signed subtraction
function -(x::BigFloat, c::Clong)
    z = BigFloat()
    ccall((:mpfr_sub_si, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Clong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
function -(c::Clong, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_si_sub, :libmpfr), Int32, (Ptr{BigFloat}, Clong, Ptr{BigFloat}, Int32), &z, c, &x, ROUNDING_MODE[end])
    return z
end
-(x::BigFloat, c::ClongMax) = -(x, convert(Clong, c))
-(c::ClongMax, x::BigFloat) = -(convert(Clong, c), x)

# Float64 subtraction
function -(x::BigFloat, c::Float64)
    z = BigFloat()
    ccall((:mpfr_sub_d, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Float64, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
function -(c::Float64, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_d_sub, :libmpfr), Int32, (Ptr{BigFloat}, Float64, Ptr{BigFloat}, Int32), &z, c, &x, ROUNDING_MODE[end])
    return z
end
-(x::BigFloat, c::Float32) = -(x, convert(Float64, c))
-(c::Float32, x::BigFloat) = -(convert(Float64, c), x)

# BigInt subtraction
function -(x::BigFloat, c::BigInt)
    z = BigFloat()
    ccall((:mpfr_sub_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigInt}, Int32), &z, &x, &c, ROUNDING_MODE[end])
    return z
end
function -(c::BigInt, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_z_sub, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigInt}, Ptr{BigFloat}, Int32), &z, &c, &x, ROUNDING_MODE[end])
    return z
end

# Unsigned multiplication
function *(x::BigFloat, c::Culong)
    z = BigFloat()
    ccall((:mpfr_mul_ui, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Culong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
*(c::Culong, x::BigFloat) = x * c
*(c::CulongMax, x::BigFloat) = x * convert(Culong, c)
*(x::BigFloat, c::CulongMax) = x * convert(Culong, c)

# Signed multiplication
function *(x::BigFloat, c::Clong)
    z = BigFloat()
    ccall((:mpfr_mul_si, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Clong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
*(c::Clong, x::BigFloat) = x * c
*(x::BigFloat, c::ClongMax) = x * convert(Clong, c)
*(c::ClongMax, x::BigFloat) = x * convert(Clong, c)

# Float64 multiplication
function *(x::BigFloat, c::Float64)
    z = BigFloat()
    ccall((:mpfr_mul_d, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Float64, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
*(c::Float64, x::BigFloat) = x * c
*(c::Float32, x::BigFloat) = x * convert(Float64, c)
*(x::BigFloat, c::Float32) = x * convert(Float64, c)

# BigInt multiplication
function *(x::BigFloat, c::BigInt)
    z = BigFloat()
    ccall((:mpfr_mul_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigInt}, Int32), &z, &x, &c, ROUNDING_MODE[end])
    return z
end
*(c::BigInt, x::BigFloat) = x * c

# Unsigned division
function /(x::BigFloat, c::Culong)
    z = BigFloat()
    ccall((:mpfr_div_ui, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Culong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
function /(c::Culong, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_ui_div, :libmpfr), Int32, (Ptr{BigFloat}, Culong, Ptr{BigFloat}, Int32), &z, c, &x, ROUNDING_MODE[end])
    return z
end
/(x::BigFloat, c::CulongMax) = /(x, convert(Culong, c))
/(c::CulongMax, x::BigFloat) = /(convert(Culong, c), x)

# Signed division
function /(x::BigFloat, c::Clong)
    z = BigFloat()
    ccall((:mpfr_div_si, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Clong, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
function /(c::Clong, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_si_div, :libmpfr), Int32, (Ptr{BigFloat}, Clong, Ptr{BigFloat}, Int32), &z, c, &x, ROUNDING_MODE[end])
    return z
end
/(x::BigFloat, c::ClongMax) = /(x, convert(Clong, c))
/(c::ClongMax, x::BigFloat) = /(convert(Clong, c), x)

# Float64 division
function /(x::BigFloat, c::Float64)
    z = BigFloat()
    ccall((:mpfr_div_d, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Float64, Int32), &z, &x, c, ROUNDING_MODE[end])
    return z
end
function /(c::Float64, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_d_div, :libmpfr), Int32, (Ptr{BigFloat}, Float64, Ptr{BigFloat}, Int32), &z, c, &x, ROUNDING_MODE[end])
    return z
end
/(x::BigFloat, c::Float32) = /(x, convert(Float64, c))
/(c::Float32, x::BigFloat) = /(convert(Float64, c), x)

# BigInt division
function /(x::BigFloat, c::BigInt)
    z = BigFloat()
    ccall((:mpfr_div_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigInt}, Int32), &z, &x, &c, ROUNDING_MODE[end])
    return z
end

# Basic operations
for (fJ, fC) in ((:+,:add), (:-,:sub), (:*,:mul), (:/,:div), (:^, :pow))
    @eval begin
        function ($fJ)(x::BigFloat, y::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)),:libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, &y, ROUNDING_MODE[end])
            return z
        end
    end
end

# More efficient commutative operations
for (fJ, fC, fI) in ((:+, :add, 0), (:*, :mul, 1))
    @eval begin
        function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &a, &b, ROUNDING_MODE[end])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &z, &c, ROUNDING_MODE[end])
            return z
        end
        function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &a, &b, ROUNDING_MODE[end])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &z, &c, ROUNDING_MODE[end])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &z, &d, ROUNDING_MODE[end])
            return z
        end
        function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat, e::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &a, &b, ROUNDING_MODE[end])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &z, &c, ROUNDING_MODE[end])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &z, &d, ROUNDING_MODE[end])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &z, &e, ROUNDING_MODE[end])
            return z
        end
    end
end

function -(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_neg, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function sqrt(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_sqrt, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    if isnan(z)
        throw(DomainError())
    end
    return z
end

sqrt(x::BigInt) = sqrt(BigFloat(x))

rad2deg(z::BigFloat) = 180/big(pi)*z
deg2rad(z::BigFloat) = big(pi)/180*z

function ^(x::BigFloat, y::CulongMax)
    z = BigFloat()
    ccall((:mpfr_pow_ui, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Culong, Int32), &z, &x, y, ROUNDING_MODE[end])
    return z
end

function ^(x::BigFloat, y::ClongMax)
    z = BigFloat()
    ccall((:mpfr_pow_si, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Clong, Int32), &z, &x, y, ROUNDING_MODE[end])
    return z
end

function ^(x::BigFloat, y::BigInt)
    z = BigFloat()
    ccall((:mpfr_pow_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigInt}, Int32), &z, &x, &y, ROUNDING_MODE[end])
    return z
end

for f in (:exp, :exp2, :exp10, :expm1, :digamma, :erf, :erfc, :zeta,
          :cosh,:sinh,:tanh,:sech,:csch,:coth, :cbrt)
    @eval function $f(x::BigFloat)
        z = BigFloat()
        ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
        return z
    end
end

function airyai(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_ai, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function ldexp(x::BigFloat, n::Clong)
    z = BigFloat()
    ccall((:mpfr_mul_2si, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Clong, Int32), &z, &x, n, ROUNDING_MODE[end])
    return z
end
function ldexp(x::BigFloat, n::Culong)
    z = BigFloat()
    ccall((:mpfr_mul_2ui, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Culong, Int32), &z, &x, n, ROUNDING_MODE[end])
    return z
end
ldexp(x::BigFloat, n::ClongMax) = ldexp(x, convert(Clong, n))
ldexp(x::BigFloat, n::CulongMax) = ldexp(x, convert(Culong, n))
ldexp(x::BigFloat, n::Integer) = x*exp2(BigFloat(n))

function besselj0(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_j0, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function besselj1(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_j1, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function besselj(n::Integer, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_jn, :libmpfr), Int32, (Ptr{BigFloat}, Clong, Ptr{BigFloat}, Int32), &z, n, &x, ROUNDING_MODE[end])
    return z
end

function bessely0(x::BigFloat)
    if x < 0
        throw(DomainError())
    end
    z = BigFloat()
    ccall((:mpfr_y0, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function bessely1(x::BigFloat)
    if x < 0
        throw(DomainError())
    end
    z = BigFloat()
    ccall((:mpfr_y1, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function bessely(n::Integer, x::BigFloat)
    if x < 0
        throw(DomainError())
    end
    z = BigFloat()
    ccall((:mpfr_yn, :libmpfr), Int32, (Ptr{BigFloat}, Clong, Ptr{BigFloat}, Int32), &z, n, &x, ROUNDING_MODE[end])
    return z
end

function factorial(x::BigFloat)
    if x < 0 || !isinteger(x)
        throw(DomainError())
    end
    ui = convert(Culong, x)
    z = BigFloat()
    ccall((:mpfr_fac_ui, :libmpfr), Int32, (Ptr{BigFloat}, Culong, Int32), &z, ui, ROUNDING_MODE[end])
    return z
end

function hypot(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_hypot, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, &y, ROUNDING_MODE[end])
    return z
end

for f in (:log, :log2, :log10)
    @eval function $f(x::BigFloat)
        if x < 0
            throw(DomainError())
        end
        z = BigFloat()
        ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
        return z
    end
end

function log1p(x::BigFloat)
    if x < -1
        throw(DomainError())
    end
    z = BigFloat()
    ccall((:mpfr_log1p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
    return z
end

function max(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_max, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, &y, ROUNDING_MODE[end])
    return z
end

function min(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_min, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, &y, ROUNDING_MODE[end])
    return z
end

function modf(x::BigFloat)
    if isinf(x)
        return (BigFloat(NaN), x)
    end
    zint = BigFloat()
    zfloat = BigFloat()
    ccall((:mpfr_modf, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &zint, &zfloat, &x, ROUNDING_MODE[end])
    return (zfloat, zint)
end

function rem(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_fmod, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, &y, ROUNDING_MODE[end])
    return z
end

function sum(arr::AbstractArray{BigFloat})
    z = BigFloat(0)
    for i in arr
        ccall((:mpfr_add, :libmpfr), Int32,
            (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Cint),
            &z, &z, &i, 0)
    end
    return z
end

# Functions for which NaN results are converted to DomainError, following Base
for f in (:sin,:cos,:tan,:sec,:csc,
          :acos,:asin,:atan,:acosh,:asinh,:atanh, :gamma)
    @eval begin
        function ($f)(x::BigFloat)
            if isnan(x)
                return x
            end
            z = BigFloat()
            ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[end])
            if isnan(z)
                throw(DomainError())
            end
            return z
        end
    end
end

# log of absolute value of gamma function
const lgamma_signp = Array(Cint, 1)
function lgamma(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_lgamma,:libmpfr), Cint, (Ptr{BigFloat}, Ptr{Cint}, Ptr{BigFloat}, Int32), &z, lgamma_signp, &x, ROUNDING_MODE[end])
    return z
end

lgamma_r(x::BigFloat) = (lgamma(x), lgamma_signp[1])

function atan2(y::BigFloat, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_atan2, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &y, &x, ROUNDING_MODE[end])
    return z
end

# Utility functions
==(x::BigFloat, y::BigFloat) = ccall((:mpfr_equal_p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}), &x, &y) != 0
<=(x::BigFloat, y::BigFloat) = ccall((:mpfr_lessequal_p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}), &x, &y) != 0
>=(x::BigFloat, y::BigFloat) = ccall((:mpfr_greaterequal_p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}), &x, &y) != 0
<(x::BigFloat, y::BigFloat) = ccall((:mpfr_less_p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}), &x, &y) != 0
>(x::BigFloat, y::BigFloat) = ccall((:mpfr_greater_p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}), &x, &y) != 0

function ==(i::BigInt, f::BigFloat)
    !isnan(f) && ccall((:mpfr_cmp_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigInt}), &f, &i) == 0
end

==(f::BigFloat, i::BigInt) = i == f

function <(i::BigInt, f::BigFloat)
    # note: mpfr_cmp_z returns 0 if isnan(f)
    ccall((:mpfr_cmp_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigInt}), &f, &i) > 0
end

function <(f::BigFloat, i::BigInt)
    # note: mpfr_cmp_z returns 0 if isnan(f)
    ccall((:mpfr_cmp_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigInt}), &f, &i) < 0
end

<=(i::BigInt, f::BigFloat) = !isnan(f) && !(f < i)
<=(f::BigFloat, i::BigInt) = !isnan(f) && !(i < f)

signbit(x::BigFloat) = ccall((:mpfr_signbit, :libmpfr), Int32, (Ptr{BigFloat},), &x) != 0

function precision(x::BigFloat)
    return ccall((:mpfr_get_prec, :libmpfr), Clong, (Ptr{BigFloat},), &x)
end

get_bigfloat_precision() = DEFAULT_PRECISION[end]
function set_bigfloat_precision(x::Int)
    if x < 2
        throw(DomainError())
    end
    DEFAULT_PRECISION[end] = x
end

maxintfloat(x::BigFloat) = BigFloat(2)^precision(x)
maxintfloat(::Type{BigFloat}) = BigFloat(2)^get_bigfloat_precision()

function to_mpfr(r::RoundingMode)
    c = r.code
    if !(0 <= c <= 4)
        error("invalid BigFloat rounding mode")
    end
    c
end

function from_mpfr(c::Integer)
    if !(0 <= c <= 4)
        error("invalid MPFR rounding mode code")
    end
    RoundingMode(c)
end

get_rounding(::Type{BigFloat}) = from_mpfr(ROUNDING_MODE[end])
set_rounding(::Type{BigFloat},r::RoundingMode) = ROUNDING_MODE[end] = to_mpfr(r)

function copysign(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_copysign, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, &y, ROUNDING_MODE[end])
    return z
end

function exponent(x::BigFloat)
    if x == 0 || !isfinite(x)
        throw(DomainError())
    end
    # The '- 1' is to make it work as Base.exponent
    return ccall((:mpfr_get_exp, :libmpfr), Clong, (Ptr{BigFloat},), &x) - 1
end

function isinteger(x::BigFloat)
    return ccall((:mpfr_integer_p, :libmpfr), Int32, (Ptr{BigFloat},), &x) != 0
end

for f in (:ceil, :floor, :trunc, :round)
    @eval begin
        function ($f)(x::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}), &z, &x)
            return z
        end
    end
end

function itrunc(x::BigFloat)
    z = BigInt()
    ccall((:mpfr_get_z, :libmpfr), Int32, (Ptr{BigInt}, Ptr{BigFloat}, Int32), &z, &x, 0)
    return z
end

iround(x::BigFloat) = itrunc(round(x))

function isinf(x::BigFloat)
    return ccall((:mpfr_inf_p, :libmpfr), Int32, (Ptr{BigFloat},), &x) != 0
end

function isnan(x::BigFloat)
    return ccall((:mpfr_nan_p, :libmpfr), Int32, (Ptr{BigFloat},), &x) != 0
end

isfinite(x::BigFloat) = !isinf(x) && !isnan(x)

@eval inf(::Type{BigFloat}) = $(BigFloat(Inf))
@eval nan(::Type{BigFloat}) = $(BigFloat(NaN))

typemax(::Type{BigFloat}) = inf(BigFloat)
@eval typemin(::Type{BigFloat}) = $(BigFloat(-Inf))

function nextfloat(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_set, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32),
          &z, &x, ROUNDING_MODE[end])
    ccall((:mpfr_nextabove, :libmpfr), Int32, (Ptr{BigFloat},), &z) != 0
    return z
end

function prevfloat(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_set, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32),
          &z, &x, ROUNDING_MODE[end])
    ccall((:mpfr_nextbelow, :libmpfr), Int32, (Ptr{BigFloat},), &z) != 0
    return z
end

eps(::Type{BigFloat}) = nextfloat(BigFloat(1)) - BigFloat(1)

realmin(::Type{BigFloat}) = nextfloat(zero(BigFloat))
realmax(::Type{BigFloat}) = prevfloat(inf(BigFloat))

function with_bigfloat_precision(f::Function, precision::Integer)
    old_precision = get_bigfloat_precision()
    set_bigfloat_precision(precision)
    try
        return f()
    finally
        set_bigfloat_precision(old_precision)
    end
end

function string(x::BigFloat)
    lng = 128
    for i = 1:2
        z = Array(Uint8, lng + 1)
        lng = ccall((:mpfr_snprintf,:libmpfr), Int32, (Ptr{Uint8}, Culong, Ptr{Uint8}, Ptr{BigFloat}...), z, lng + 1, "%.Re", &x)
        if lng < 128 || i == 2
            return bytestring(z[1:lng])
        end
    end
end

print(io::IO, b::BigFloat) = print(io, string(b))
show(io::IO, b::BigFloat) = print(io, string(b), " with $(precision(b)) bits of precision")
showcompact(io::IO, b::BigFloat) = print(io, string(b))

end #module


module DiffEqFlux

using Adapt, Base.Iterators, ConsoleProgressMonitor, DataInterpolations,
    DiffEqBase, DiffEqSensitivity, DiffResults, Distributions, DistributionsAD,
    ForwardDiff, Optimization, OptimizationPolyalgorithms, LinearAlgebra,
    Logging, LoggingExtras, Printf, ProgressLogging, Random, RecursiveArrayTools,
    Reexport, SciMLBase, StaticArrays, TerminalLoggers, Zygote, ZygoteRules

@reexport using DiffEqSensitivity
@reexport using Zygote

# deprecate

import OptimizationFlux
import NNlib
import Lux
using Requires
using Cassette
@reexport using Flux
@reexport using OptimizationOptimJL

gpu_or_cpu(x) = Array

# ForwardDiff integration

ZygoteRules.@adjoint function ForwardDiff.Dual{T}(x, ẋ::Tuple) where T
  @assert length(ẋ) == 1
  ForwardDiff.Dual{T}(x, ẋ), ḋ -> (ḋ.partials[1], (ḋ.value,))
end

ZygoteRules.@adjoint ZygoteRules.literal_getproperty(d::ForwardDiff.Dual{T}, ::Val{:partials}) where T =
  d.partials, ṗ -> (ForwardDiff.Dual{T}(ṗ[1], 0),)

ZygoteRules.@adjoint ZygoteRules.literal_getproperty(d::ForwardDiff.Dual{T}, ::Val{:value}) where T =
  d.value, ẋ -> (ForwardDiff.Dual{T}(0, ẋ),)

ZygoteRules.@adjoint ZygoteRules.literal_getproperty(A::Tridiagonal, ::Val{:dl}) = A.dl,y -> Tridiagonal(dl,zeros(length(d)),zeros(length(du)),)
ZygoteRules.@adjoint ZygoteRules.literal_getproperty(A::Tridiagonal, ::Val{:d}) = A.d,y -> Tridiagonal(zeros(length(dl)),d,zeros(length(du)),)
ZygoteRules.@adjoint ZygoteRules.literal_getproperty(A::Tridiagonal, ::Val{:du}) = A.dl,y -> Tridiagonal(zeros(length(dl)),zeros(length(d),du),)
ZygoteRules.@adjoint Tridiagonal(dl, d, du) = Tridiagonal(dl, d, du), p̄ -> (diag(p̄[2:end,1:end-1]),diag(p̄),diag(p̄[1:end-1,2:end]))

include("ffjord.jl")
include("train.jl")
include("fast_layers.jl")
include("neural_de.jl")
include("require.jl")
include("spline_layer.jl")
include("tensor_product_basis.jl")
include("tensor_product_layer.jl")
include("collocation.jl")
include("hnn.jl")
include("multiple_shooting.jl")

Flux.device(::FastLayer) = @warn "device(f::FastLayer) is a no-op: to move FastChain computations to a GPU, apply gpu(x) to the weight vector"
Flux.gpu(::FastLayer) = @warn "device(f::FastLayer) is a no-op: to move FastChain computations to a GPU, apply gpu(x) to the weight vector"
Flux.cpu(::FastLayer) = @warn "device(f::FastLayer) is a no-op: to move FastChain computations to a CPU, apply cpu(x) to the weight vector"

export DeterministicCNF, FFJORD, NeuralODE, NeuralDSDE, NeuralSDE, NeuralCDDE, NeuralDAE, NeuralODEMM, TensorLayer, AugmentedNDELayer, SplineLayer, NeuralHamiltonianDE
export HamiltonianNN
export ChebyshevBasis, SinBasis, CosBasis, FourierBasis, LegendreBasis, PolynomialBasis
export FastDense, StaticDense, FastChain, initial_params
export FFJORDDistribution

export EpanechnikovKernel, UniformKernel, TriangularKernel, QuarticKernel
export TriweightKernel, TricubeKernel, GaussianKernel, CosineKernel
export LogisticKernel, SigmoidKernel, SilvermanKernel
export collocate_data

export multiple_shoot

end


include("friend_sets_struct.jl")
include("enemy_sets_struct.jl")
include("item_use_struct.jl")
include("irt_struct.jl")

mutable struct Settings
    n_items::Int64
    n_fs::Int64
    bank::DataFrames.DataFrame
    irt::IRT
    theta_bounds::Vector{Vector{Float64}}
    forced0::Vector{Vector{Bool}}
    n_groups::Int64
    T::Int64
    Tg::Vector{Int64}
    fs::FriendSets # friend Sets
    es::EnemySets # enemy Sets
    iu::ItemUse
    ol_max::Matrix{Float64}
    to_apply::Vector{Bool} #[iu_max?, iu_min?, ol?]
    Settings(
        n_items,
        n_fs,
        bank,
        irt,
        theta_bounds,
        forced0,
        n_groups,
        T,
        Tg,
        fs,
        es,
        iu,
        ol_max,
        to_apply,
    ) = new(
        n_items,
        n_fs,
        bank,
        irt,
        theta_bounds,
        forced0,
        n_groups,
        T,
        Tg,
        fs,
        es,
        iu,
        ol_max,
        to_apply,
    ) # no pattern mode
    Settings() = new(
        0,
        0,
        DataFrames.DataFrame(),
        IRT(),
        [[-6.0, 6.0]],
        Vector{Vector{Bool}}(undef, 0),
        1,
        1,
        [1],
        FriendSets(),
        EnemySets(),
        ItemUse(),
        zeros(Int64, 0, 0),
        [false, false, false],
    )
end


"""
`Crop <: ImageTransformation`

Description
============

Crops out the area of the specified pixel dimensions starting
at a specified position that denotes the top-left corner
of the crop. A position of `x = 1`, and `y = 1` would mean that
the crop is located in the top-left corner of the original image

Usage
======

    Crop(1, 1, 64, 64)

    Crop(x = 1, y = 1, width = 64, height = 64)

Arguments
==========

- **`x`** : The horizontal offset of the top left corner of the
window that should be cropped out

- **`y`** : The vertical offset of the top left corner of the
window that should be cropped out

- **`width`** : The desired width or the cropped out sub-image
in pixels

- **`height`** : The desired height or the cropped out sub-image
in pixels

Methods
========

- **`transform`** : Applies the transformation to the given Image
or set of images

Author(s)
==========

- Christof Stocker (Github: https://github.com/Evizero)

Examples
========

    using Augmentor
    using TestImages

    # load an example image
    img = testimage("lena")

    # Crop out a 64x32 area starting at x=10, y=20
    img_new = transform(Crop(10, 20, 64, 32), img)

see also
=========

`ImageTransformation`, `transform`
"""
immutable Crop <: ImageTransformation
    x::Int
    y::Int
    width::Int
    height::Int

    function Crop(x::Int, y::Int, width::Int, height::Int)
        x > 0 || throw(ArgumentError("x has to be greater than 0"))
        y > 0 || throw(ArgumentError("y has to be greater than 0"))
        width > 0 || throw(ArgumentError("width has to be greater than 0"))
        height > 0 || throw(ArgumentError("height has to be greater than 0"))
        new(x, y, width, height)
    end
end

Crop(; x::Int = 1, y::Int = 1, width::Int = 64, height::Int = 64) = Crop(x, y, width, height)

Base.showcompact(io::IO, op::Crop) = print(io, "Crop at x=$(op.x) y=$(op.y) a $(op.width)x$(op.height) window.")
multiplier(::Crop) = 1

function transform{T<:AbstractImage}(op::Crop, img::T)
    w = width(img)
    h = height(img)
    op.x <= w || throw(ArgumentError("x of crop window is out of bounds"))
    op.y <= h || throw(ArgumentError("y of crop window is out of bounds"))

    x_end = op.x + op.width - 1
    y_end = op.y + op.height - 1
    x_end <= w || throw(ArgumentError("width of crop window is out of bounds"))
    y_end <= h || throw(ArgumentError("height of crop window is out of bounds"))

    result = crop(img, op.x:x_end, op.y:y_end)
    _log_operation!(result, op)
end


struct DesignMatrix
    "Array of formulas"
    formulas::Any
    "A concatenated designmatric. In case of Mixed Models an array, where the first one is a FeMat, later ones ReMats. "
    Xs::Any
    "Event table with all events"
    events::Any
end

function DesignMatrix()
    return DesignMatrix([], [], [])
end

struct UnfoldMixedModelFitCollection{T<:AbstractFloat} <:
       MixedModels.MixedModelFitCollection{T}
    fits::Vector
    λ::Vector{<:Union{LowerTriangular{T,Matrix{T}},Diagonal{T,Vector{T}}}}
    inds::Vector{Vector{Int}}
    lowerbd::Vector{T}
    fcnames::NamedTuple
end

abstract type UnfoldModel end

#function UnfoldModel()
# todo make generator function with empty DesignMatrix
mutable struct UnfoldLinearModel <: UnfoldModel
    design::Dict
    designmatrix::DesignMatrix
    modelfit::Any
end

UnfoldLinearModel(d::Dict) = UnfoldLinearModel(d, Unfold.DesignMatrix(), [])
UnfoldLinearModel(d::Dict, X::DesignMatrix) = UnfoldLinearModel(d, X, [])

mutable struct UnfoldLinearMixedModel <: UnfoldModel
    design::Dict
    designmatrix::DesignMatrix
    modelfit::Any#::Array{UnfoldMixedModelFitCollection} # optional info on the modelfit
end
UnfoldLinearMixedModel(d::Dict) = UnfoldLinearMixedModel(d, Unfold.DesignMatrix(), [])
UnfoldLinearMixedModel(d::Dict, X::DesignMatrix) = UnfoldLinearMixedModel(d, X, [])

mutable struct UnfoldLinearModelContinuousTime <: UnfoldModel
    design::Dict
    designmatrix::DesignMatrix
    modelfit::Any
end

UnfoldLinearModelContinuousTime(d::Dict) =
    UnfoldLinearModelContinuousTime(d, Unfold.DesignMatrix(), [])
UnfoldLinearModelContinuousTime(d::Dict, X::DesignMatrix) =
    UnfoldLinearModelContinuousTime(d, X, [])

mutable struct UnfoldLinearMixedModelContinuousTime <: UnfoldModel
    design::Dict
    designmatrix::DesignMatrix
    modelfit::Any#::UnfoldMixedModelFitCollection
end

UnfoldLinearMixedModelContinuousTime(d::Dict) =
    UnfoldLinearMixedModelContinuousTime(d, Unfold.DesignMatrix(), [])
UnfoldLinearMixedModelContinuousTime(d::Dict, X::DesignMatrix) =
    UnfoldLinearMixedModelContinuousTime(d, X, [])

abstract type ModelFit end

struct LinearModelFit <: ModelFit
    estimate::Any
    info::Any
    standarderror::Any
end

LinearModelFit(estimate) = LinearModelFit(estimate, [], [])
LinearModelFit(estimate, info) = LinearModelFit(estimate, info, [])

function Base.show(io::IO, obj::UnfoldModel)
    println(io, "Unfold-Type: $(typeof(obj)) \n")
    println(io, "formula: $(obj.design)")
    println(
        io,
        "Useful functions:\n
    design(uf) \t\t(returns Dict of event => (formula,times/basis))  \n
    designmatrix(uf) \t(returns DesignMatrix with events) \n
    modelfit(uf) \t\t(returns modelfit object) \n
    coeftable(uf) \t\t(returns tidy result dataframe) \n",
    )
end


using JuMP; using NCDatasets; using AxisArrays; using Missings; using CalliopeJuMP.Util;

function load_capacity_constraints(model_dict)
    sets = model_dict["sets"]
    constraint_dict = Dict()

    if haskey(sets, "loc_techs_storage_capacity_constraint")
        constraint_dict["storage_capacity_constraint"] = (
            build_constraint(["loc_techs_storage_capacity_constraint"],
            "storage_capacity", model_dict)
        )
    end

    if haskey(sets, "loc_techs_energy_capacity_storage_constraint")
        constraint_dict["energy_capacity_storage_constraint"] = (
            build_constraint(["loc_techs_energy_capacity_storage_constraint"],
            "energy_capacity_storage", model_dict)
        )
    end

    if haskey(sets, "loc_techs_resource_capacity_constraint")
        constraint_dict["resource_capacity_constraint"] = (
            build_constraint(["loc_techs_resource_capacity_constraint"],
            "resource_capacity", model_dict)
        )
    end

    if haskey(sets, "loc_techs_resource_capacity_equals_energy_capacity_constraint")
        constraint_dict["resource_capacity_equals_energy_capacity_constraint"] = (
            build_constraint(["loc_techs_resource_capacity_equals_energy_capacity_constraint"],
            "resource_capacity_equals_energy_capacity", model_dict)
        )
    end

    if haskey(sets, "loc_techs_resource_area_constraint")
        constraint_dict["resource_area_constraint"] = (
            build_constraint(["loc_techs_resource_area_constraint"],
            "resource_area", model_dict)
        )
    end

    if haskey(sets, "loc_techs_resource_area_per_energy_capacity_constraint")
        constraint_dict["resource_area_per_energy_capacity_constraint"] = (
            build_constraint(["loc_techs_resource_area_per_energy_capacity_constraint"],
            "resource_area_per_energy_capacity", model_dict)
        )
    end

    if haskey(sets, "locs_resource_area_capacity_per_loc_constraint")
        constraint_dict["resource_area_capacity_per_loc_constraint"] = (
            build_constraint(["locs_resource_area_capacity_per_loc_constraint"],
            "resource_area_capacity_per_loc", model_dict)
        )
    end

    if haskey(sets, "loc_techs_energy_capacity_constraint")
        constraint_dict["energy_capacity_constraint"] = (
            build_constraint(["loc_techs_energy_capacity_constraint"],
            "energy_capacity", model_dict)
        )
    end

    return constraint_dict
end

function get_capacity_constraint(model_dict, parameter, loc_tech ;
                    _equals=nothing, _max=nothing, _min=nothing, scale=nothing)
    decision_variable = model_dict["variables"][parameter]
    backend_model = model_dict["backend_model"]

    if _equals == nothing
        _equals = get_param(model_dict, string(parameter, "_equals"), [loc_tech])
    end

    if _max == nothing
        _max = get_param(model_dict, string(parameter, "_max"), [loc_tech])
    end

    if _min == nothing
        _min = get_param(model_dict, string(parameter, "_min"), [loc_tech])
    end

    if scale != nothing
        _equals = scale * _equals
        _min = scale * _min
        _max = scale * _max
    end

    if _equals != false && _equals != nothing
        if isinf(_equals)
            throw("Cannot use inf for $parameter/_equals for loc:tech `$loc_tech`")
        end

        return @constraint(backend_model, decision_variable[loc_tech] == _equals)

    else
        if !isinf(_max) && _max != nothing
            setupperbound(decision_variable[loc_tech], _max)
        end

        if _min != 0 && _min != nothing
            setlowerbound(decision_variable[loc_tech], _min)
        end

        return @constraint(backend_model, 0==0)
    end
end

function storage_capacity_constraint_rule(backend_model, set_indices, model_dict)
    """
    Set maximum storage capacity. Supply_plus & storage techs only
    This can be set by either storage_cap (kWh) or by
    energy_cap (charge/discharge capacity) * charge rate.
    If storage_cap.equals and energy_cap.equals are set for the technology, then
    storage_cap * charge rate = energy_cap must hold. Otherwise, take the lowest capacity
    capacity defined by storage_cap.max or energy_cap.max / charge rate.
    """
    loc_tech = set_indices[1]
    storage_cap_equals = get_param(model_dict, "storage_cap_equals", [loc_tech])
    scale = get_param(model_dict, "energy_cap_scale", [loc_tech])
    energy_cap_equals = scale * get_param(model_dict, "energy_cap_equals", [loc_tech])

    energy_cap_max = get_param(model_dict, "energy_cap_max", [loc_tech])
    storage_cap_max = get_param(model_dict, "storage_cap_max", [loc_tech])

    charge_rate = get_param(model_dict, "charge_rate", [loc_tech])

    # FIXME working out storage_cap_max or storage_cap_equals from e_cap_max/_equals
    # should be done in preprocessing, not here.

    if storage_cap_equals > 0
        return get_capacity_constraint(model_dict, "storage_cap",
                                        loc_tech, _equals=storage_cap_equals)
    elseif energy_cap_equals > 0 && charge_rate > 0
        storage_cap_equals = energy_cap_equals * scale / charge_rate
        return get_capacity_constraint(model_dict, "storage_cap",
                                        loc_tech, _equals=storage_cap_equals)
    elseif storage_cap_max > 0
        return get_capacity_constraint(model_dict, "storage_cap",
                                        loc_tech, _max=storage_cap_max)
    elseif energy_cap_max > 0 && charge_rate > 0
        storage_cap_max = energy_cap_max * scale / charge_rate
        return get_capacity_constraint(model_dict, "storage_cap",
                                        loc_tech, _max=storage_cap_max)
    else
        return @constraint(backend_model, 0==0)
    end
end

function energy_capacity_storage_constraint_rule(backend_model, set_indices, model_dict)
    """
    Set an additional energy capacity constraint on storage technologies,
    based on their use of `charge_rate`
    """
    loc_tech = set_indices[1]
    variables = model_dict["variables"]
    energy_cap_scale = get_param(model_dict, "energy_cap_scale", [loc_tech])
    charge_rate = get_param(model_dict, "charge_rate", [loc_tech])

    return @constraint(backend_model,
        variables["energy_cap"][loc_tech] <=
        variables["storage_cap"][loc_tech] * charge_rate * energy_cap_scale
    )
end

function resource_capacity_constraint_rule(backend_model, set_indices, model_dict)
    """
    Add upper and lower bounds for resource_cap
    """
    loc_tech = set_indices[1]
    return get_capacity_constraint(model_dict, "resource_cap", loc_tech)
end

function resource_capacity_equals_energy_capacity_constraint_rule(backend_model, set_indices, model_dict)
    """
    Add equality constraint for resource_cap to equal energy_cap, for any technologies
    which have defined resource_cap_equals_energy_cap
    """
    loc_tech = set_indices[1]
    variables = model_dict["variables"]
    return @constraint(backend_model,
        variables["resource_cap"][loc_tech] == variables["energy_cap"][loc_tech]
    )
end

function resource_area_constraint_rule(backend_model, set_indices, model_dict)
    """
    Set upper and lower bounds for resource_area.
    """
    loc_tech = set_indices[1]
    variables = model_dict["variables"]
    energy_cap_max = get_param(model_dict, "energy_cap_max", [loc_tech])
    area_per_energy_cap = get_param(model_dict, "resource_area_per_energy_cap", [loc_tech])

    if energy_cap_max == 0
        # If a technology has no energy_cap here, we force resource_area to zero,
        # so as not to accrue spurious costs
        return @constraint(backend_model, variables["resource_area"][loc_tech] == 0)
    else
        return get_capacity_constraint(model_dict, "resource_area", loc_tech)
    end
end

function resource_area_per_energy_capacity_constraint_rule(backend_model, set_indices, model_dict)
    """
    Add equality constraint for resource_area to equal a percentage of energy_cap,
    for any technologies which have defined resource_area_per_energy_cap
    """
    loc_tech = set_indices[1]
    variables = model_dict["variables"]
    area_per_energy_cap = get_param(model_dict, "resource_area_per_energy_cap", [loc_tech])

    return @constraint(backend_model,
        variables["resource_area"][loc_tech] ==
        variables["energy_cap"][loc_tech] * area_per_energy_cap
    )
end

function resource_area_capacity_per_loc_constraint_rule(backend_model, set_indices, model_dict)
    """
    Set upper bound on use of area for all locations which have `available_area`
    constraint set. Does not consider resource_area applied to demand technologies
    """
    loc = set_indices[1]
    variables = model_dict["variables"]
    parameters = model_dict["parameters"]
    available_area = parameters["available_area"][loc]

    loc_techs = split_comma_list(parameters["lookup_loc_techs_area"][loc])

    return @constraint(backend_model,
        sum(variables["resource_area"][loc_tech] for loc_tech in loc_techs)
        <= available_area
    )
end

function energy_capacity_constraint_rule(backend_model, set_indices, model_dict)
    """
    Set upper and lower bounds for energy_cap.
    """
    loc_tech = set_indices[1]
    return get_capacity_constraint(model_dict, "energy_cap", loc_tech)
end


using HDF5
using JSON
savepath = "SARA/NatCom2020/outer/data/"
xrd_file = "Bi2O3_19F44_01_outer_xrd_gradients_input_noise_iSARA.h5"

# savefile = "Bi2O3_19F44_01_outer_xrd_gradients_no_input_noise.h5"
xrd_f = h5open(savepath * xrd_file, "r")
xrd_temperatures = read(xrd_f, "temperatures")
xrd_dwelltimes = read(xrd_f, "dwelltimes")
xrd_coefficients = read(xrd_f, "coefficients")
close(xrd_f)

xrd_f = h5open(savepath * "Bi2O3_19F44_01_outer_xrd_data.h5", "r")
xrd_positions = read(xrd_f, "positions")
close(xrd_f)

include("../inner/inner_load_data.jl")
path = "/Users/sebastianament/Documents/SEA/XRD Analysis/SARA/Bi2O3_19F44_01/"
# path = ".."
file = "Bi2O3_19F44_01_inner_loop_data.json"
f = JSON.parsefile(path * file)
optical_positions, optical_coefficients, rescaling_parameters, optical_temperatures, optical_dwelltimes = load_data(f)

using Plots
plotly()
for i in 1:8
    p = 10
    match = (T, τ) -> xrd_temperatures[end, i] ≈ T && xrd_dwelltimes[end, i] ≈ τ
    j = 1
    while true
        if match(optical_temperatures[j]-p, optical_dwelltimes[j])
            break
        end
        j += 1
    end
    # j = findfirst(match, [optical_temperatures, optical_dwelltimes])
    plot(yscale = :log10)
    # xrd = xrd_coefficients[1, :, i]
    # opt = optical_coefficients[j][1]

    # xrd = abs.(diff(xrd))
    # opt = abs.(diff(opt))
    xrd = sum(abs2, diff(xrd_coefficients[:, :, i], dims = 2), dims = 1)
    opt = [optical_coefficients[j][k1][k2] for k1 in 1:16, k2 in 1:151]
    opt = sum(abs2, diff(opt, dims = 2), dims = 1)
    xrd = vec(xrd)
    opt = vec(opt)
    xrd ./= maximum(xrd)
    opt ./= maximum(opt)
    xrd_pos = xrd_positions[:, i]
    opt_pos = optical_positions[j]
    xrd_pos = xrd_pos[1:end-1]
    opt_pos = opt_pos[1:end-1]
    plot!(xrd_pos, xrd, label = "xrd")
    plot!(opt_pos, opt, label = "opt")
    gui()
end


using PortAudio

"""
Continuously read from the default audio input and plot an
ASCII level/peak meter
"""
function micmeter(metersize)
    mic = PortAudioStream(1, 0; latency = 0.1)

    signalmax = zero(eltype(mic))
    println("Press Ctrl-C to quit")
    while true
        block = read(mic, 512)
        blockmax = maximum(abs.(block)) # find the maximum value in the block
        signalmax = max(signalmax, blockmax) # keep the maximum value ever
        print("\r") # reset the cursor to the beginning of the line
        printmeter(metersize, blockmax, signalmax)
    end
end

"""
Print an ASCII level meter of the given size. Signal and peak
levels are assumed to be scaled from 0.0-1.0, with peak >= signal
"""
function printmeter(metersize, signal, peak)
    # calculate the positions in terms of characters
    peakpos = clamp(round(Int, peak * metersize), 0, metersize)
    meterchars = clamp(round(Int, signal * metersize), 0, peakpos - 1)
    blankchars = max(0, peakpos - meterchars - 1)

    for position in 1:meterchars
        printstyled(">", color = barcolor(metersize, position))
    end

    print(" "^blankchars)
    printstyled("|", color = barcolor(metersize, peakpos))
    print(" "^(metersize - peakpos))
end

"""
Compute the proper color for a given position in the bar graph. The first
half of the bar should be green, then the remainder is yellow except the final
character, which is red.
"""
function barcolor(metersize, position)
    if position / metersize <= 0.5
        :green
    elseif position == metersize
        :red
    else
        :yellow
    end
end

micmeter(80)

# function micmeter(metersize)
#     #meter size = 80
#     #max size = 1.0
#     #blockmax varaible max
#     mic = PortAudioStream(1, 0; latency = 0.1)
#     signalmax = zero(eltype(mic))
#     data = []
#     while true
#         block = read(mic, 512)
#         blockmax = maximum(abs.(block)) # find the maximum value in the block
#         signalmax = max(signalmax, blockmax) # keep the maximum value ever
#         println(size(data))
#         if size(data) == (16000,)
#             println("Emptied Array data")
#             senddata()
#             empty!(data)
#         end
#         append!(data, blockmax)
#         # println(metersize, " ",blockmax," ", signalmax)
#     end
# end

# micmeter(80)