# 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])
    @variable(m, inf1, Infinite(par1))
    @variable(m, inf2, Infinite(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 isequal(make_point_variable_ref(m, inf1, Float64[0]), pvref)
        @test parameter_values(pvref) == (0.,)
        @test isequal(make_point_variable_ref(m, inf1, Float64[0]), pvref)
        @test parameter_values(pvref) == (0.,)
        # test with inf2
        pvref = GeneralVariableRef(m, 2, PointVariableIndex)
        @test isequal(make_point_variable_ref(m, inf2, Float64[0, 0]), pvref)
        @test parameter_values(pvref) == (0., 0.)
        @test isequal(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 isequal(make_point_variable_ref(m, d1, Float64[0]), pvref)
        @test parameter_values(pvref) == (0.,)
        @test isequal(make_point_variable_ref(m, d1, Float64[0]), pvref)
        @test parameter_values(pvref) == (0.,)
        # test with d2
        pvref = GeneralVariableRef(m, 4, PointVariableIndex)
        @test isequal(make_point_variable_ref(m, d2, Float64[0, 0]), pvref)
        @test parameter_values(pvref) == (0., 0.)
        @test isequal(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_point_variable
    @testset "add_point_variable" begin
        @test_throws ErrorException add_point_variable(Model(), d1, [0.], 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_semi_infinite_variable_ref (InfiniteModel)
    @testset "make_semi_infinite_variable_ref (InfiniteModel)" begin
        # test first addition
        rvref1 = GeneralVariableRef(m, 1, SemiInfiniteVariableIndex)
        @test isequal(make_semi_infinite_variable_ref(m, inf2, [1], Float64[1]), rvref1)
        @test isequal(infinite_variable_ref(rvref1), inf2)
        @test eval_supports(rvref1) == Dict(1 => Float64(1))
        @test isequal(make_semi_infinite_variable_ref(m, inf2, [1], Float64[1]), rvref1)
        @test isequal(infinite_variable_ref(rvref1), inf2)
        @test eval_supports(rvref1) == Dict(1 => Float64(1))
        # test second addition
        rvref2 = GeneralVariableRef(m, 2, SemiInfiniteVariableIndex)
        @test isequal(make_semi_infinite_variable_ref(m, inf2, [1], Float64[0]), rvref2)
        @test isequal(infinite_variable_ref(rvref2), inf2)
        @test eval_supports(rvref2) == Dict(1 => Float64(0))
        # test first addition with derivative
        rvref1 = GeneralVariableRef(m, 3, SemiInfiniteVariableIndex)
        @test isequal(make_semi_infinite_variable_ref(m, d2, [1], Float64[1]), rvref1)
        @test isequal(infinite_variable_ref(rvref1), d2)
        @test eval_supports(rvref1) == Dict(1 => Float64(1))
        # test second addition with derivative
        rvref2 = GeneralVariableRef(m, 4, SemiInfiniteVariableIndex)
        @test isequal(make_semi_infinite_variable_ref(m, d2, [1], Float64[0]), rvref2)
        @test isequal(infinite_variable_ref(rvref2), d2)
        @test eval_supports(rvref2) == Dict(1 => Float64(0))
    end
    # test add_semi_infinite_variable
    @testset "add_semi_infinite_variable" begin
        @test_throws ErrorException add_semi_infinite_variable(Model(), Bad(), Val(:some_key))
    end
    # test make_semi_infinite_variable_ref (optimizer_model)
    @testset "make_semi_infinite_variable_ref (optimizer_model)" begin
        opt_m = Model()
        opt_m.ext[:my_key] = 42
        @test_throws ErrorException make_semi_infinite_variable_ref(opt_m, inf2, [1], Float64[1])
    end
    # test _process_aff_result
    @testset "_process_aff_result" begin
        @test InfiniteOpt._process_aff_result(42) == 42
        @test isequal(InfiniteOpt._process_aff_result(1par1), par1)
        @test isequal(InfiniteOpt._process_aff_result(2par1 + 3), 2par1 + 3)
    end
end

# Test measure expansion methods
@testset "expand_measure" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, par1 in [1, 2])
    @infinite_parameter(m, par2 in [1, 2])
    @infinite_parameter(m, pars1[1:2] in [1, 2], independent = true)
    @infinite_parameter(m, pars2[1:2] in [1, 2])
    @variable(m, inf1, Infinite(par1))
    @variable(m, inf2, Infinite(par1, par2))
    @variable(m, inf3, Infinite(par2))
    @variable(m, inf4, Infinite(pars1))
    @variable(m, inf5, Infinite(pars1, pars2))
    @variable(m, inf6, Infinite(pars2))
    @variable(m, inf7, Infinite(par1, par2, pars1))
    @variable(m, inf8, Infinite(pars1[1]))
    @variable(m, inf9, Infinite(pars1[1], par1))
    @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
        expected = 0.5 * (inf1(1) + inf1(2))
        @test isequal_canonical(InfiniteOpt.expand_measure(inf1, data1, m), expected)
        # test single param infinite var without measure param
        @test isequal_canonical(InfiniteOpt.expand_measure(inf1, data2, m), 3inf1)
        # test single param infinite var with measure param and others
        expected = 0.5 * (inf2(1, par2) + inf2(2, par2))
        @test isequal_canonical(InfiniteOpt.expand_measure(inf2, data1, m), expected)
        # test multi param infinite var with single element evaluation
        expected = 0.5 * (inf7(par1, par2, [1, pars1[2]]) + inf7(par1, par2, [2, pars1[2]]))
        @test isequal_canonical(InfiniteOpt.expand_measure(inf7, data7, m), expected)
    end
    # test expand_measure (derivatives) with DiscreteMeasureData
    @testset "Derivative (1D DiscreteMeasureData)" begin
        # test single param infinite var with measure param
        expected = 0.5 * (d1(1) + d1(2))
        @test isequal_canonical(InfiniteOpt.expand_measure(d1, data1, m), expected)
        # test single param infinite var without measure param
        @test isequal_canonical(InfiniteOpt.expand_measure(d1, data2, m), 3d1)
        # test single param infinite var with measure param and others
        expected = 0.5 * (d2(1, par2) + d2(2, par2))
        @test isequal_canonical(InfiniteOpt.expand_measure(d2, data1, m), expected)
        # test multi param infinite var with single element evaluation
        expected = 0.5 * (d7(par1, par2, [1, pars1[2]]) + d7(par1, par2, [2, pars1[2]]))
        @test isequal_canonical(InfiniteOpt.expand_measure(d7, data7, m), expected)
    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 isequal_canonical(InfiniteOpt.expand_measure(f1, data2, m), 3 * f1)
        # test single param infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, SemiInfiniteVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, SemiInfiniteVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, SemiInfiniteVariableIndex)
        @test isequal_canonical(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, SemiInfiniteVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 3, SemiInfiniteVariableIndex)
        @test isequal_canonical(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
        expected = inf4([1, 1]) + inf4([2, 2])
        @test isequal_canonical(InfiniteOpt.expand_measure(inf4, data3, m), expected)
        # test infinite var without measure param
        @test isequal_canonical(InfiniteOpt.expand_measure(inf4, data4, m), 4inf4)
        # test infinite var with measure param and others
        expected = inf5([1, 1], pars2) + inf5([2, 2], pars2)
        @test isequal_canonical(InfiniteOpt.expand_measure(inf5, data3, m), expected)
        # test infinite var with measure param that is out of order
        expected = inf4([1.2, 1]) + inf4([2, 2])
        @test isequal_canonical(InfiniteOpt.expand_measure(inf4, data5, m), expected)
        # test infinite var with measure param that doesn't overlap
        @test isequal_canonical(InfiniteOpt.expand_measure(inf8, data6, m), 2inf8)
        # test infinite var with measure param that doesn't overlap and there are others
        @test isequal_canonical(InfiniteOpt.expand_measure(inf9, data6, m), 2inf9)
        # test infinite variable has subset of multi params
        expected = inf8(1) + inf8(2)
        @test isequal_canonical(InfiniteOpt.expand_measure(inf8, data3, m), expected)
        # test making semi-infinite vars with variable not containing all the measure prefs
        expected = inf9(1, par1) + inf9(2, par1)
        @test isequal_canonical(InfiniteOpt.expand_measure(inf9, data3, m), expected)
    end
    # test expand_measure (derivatives) with Multi DiscreteMeasureData
    @testset "Derivative (Multi DiscreteMeasureData)" begin
        # test infinite var with measure param
        expected = d4([1, 1]) + d4([2, 2])
        @test isequal_canonical(InfiniteOpt.expand_measure(d4, data3, m), expected)
        # test infinite var without measure param
        @test isequal_canonical(InfiniteOpt.expand_measure(d4, data4, m), 4d4)
        # test infinite var with measure param and others
        expected = d5([1, 1], pars2) + d5([2, 2], pars2)
        @test isequal_canonical(InfiniteOpt.expand_measure(d5, data3, m), expected)
        # test infinite var with measure param that is out of order
        expected = d4([1.2, 1]) + d4([2, 2])
        @test isequal_canonical(InfiniteOpt.expand_measure(d4, data5, m), expected)
        # test infinite var with measure param that doesn't overlap
        @test isequal_canonical(InfiniteOpt.expand_measure(d8, data6, m), 2d8)
        # test infinite var with measure param that doesn't overlap and there are others
        @test isequal_canonical(InfiniteOpt.expand_measure(d9, data6, m), 2d9)
        # test infinite variable has subset of multi params
        expected = d8(1) + d8(2)
        @test isequal_canonical(InfiniteOpt.expand_measure(d8, data3, m), expected)
        # test making semi-infinite vars with variable not containing all the measure prefs
        expected = d9(1, par1) + d9(2, par1)
        @test isequal_canonical(InfiniteOpt.expand_measure(d9, data3, m), expected)
    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 isequal_canonical(InfiniteOpt.expand_measure(f4, data4, m), 4 * f4)
        # test infinite var with measure param and others
        idx = length(InfiniteOpt._data_dictionary(m, SemiInfiniteVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, SemiInfiniteVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, SemiInfiniteVariableIndex)
        @test isequal_canonical(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 isequal_canonical(InfiniteOpt.expand_measure(f8, data6, m), 2 * f8)
        # test infinite var with measure param that doesn't overlap and there are others
        @test isequal_canonical(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 semi-infinite vars with variable not containing all the measure prefs
        idx = length(InfiniteOpt._data_dictionary(m, SemiInfiniteVariable)) + 1
        rv1 = GeneralVariableRef(m, idx, SemiInfiniteVariableIndex)
        rv2 = GeneralVariableRef(m, idx + 1, SemiInfiniteVariableIndex)
        @test isequal_canonical(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 (Semi-infinite variable)
    @testset "Semi-Infinite Variable (1D DiscreteMeasureData)" begin
        # test single param semi-infinite var without measure param
        v = inf2(1, par2)
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data1, m), 0.5 * (v + v))
        # test single param semi-infinite var with measure param
        expected = 1.5 * (inf2(1, 1) + inf2(1, 1))
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data2, m), expected)
        # test single param semi-infinite var with measure param and others
        v = inf7(1, par2, pars1)
        expected = 1.5 * (inf7(1, 1, pars1) + inf7(1, 1, pars1))
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data2, m), expected)
        # test single param semi-infinite var partially from array element
        v = inf7(1, par2, pars1)
        expected = 0.5 * (inf7(1, par2, [1, pars1[2]]) + inf7(1, par2, [2, pars1[2]]))
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data7, m), expected)
    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 (semi-infinite variable)
    @testset "Semi-Infinite Variable (Multi DiscreteMeasureData)" begin
        # test array param semi-infinite var without measure param
        v = inf5([1, 1], pars2)
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data3, m), v + v)
        # test array param semi-infinite var with measure param
        expected = 2 * (inf5([1, 1], [1, 1]) + inf5([1, 1], [1, 1]))
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data4, m), expected)
        # test array param semi-infinite var with measure param and others
        v = inf7(1, par2, pars1)
        expected = inf7(1, par2, [1, 1]) + inf7(1, par2, [2, 2])
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data3, m), expected)
        # test array param that is out of order and should make point variable
        v = inf5(pars1, [1, 1])
        expected = inf5([1.2, 1], [1, 1]) + inf5([2, 2], [1, 1])
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data5, m), expected)
        # test array param that with no others that partially overlap
        v = inf5([pars1[1], 1], [1, 1])
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data6, m), 2v)
        # test array param with others that partially overlap
        v = inf5([pars1[1], 1], pars2)
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data6, m), 2v)
        # test partial overlap
        v = inf5([pars1[1], 1], pars2)
        expected = inf5([1, 1], pars2) + inf5([2, 1], pars2)
        @test isequal_canonical(InfiniteOpt.expand_measure(v, data3, m), expected)
    end
    # test expand_measure (finite variable)
    @testset "Finite Variable (1D DiscreteMeasureData)" begin
        # test with single parameter measure
        @test isequal_canonical(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 isequal_canonical(InfiniteOpt.expand_measure(x, data3, m), x + x)
    end
    # test expand_measure (parameter)
    @testset "Infinite Parameter (1D DiscreteMeasureData)" begin
        # test with different parameter
        @test isequal_canonical(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 isequal_canonical(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 isequal_canonical(InfiniteOpt.expand_measure(expr, data2, m), expr * 3)
        # test QuadExpr
        expr = x^2 + 2x +3
        @test isequal_canonical(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 isequal_canonical(InfiniteOpt.expand_measure(expr, data3, m), expr * 2)
        # test QuadExpr
        expr = x^2 + 2x +3
        @test isequal_canonical(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
        expected = inf1(1) + inf1(2) - x + 3par2 - 1.5
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data1, m), expected)
        # test single param AffExpr, with measures
        expr = meas2 - x + par1
        expected = 6 * inf3(1) * x - x - 3
        @test isequal_canonical(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
        expected = inf4([1, 1]) + inf4([2, 2]) + 2par1 - 2x + 7
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data3, m), expected)
        # test array param AffExpr, with measures
        expr = meas2 - x + par1 - inf4
        expected = 12 * inf3(1) * x - 4par1 - 2x - inf4([1, 1]) - inf4([2, 2])
        @test isequal_canonical(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
        expected = 0.5 * (2 * inf1(1) * inf2(1, par2) - 2 * inf3 * inf4 +
                          2 * inf1(2) * inf2(2, par2) + 2x + 4)
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data1, m), expected)
        # test single param QuadExpr with first variable not integrated
        expr = 3 * inf3 * inf1 + pars1[1] - 1
        expected = 0.5 * (3 * inf3 * inf1(1) + 3 * inf3 * inf1(2) + 2pars1[1] - 2)
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data1, m), expected)
        # test single param QuadExpr with first variable integrated
        expr = 3 * inf2 * inf1 + pars1[1] + 1
        expected = 1.5 * (6 * inf2(par1, 1) * inf1 + 2pars1[1] + 2)
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data2, m), expected)
        # test single parameter with first quadratic term becomes number
        expr = par1 * inf1 + 2
        expected = 0.5 * (inf1(1) + 2 * inf1(2) + 4)
        @test isequal_canonical(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 isequal_canonical(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
        expected = 0.5 * (inf1(1) + 2 * inf1(2) + 4)
        @test isequal_canonical(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 isequal_canonical(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
        expected = 2 * inf4([1, 1]) * inf5([1, 1], pars2) - 2 * inf1 * inf2 +
                   2 * inf4([2, 2]) * inf5([2, 2], pars2) + 2x + 4
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data3, m), expected)
        # test array param QuadExpr with first variable not integrated
        expr = 3 * inf1 * inf4 + par1 - 1
        expected = 3 * inf1 * inf4([1, 1]) + 3 * inf1 * inf4([2, 2]) + 2par1 - 2
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data3, m), expected)
        # test array param QuadExpr with first variable integrated
        expr = 3 * inf5 * inf1 + pars1[1] + 1
        expected = 2 * (6 * inf5(pars1, [1, 1]) * inf1 + 2pars1[1] + 2)
        @test isequal_canonical(InfiniteOpt.expand_measure(expr, data4, m), expected)
        # test array parameter with first quadratic term becomes number
        expr = pars1[1] * inf4 + 2
        expected = inf4([1, 1]) + 2 * inf4([2, 2]) + 4
        @test isequal_canonical(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 isequal_canonical(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
        expected = inf4([1, 1]) + 2 * inf4([2, 2]) + 4
        @test isequal_canonical(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 isequal_canonical(InfiniteOpt.expand_measure(expr, data3, m), expected)
    end
    # test expand_measure (NLPExpr univariate)
    @testset "NLPExpr (1D DiscreteMeasureData)" begin
        # test simple
        expr = sin(inf1)
        expected = 0.5 * sin(inf1(1)) + 0.5 * sin(inf1(2))
        @test isequal(expand_measure(expr, data1, m), expected)
        # test with parameter
        expr = sin(inf1) + par1
        expected = 0.5 * (sin(inf1(1)) + 1) + 0.5 * (sin(inf1(2)) + 2)
        @test isequal(expand_measure(expr, data1, m), expected)
    end
    # test expand_measure (NLPExpr multivariate)
    @testset "NLPExpr (Multi DiscreteMeasureData)" begin
        # test simple
        expr = sin(inf5)
        expected = 1 * sin(inf5([1, 1], pars2)) + 1 * sin(inf5([2, 2], pars2))
        @test isequal(expand_measure(expr, data3, m), expected)
    end
    # test expand_measure (measure)
    @testset "Measure" begin
        # test single param measure
        expected = 0.5 * (inf1(1) + inf1(2))
        @test isequal_canonical(InfiniteOpt.expand_measure(meas1, data1, m), expected)
        # test another single param
        expected = 0.5 * (12 * inf3(1) * x - 3 * (1 + 2))
        @test isequal_canonical(InfiniteOpt.expand_measure(meas2, data1, m), expected)
        # test array param measure
        expected = inf1(1) + inf1(2)
        @test isequal_canonical(InfiniteOpt.expand_measure(meas1, data3, m), expected)
    end
    # test expand_measure (FunctionalDiscreteMeasureData)
    @testset "FunctionalDiscreteMeasureData" begin
        # test without generative supports
        coefa(a) = ones(length(a))
        data = FunctionalDiscreteMeasureData(par1, coefa, 2, All,
                                             is_expect = true)
        @test isequal_canonical(InfiniteOpt.expand_measure(x, data, m), 2x)
        # test with generative supports
        coefb(a) = ones(length(a) + 1)
        info = UniformGenerativeInfo([0.5], InternalLabel)
        data = FunctionalDiscreteMeasureData(par1, coefb, 2, All,
                                             generative_support_info = info)
        InfiniteOpt._set_generative_support_info(dispatch_variable_ref(par1), info)
        @test isequal_canonical(InfiniteOpt.expand_measure(x, data, m), 3x)
    end
    # test _prep_generative_supps
    @testset "_prep_generative_supps" begin
        @test InfiniteOpt._prep_generative_supps(par1, NoGenerativeSupports) isa Nothing
        @test InfiniteOpt._prep_generative_supps(par1, UniformGenerativeInfo) isa Nothing
    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, par1 in [1, 2])
    @infinite_parameter(m, par2 in [1, 2])
    @infinite_parameter(m, pars1[1:2] in [1, 2], num_supports = 2)
    @variable(m, inf3, Infinite(par2))
    @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 isequal_canonical(InfiniteOpt.analytic_expansion(2x + inf3, data, m), 0.5 * (2x + inf3))
        # test as expectation
        coef1(a) = ones(length(a))
        data = FunctionalDiscreteMeasureData(par2, coef1, 0, WeightedSample,
                                             is_expect = true)
        @test isequal_canonical(InfiniteOpt.analytic_expansion(inf3, data, m), inf3)
        # test other
        data = DiscreteMeasureData(par1, [1, 0.5], [1, 1.5])
        @test isequal_canonical(InfiniteOpt.analytic_expansion(x, data, m), 1.5 * x)
    end
    # test with multi dimensional data
    @testset "Multi Discrete Data" begin
        # test with bounds
        coef2(a) = ones(length(a))
        data = FunctionalDiscreteMeasureData(pars1, coef2, 0, All,
                    lower_bounds = [1, 1], upper_bounds = [1.5, 2])
        @test isequal_canonical(InfiniteOpt.analytic_expansion(x, data, m), 0.5x)
        # test as expectation
        data = DiscreteMeasureData(pars1, [1], [[1, 1]], is_expect = true)
        @test isequal_canonical(InfiniteOpt.analytic_expansion(inf3, data, m), inf3)
        # test other
        coef3(a) = ones(size(a, 2))
        data = FunctionalDiscreteMeasureData(pars1, coef3, 2, All)
        @test isequal_canonical(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, par1 in [1, 2])
    @infinite_parameter(m, par2 in [1, 2])
    @infinite_parameter(m, pars1[1:2] in [1, 2])
    @variable(m, inf1 >= 1, Infinite(par1))
    @variable(m, inf2, Infinite(par1, par2))
    @variable(m, inf3, Infinite(par2))
    @variable(m, inf4, Infinite(pars1))
    @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
        expected = 0.5 * (inf1(1) + inf1(2) + 6x - inf2(1, par2) -
                          inf2(2, par2) + 2inf3 - 4)
        @test isequal_canonical(InfiniteOpt.expand_measures(meas1, m), expected)
        # test the second measure
        expected = 2 * inf4([1, 1]) * x + 2 * inf4([2, 2]) * x - 3 + 2inf2
        @test isequal_canonical(InfiniteOpt.expand_measures(meas2, m), expected)
        # test analytic expansion
        meas3 = measure(x, data1)
        @test isequal_canonical(InfiniteOpt.expand_measures(meas3, m), x)
    end
    # test expand_measures (GeneralVariableRef)
    @testset "GeneralVariableRef" begin
        @test isequal_canonical(InfiniteOpt.expand_measures(x, m), x)
        @test isequal_canonical(InfiniteOpt.expand_measures(par1, m), par1)
        @test isequal_canonical(InfiniteOpt.expand_measures(inf1, m), inf1)
    end
    # test expand_measures (Finite Expression)
    @testset "Expression (Finite)" begin
        expr = 2x + 2
        @test isequal_canonical(InfiniteOpt.expand_measures(expr, m), expr)
        expr = x^2 + 2x + 2
        @test isequal_canonical(InfiniteOpt.expand_measures(expr, m), expr)
    end
    # test expand_measures (GenericAffExpr)
    @testset "AffExpr (General)" begin
        # test returning AffExpr
        expr = 2inf4 + x + 2meas1
        expected = 2inf4 + 7x + inf1(1) + inf1(2) - inf2(1, par2) -
                   inf2(2, par2) + 2inf3 - 4
        @test isequal_canonical(InfiniteOpt.expand_measures(expr, m), expected)
        # test returning QuadExpr
        expr = x + 3par1 + meas2
        expected = x + 3par1 + 2 * inf4([1, 1]) * x + 2 * inf4([2, 2]) * x - 3 + 2inf2
        @test isequal_canonical(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
        m1 = 0.5 * (inf1(1) + inf1(2) + 6x - inf2(1, par2) - inf2(2, par2) +
                    2inf3 - 4)
        # test with this thorough expression
        @test isequal_canonical(InfiniteOpt.expand_measures(expr, m), 2 * m1 * x - x + 1)
    end
     # test expand_measures (NLPExpr)
     @testset "NLPExpr (General)" begin
        # prepare first measure expansion
        expr = sin(meas1)
        m1 = 0.5 * (inf1(1) + inf1(2) + 6x - inf2(1, par2) - inf2(2, par2) +
                    2inf3 - 4)
        # test with this thorough expression
        @test isequal_canonical(InfiniteOpt.expand_measures(expr, m), sin(m1))
    end
    # test expand_measures (AbstractArray)
    @testset "AbstractArray" begin
        # prepare first measure expansion
        expr = [2x + 1, x]
        @test isequal_canonical(InfiniteOpt.expand_measures(expr, m), expr)
    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, par1 in [1, 2])
    @infinite_parameter(m, par2 in [1, 2])
    @infinite_parameter(m, pars1[1:2] in [1, 2])
    @variable(m, inf1 >= 1, Infinite(par1))
    @variable(m, inf2 >= 0, Infinite(par1, par2))
    @variable(m, inf3, Infinite(par2))
    @variable(m, inf4, Infinite(pars1))
    @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
        expected = 0.5 * (inf1(1) + inf1(2) + 6x - inf2(1, par2) -
                   inf2(2, par2) + 2inf3 - 4)
        @test isequal_canonical(expand(meas1), expected)
        # test the second measure
        expected = 2 * inf4([1, 1]) * x + 2 * inf4([2, 2]) * x - 3 + 2inf2
        @test isequal_canonical(expand(meas2), expected)
        # test analytic
        meas = measure(x, data1)
        @test is_analytic(meas)
        @test isequal_canonical(expand(meas), 1*x)
    end
    # test expand_all_measures!
    @testset "expand_all_measures!" begin
        # prepare the model for testing
        @variable(m, y)
        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.)
        @constraint(m, c3, measure(inf2, data1) + inf1 >= 0.,
                    DomainRestrictions(par2 => [1, 1.5]))
        @constraint(m, c4, measure(inf4 + y, data2) <= 3.,
                    DomainRestrictions(par2 => 1))
        @constraint(m, c5, [measure(inf2, data1), x] in MOI.Zeros(2))
        # prepare comparison expressions
        obj = x + 0.5inf1(1) + inf1(2) + 3
        c2_expected = 2x - (2 * inf1(1) * x + 2 * inf1(2) * x + inf2(1, 1) +
                            inf2(1, 2) + inf2(2, 1) + inf2(2, 2))
        c3_expected = 0.5inf2(1, par2) + 0.5inf2(2, par2) + inf1
        c4_expected = inf4([1, 1]) + inf4([2, 2]) + 2y
        c5_expected = AbstractJuMPScalar[0.5inf2(1, par2) + 0.5inf2(2, par2), x]
        # test the expansion
        @test isa(expand_all_measures!(m), Nothing)
        @test isequal_canonical(objective_function(m), obj)
        @test is_valid(m, c2)
        @test is_valid(m, c3)
        @test is_valid(m, c4)
        @test is_valid(m, c5)
        @test name(c1) == "c1"
        @test name(c2) == "c2"
        @test name(c3) == "c3"
        @test name(c4) == "c4"
        @test name(c5) == "c5"
        @test isequal_canonical(constraint_object(c1).func, inf1 + x)
        @test isequal_canonical(constraint_object(c2).func, c2_expected)
        @test isequal_canonical(constraint_object(c3).func, c3_expected)
        @test isequal_canonical(constraint_object(c4).func, c4_expected)
        @test isequal_canonical(constraint_object(c5).func, c5_expected)
        @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 constraint_object(c5).set == MOI.Zeros(2)
        @test domain_restrictions(c1) == DomainRestrictions()
        @test domain_restrictions(c2) == DomainRestrictions()
        @test domain_restrictions(c3) == DomainRestrictions(par2 => [1, 1.5])
        @test domain_restrictions(c4) == DomainRestrictions(par2 => 1)
        @test domain_restrictions(c5) == DomainRestrictions()
    end
end


#module plotting
#(for future dev)

#export plotBode, plotNyquist

using DataFrames, Queryverse, DelimitedFiles, CSV, Plots
include("/Users/nirmitdeshpande/.julia/dev/impedance/src/preprocessing.jl")

function plotNyquist()
    cyc1=100.0
    cyc2=103.0

    df = cellSelect(cyc1, cyc2)
   #since plottingbasic() will be in an independant file, add parameters here

    #add plot variation and
    plot(df[!,4], df[!,3], title= "Nyquist Plot", xlabel = "Re(Z)",ylabel="Im(Z)", label="/")
    #savefig("testerplot.png")

    #use DF plotting instead of basic array
end

function plotBode()
    cyc1=100.0
    cyc2=103.0

    df= cellSelect(cyc1, cyc2)

    dff= DataFrame(a=Float64)
    dfa= DataFrame(a=Float64)
    dfb= DataFrame(b=Float64)

    dfa= angle.(df[!,3]+ (df[!,4])im)
    dff= log.(abs.(df[!,2]))

    plot(dfa, dff, title= "Bode Plot (phase)", xlabel = "log(f)",ylabel="phase", label="/")
    #savefig("testerplot.png")
    #dfb= log.(abs.(df[!,3]+ (df[!,4])im))

   #plot(dfb,dff, title= "Bode Plot (magnitude)", xlabel = "log(f)",ylabel="log(magnitude)", label="abc")
    #savefig("testerplot.png")
end

#end


# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder

name = "FINUFFTBuilder"
version = v"0.1.4"

# Collection of sources required to build FINUFFTBuilder
sources = [
    "https://github.com/flatironinstitute/finufft/archive/1.1.1.zip" =>
    "a51e8d56544378ace1e5c4177717dfe0943ea3aab08018094f2a9c4375576ed6",

]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir
cd finufft-1.1.1/
# Scrub out OMP suffix, since FFTW compiled with --with-combined-threads
sed -i s/_\$\(FFTWOMPSUFFIX\)// makefile
# -march=native appears to be Intel-only
if [[ $target == aarch64-* ]] || [[ $target == arm-* ]] || [[ $target == powerpc* ]]; then
   sed -i s/-march=native// makefile
fi
# Static libgcc linking for MacOS
#if [[ ${target} == x86_64-apple* ]]; then
#    static="-static-libgcc -static-libstdc++"
#fi
# Compile
make lib/libfinufft.so LIBRARY_PATH=$prefix/lib/ CPATH=$prefix/include/ LIBS="$static -lm -L$prefix/lib $LDFLAGS"
# Copy in lib with right suffix
if [[ ${target} == x86_64-apple* ]]; then
   cp `g++ --print-file-name=libgomp.dylib` $prefix/lib/libgomp.1.dylib
   cp lib/libfinufft.so $prefix/lib/libfinufft.dylib
else
   cp lib/libfinufft.so $prefix/lib/
fi
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = [
    Linux(:i686, libc=:glibc),
    Linux(:x86_64, libc=:glibc),
    Linux(:aarch64, libc=:glibc),
    Linux(:armv7l, libc=:glibc, call_abi=:eabihf),
    Linux(:powerpc64le, libc=:glibc),
    Linux(:i686, libc=:musl),
    Linux(:x86_64, libc=:musl),
    Linux(:aarch64, libc=:musl),
    Linux(:armv7l, libc=:musl, call_abi=:eabihf),
    MacOS(:x86_64),
    FreeBSD(:x86_64)
]

# The products that we will ensure are always built
products(prefix) = [
    LibraryProduct(prefix, "libfinufft", :libfinufft)
]

# Dependencies that must be installed before this package can be built
dependencies = [
    "https://raw.githubusercontent.com/JuliaMath/FFTW.jl/v0.2.4/deps/build_fftw.jl"
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies)


module GeneratorsTest

using Test
using ForneyLab
using LinearAlgebra: Diagonal
using ForneyLab: entropiesSourceCode, energiesSourceCode, freeEnergySourceCode, marginalTableSourceCode, inboundSourceCode, scheduleSourceCode, removePrefix, vagueSourceCode, initializationSourceCode, optimizeSourceCode, algorithmSourceCode, valueSourceCode, posteriorFactorSourceCode

# create custom module
module Foo
    struct Baz
        f::Int64
    end
    struct Bar{T}
        f::T
    end
    struct Bak{T, K}
        f::T
        g::K
    end
end

@testset "removePrefix" begin
    # initial check
    @test removePrefix(ForneyLab.SPGaussianMeanPrecisionOutNPP) == "SPGaussianMeanPrecisionOutNPP"

    # initialize structures
    baz = Foo.Baz(1)
    bar = Foo.Bar(baz)
    bak = Foo.Bak(bar, bar)

    # check whether the removePrefix functions works as desired
    @test removePrefix(typeof(baz)) == "Baz"
    @test removePrefix(typeof(bar)) == "Bar{Baz}"
    @test removePrefix(typeof(bak)) == "Bak{Bar{Baz}, Bar{Baz}}"
end

@testset "valueSourceCode" begin
    @test valueSourceCode(1) == "1"
    @test valueSourceCode([1]) == "[1]"
    @test valueSourceCode(mat(1)) == "mat(1)"
    @test valueSourceCode(reshape([1,2,3], 3, 1)) == "reshape([1, 2, 3], (3, 1))"
    @test valueSourceCode([1 2; 2 1]) == "[1 2; 2 1]"
    @test valueSourceCode(Diagonal([1])) == "Diagonal([1])"
end

f() = 1.0 # Define a function

@testset "inboundSourceCode" begin
    # custom inbound
    inbound = Dict(:keyword => false,
                   :inx     => 1)
    @test inboundSourceCode(inbound) == "1"

    inbound = Dict(:keyword => false,
                   :g       => f)
    @test inboundSourceCode(inbound) == "f"

    inbound = Dict(:g => f)
    @test inboundSourceCode(inbound) == "g=f"

    # value inbound
    g = FactorGraph()
    var = Variable()
    inbound = Clamp(var, 1.0)
    inbound.dist_or_msg = Message
    @test inboundSourceCode(inbound) == "Message(Univariate, PointMass, m=1.0)"

    # buffer inbound
    g = FactorGraph()
    var = Variable()
    inbound = Clamp(var, 1.0)
    inbound.dist_or_msg = Message
    inbound.buffer_id = :x
    inbound.buffer_index = 1
    @test inboundSourceCode(inbound) == "Message(Univariate, PointMass, m=data[:x][1])"

    g = FactorGraph()
    var = Variable()
    inbound = Clamp(var, 1.0)
    inbound.dist_or_msg = Message
    inbound.buffer_id = :x
    inbound.buffer_index = 0
    @test inboundSourceCode(inbound) == "Message(Univariate, PointMass, m=data[:x])"

    # marginal
    inbound = MarginalEntry()
    inbound.marginal_id = :x
    @test inboundSourceCode(inbound) == "marginals[:x]"

    # message
    inbound = ScheduleEntry()
    inbound.schedule_index = 1
    @test inboundSourceCode(inbound) == "messages[1]"

    # nothing
    @test inboundSourceCode(nothing) == "nothing"
end

@testset "vagueSourceCode" begin
    entry = ScheduleEntry()
    entry.family = GaussianMeanPrecision
    entry.dimensionality = ()
    entry_code = vagueSourceCode(entry)
    @test entry_code == "vague(GaussianMeanPrecision)"

    entry = ScheduleEntry()
    entry.family = GaussianMeanPrecision
    entry.dimensionality = (1,)
    entry_code = vagueSourceCode(entry)
    @test entry_code == "vague(GaussianMeanPrecision, (1,))"
end

@testset "marginalTableSourceCode" begin
    inbounds = Vector{ScheduleEntry}(undef, 2)
    inbounds[1] = ScheduleEntry()
    inbounds[1].schedule_index = 1
    inbounds[2] = ScheduleEntry()
    inbounds[2].schedule_index = 2

    marginal_table = Vector{MarginalEntry}(undef, 3)
    marginal_table[1] = MarginalEntry()
    marginal_table[1].marginal_id = :x
    marginal_table[1].marginal_update_rule = Nothing
    marginal_table[1].inbounds = [inbounds[1]]
    marginal_table[2] = MarginalEntry()
    marginal_table[2].marginal_id = :y
    marginal_table[2].marginal_update_rule = ForneyLab.Product
    marginal_table[2].inbounds = inbounds
    marginal_table[3] = MarginalEntry()
    marginal_table[3].marginal_id = :z
    marginal_table[3].marginal_update_rule = ForneyLab.MGaussianMeanPrecisionGGD
    marginal_table[3].inbounds = inbounds

    marginal_table_code = marginalTableSourceCode(marginal_table)

    @test occursin("marginals[:x] = messages[1].dist", marginal_table_code)
    @test occursin("marginals[:y] = messages[1].dist * messages[2].dist", marginal_table_code)
    @test occursin("marginals[:z] = ruleMGaussianMeanPrecisionGGD(messages[1], messages[2])", marginal_table_code)
end

@testset "scheduleSourceCode" begin
    schedule = Vector{ScheduleEntry}(undef, 2)
    schedule[1] = ScheduleEntry()
    schedule[1].schedule_index = 1
    schedule[1].message_update_rule = Nothing
    schedule[1].inbounds = []
    schedule[2] = ScheduleEntry()
    schedule[2].message_update_rule = ForneyLab.SPGaussianMeanPrecisionOutNPP
    schedule[2].schedule_index = 2
    schedule[2].inbounds = [schedule[1]]

    schedule_code = scheduleSourceCode(schedule)
    @test occursin("messages[2] = ruleSPGaussianMeanPrecisionOutNPP(messages[1])", schedule_code)
end

@testset "entropiesSourceCode" begin
    inbounds = Vector{MarginalEntry}(undef, 2)
    inbounds[1] = MarginalEntry()
    inbounds[1].marginal_id = :y_x
    inbounds[2] = MarginalEntry()
    inbounds[2].marginal_id = :x

    entropies_vect = [Dict(:counting_number => -1,
                           :inbound         => inbounds[1]),
                      Dict(:counting_number => 2,
                           :inbound         => inbounds[2])]

    entropies_code = entropiesSourceCode(entropies_vect)

    @test occursin("F -= -1*differentialEntropy(marginals[:y_x])", entropies_code)
    @test occursin("F -= 2*differentialEntropy(marginals[:x])", entropies_code)
end

@testset "energiesSourceCode" begin
    inbound = MarginalEntry()
    inbound.marginal_id = :x
    energies_vect = [Dict(:counting_number => 1,
                          :node            => GaussianMeanPrecision,
                          :inbounds        => [inbound])]
    energies_code = energiesSourceCode(energies_vect)

    @test occursin("F += averageEnergy(GaussianMeanPrecision, marginals[:x])", energies_code)
end

@testset "initializationSourceCode" begin
    fg = FactorGraph()
    pfz = PosteriorFactorization()
    pf = PosteriorFactor(fg, id=:X)
    algo = InferenceAlgorithm(pfz)
    pf.algorithm_id = algo.id
    pf.initialize = true
    entry = ScheduleEntry()
    entry.schedule_index = 1
    entry.initialize = true
    entry.family = GaussianMeanPrecision
    entry.dimensionality = ()
    pf.schedule = [entry]

    pf_code = initializationSourceCode(pf)
    @test occursin("function initX()", pf_code)
    @test occursin("messages[1] = Message(vague(GaussianMeanPrecision))", pf_code)
end

@testset "optimizeSourceCode" begin
    fg = FactorGraph()
    pfz = PosteriorFactorization()
    pf = PosteriorFactor(fg, id=:X)
    algo = InferenceAlgorithm(pfz)
    pf.algorithm_id = algo.id
    pf.optimize = true

    pf_code = optimizeSourceCode(pf)
    @test occursin("function optimizeX!(data::Dict, marginals::Dict=Dict(), messages::Vector{Message}=initX())", pf_code)
end

@testset "posteriorFactorSourceCode" begin
    fg = FactorGraph()
    pfz = PosteriorFactorization()
    pf = PosteriorFactor(fg, id=:X)
    algo = InferenceAlgorithm(pfz)
    pf.algorithm_id = algo.id
    pf.schedule = []
    pf.marginal_table = []

    pf_code = posteriorFactorSourceCode(pf)
    @test occursin("function stepX!(data::Dict, marginals::Dict=Dict(), messages::Vector{Message}=Array{Message}(undef, 0))", pf_code)
end

@testset "algorithmSourceCode" begin
    algo = InferenceAlgorithm()
    code = algorithmSourceCode(algo)
    @test occursin("begin", code)
end

@testset "freeEnergySourceCode" begin
    algo = InferenceAlgorithm()
    algo.posterior_factorization.free_energy_flag = true
    free_energy_code = freeEnergySourceCode(algo)
    @test occursin("function freeEnergy(data::Dict, marginals::Dict)", free_energy_code)
end

end # module


module TestCLI
using Test
using FileIO
using DataStructures

include("utils.jl")

# include source of bin file here to avoid world age issues
include("../bin/qxrun.jl")

@testset "Test prepare rqc input cli script" begin
    ghz_example_dir = joinpath(dirname(@__DIR__), "examples", "ghz")
    dsl_input = joinpath(ghz_example_dir, "ghz_5.qx")
    # create empty temporary directory
    mktempdir() do path
        output_fname = joinpath(path, "out.jld2")
        args = ["-d", dsl_input,
                "-o", output_fname]
        main(args)
        @test isfile(output_fname)

        output = load(output_fname, "results")
        @test all([x[2] ≈ ghz_results[x[1]] for x in zip(output...)])
    end

    mktempdir() do path
        output_fname = joinpath(path, "out.jld2")
        args = ["-d", dsl_input,
                "-o", output_fname,
                "--number-amplitudes", "1"]
        main(args)
        @test isfile(output_fname)

        output = load(output_fname, "results")
        @test length(output[1]) == 1
        @test output[2][1] ≈ ghz_results["11001"]
    end

    mktempdir() do path
        output_fname = joinpath(path, "out.jld2")
        args = ["-d", dsl_input,
                "-o", output_fname,
                "--number-amplitudes", "2",
                "--number-slices", "1"]
        main(args)
        @test isfile(output_fname)

        output = load(output_fname, "results")
        @test length(output) == 2
        @test all([output[2][x] ≈ ghz_results[output[1][x]] for x in 1:2])
    end

end
end


using Base.Test, DataStreams, DataFrames, NullableArrays

if !isdefined(Core, :String)
    typealias String UTF8String
end

ROWS = 2
COLS = 3

sch = Data.Schema([Int, Int, Int], ROWS)
data = [ 1 2 ; 10 20 ]

@test Data.header(sch) == String["Column1", "Column2", "Column3"]
@test Data.types(sch) == [Int, Int, Int]
@test size(sch) == (ROWS, COLS)

"""
A generic `Sink` type that fulfills the DataStreams interface
wraps any kind of Julia structure `T`; by default `T` = Vector{NullableVector}
"""
type TableSink{T} <: Data.Sink
    schema::Data.Schema
    data::T
    other::Any # for other metadata, references, etc.
end

function TableSink(sch::Data.Schema)
    rows, cols = size(sch)
    TableSink(sch, NullableVector[NullableArray(T, rows) for T in Data.types(sch)], 0)
end

TableSink(s::Data.Source) = TableSink(schema(s))

import DataStreams.Data.stream!

function Data.stream!(src::DataFrame, snk::TableSink)
    # TODO: this could be improved considering different source Schema.
    snk.data = src.columns
end

src_tb = DataFrame()
snk = TableSink(sch)

snk_tb = Data.stream!(src_tb, snk)


# TODO: Check how other classification models handle the target type. Should I
#       simply enforce it to be an integer? Furthermore, a may consider enforcing
#       targets to be in the range [1, nclasses]. These could be label encoded
#       by the user or the model could detect and perform the encoding. If I did
#       this, `discriminators` could simply be a list, with the discriminator's
#       indices being the target they are associated to. `predict_response` return
#       would also be simplified to a vector or matrix
mutable struct MultiDiscriminatorClassifier{C, N <: Discriminator} <: AbstractModel
    n::Int
    seed::Union{Int,Nothing}
    partitioner::Union{RandomPartitioner,Nothing}
    discriminators::Dict{C,N}

    function MultiDiscriminatorClassifier{C,N}(n::Int; seed::Union{Nothing,Int}=nothing) where {C,N<:Discriminator}
        n < 1 && throw(DomainError(n, "tuple size `n` may not be less then 1"))
        !isnothing(seed) && seed < 0 && throw(DomainError(seed, "`seed` must be non-negative"))

        new{C,N}(n, seed, nothing, Dict{C,N}())
    end

    function MultiDiscriminatorClassifier{C,N}(width::Int, n::Int; seed::Union{Nothing,Int}=nothing) where {C,N<:Discriminator}
        partitioner = RandomPartitioner(width, n; seed)

        new{C,N}(n, seed, partitioner, Dict{C,N}())
    end
end

# Default discriminator type is Discriminator
MultiDiscriminatorClassifier{C}(args...; kargs...) where C =
    MultiDiscriminatorClassifier{C,Discriminator}(args...; kargs...)

# Default target type is 'Int'
MultiDiscriminatorClassifier(args...; kargs...) = MultiDiscriminatorClassifier{Int}(args...; kargs...)

function train!(model::MultiDiscriminatorClassifier{C,N}, X::AbstractVector{Bool}, y::C) where {C,N<:Discriminator}
    if isnothing(model.partitioner)
        model.partitioner = RandomPartitioner(length(X), model.n; seed=model.seed)
    end

    train!(get!(model.discriminators, y) do
        N(model.partitioner)
    end, X)
end

function train!(model::MultiDiscriminatorClassifier{C}, X::AbstractMatrix{Bool}, y::AbstractVector{C}) where {C}
    size(X, 1) != size(y, 1) && throw(
        DimensionMismatch("Number of observations (rows in X) must match the number of targets (elements in y)"))

    # I could create all necessary discriminators upfront, which could lead to a gain in performance. On the other hand,
    # the current design fits quite nicely with the repetitive training that is common in RL.

    for i in eachindex(y)
        train!(model, X[i, :], y[i])
    end
end

# TODO: Add predict and predict_response umbrella methods where the bleaching threshold is a keyword argument for
#       convenience.
predict(model::MultiDiscriminatorClassifier{C}, X; b=:none) where {C} = predict(model, X, b)
predict_response(model::MultiDiscriminatorClassifier{C}, X; b=:none) where {C} = predict_response(model, X, b)

# TODO: Check if model was trained
function predict(model::MultiDiscriminatorClassifier{C}, X::AbstractVector{Bool}, b::Int=0) where {C}
    largest_response = -1
    best_category = first(keys(model.discriminators))

    for (category, discriminator) in model.discriminators
        response = predict(discriminator, X; b)

        if response > largest_response
            largest_response = response
            best_category = category
        end
    end

    return best_category
end

function predict(model::MultiDiscriminatorClassifier{C}, X::AbstractMatrix{Bool}, b::Int) where {C}
    return C[predict(model, row; b) for row in eachrow(X)]
end

function predict_response(model::MultiDiscriminatorClassifier{C}, X::AbstractVecOrMat{Bool}, b::Int) where {C}
    Dict(k => predict(d, X; b) for (k,d) in model.discriminators)
end

function response_tie(responses)
    # Single-value vector cannot have a tie
    length(responses) == 1 && return false

    p = sortperm(responses; rev=true)

    # Check if the two largest responses are the same
    return responses[p[1]] == responses[p[2]]
end

const bleaching_policies = [:none, :linear]

function predict_response(model::MultiDiscriminatorClassifier{C}, X::AbstractVecOrMat{Bool}, b::Symbol) where {C}
    if b == :none
        return predict_response(model, X, 0)
    elseif b == :linear
        return predict_response_linear_bleaching(model, X)
    else
        throw(DomainError(b, "Unknown bleaching policy"))
    end
end

# Prediction with linear search for the bleaching threshold
# TODO: This could simply be called predict_response with a b=:linear argument
function predict_response_linear_bleaching(model::MultiDiscriminatorClassifier{C}, X::AbstractVector{Bool}) where {C}
    b = zero(Int)
    responses = predict_response(model, X, b)

    while true
        response_values = collect(values(responses))

        if all(isequal(0), response_values) || !response_tie(response_values)
            return responses
        end

        b += 1
        responses = predict_response(model, X, b)
    end
end

function predict_response_linear_bleaching(model::MultiDiscriminatorClassifier{C}, X::AbstractMatrix{Bool}) where {C}
    response = Dict(k => Int[] for k in keys(model.discriminators))

    mergewith!(push!, response, collect(predict_response_linear_bleaching(model, x) for x in eachrow(X))...)
end

function predict(model::MultiDiscriminatorClassifier{C}, X::AbstractVecOrMat{Bool}, b::Symbol) where {C}
    if b == :none
        return predict(model, X, 0)
    elseif b == :linear
        return predict_bleached(model, X)
    else
        throw(DomainError(b, "Unknown bleaching policy"))
    end
end

function predict_bleached(model::MultiDiscriminatorClassifier{C}, X::AbstractVector{Bool}) where {C}
    responses = predict_response_linear_bleaching(model, X)

    if all(isequal(0), values(responses))
        return rand(keys(responses))
    else
        return argmax(responses)
    end
end

# This is identical to other matrix-predicts. Maybe this could already be provided by the model interface
function predict_bleached(model::MultiDiscriminatorClassifier{C,BleachingDiscriminator}, X::AbstractMatrix{Bool}) where {C}
    return C[predict_bleached(model, row) for row in eachrow(X)]
end


import MathProgBase
export can_solve_mip, can_solve_socp, can_solve_sdp, can_solve_exp

# NOTE: Convex has been tested with the following solvers:
#   ECOS:   ECOSSolver
#   SCS:    SCSSolver
#   Gurobi: GurobiSolver
#   Mosek:  MosekSolver
#   GLPKMathProgInterface: GLPKSolverMIP
# It has not been tested with other solvers such a CPLEX.

function can_solve_mip(solver)
    name = typeof(solver).name.name
    if name == :GurobiSolver || name == :MosekSolver || name == :GLPKSolverMIP || name == :CPLEXSolver || name == :CbcSolver
        return true
    else
        @info "$name cannot solve mixed integer programs. Consider using Gurobi, Mosek, or GLPK."
        return false
    end
end

function can_solve_socp(solver)
    if :SOC in MathProgBase.supportedcones(solver)
        return true
    else
        name = typeof(solver).name.name
        @info "$name cannot solve second order cone programs. Consider using SCS, ECOS, Mosek, or Gurobi."
        return false
    end
end

function can_solve_exp(solver)
    if :ExpPrimal in MathProgBase.supportedcones(solver)
        return true
    else
        name = typeof(solver).name.name
        @info "$name cannot solve exponential programs. Consider using SCS or ECOS."
        return false
    end
end

function can_solve_sdp(solver)
    if :SDP in MathProgBase.supportedcones(solver)
        return true
    else
        name = typeof(solver).name.name
        @info "$name cannot solve semidefinite programs. Consider using SCS or Mosek."
        return false
    end
end


@testset "Graph size queries" begin

    # Helper methods
    nt(name) = named(name)
    tf(name) = t -> nt(name)(t)
    iv(size, name="in") = inputvertex(name, size)
    tv(in, name) = invariantvertex(identity, in, traitdecoration=tf(name))
    av(in, outsize, name) = absorbvertex(IndMem(MatMul(nout(in), outsize)), in, traitdecoration=tf(name))
    cc(ins...; name) = conc(ins...; dims=2, traitdecoration=tf(name), outwrap=x -> IndMem(x, collect(nout.(ins)), sum(nout, ins)))

    nc(name) = traitconf(nt(name))

    @testset "all_in_Δsize_graph" begin
        using NaiveNASlib: TightΔSizeGraph, LooseΔSizeGraph, all_in_Δsize_graph, Input, Output

        @testset "Concat and elemwise not connected" begin
            v1 = iv(3, "v1")
            v2a = av(v1, 2, "v2a")
            v2b = av(v1, 4, "v2b")
            v3 = cc(v2a, v2b; name="v3")
            v4a = av(v3, 2, "v4a")
            v4b = av(v3, 2, "v4b")
            v5 = "v5" >> v4a + v4b
            v6 = av(v5, 3, "v6")

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v2a, Output())) == ["v2a", "v3", "v2b", "v4a", "v4b"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v2a, Output())) == ["v2a", "v3", "v4a", "v4b"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v2a, Input())) == ["v2a", "v1"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v2a, Input())) == ["v2a", "v1"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v3, Output())) == ["v3", "v2a", "v2b", "v4a", "v4b"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v3, Output())) == ["v3", "v2a", "v4a", "v4b", "v2b"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v3, Input())) == ["v3", "v2a", "v2b", "v4a", "v4b"]
            # When we start from v3, at least one of v2a and v2b must change, and we decide to add both in the set, perhaps we should just pick one?
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v3, Input())) == ["v3", "v2a", "v2b", "v4a", "v4b"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v4a, Output())) == ["v4a", "v5", "v4b", "v6"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v4a, Output())) == ["v4a", "v5", "v4b", "v6"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v4a, Input())) == ["v4a", "v3", "v2a", "v2b", "v4b"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v4a, Input())) == ["v4a", "v3", "v2a", "v4b", "v2b"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v5, Output())) == ["v5", "v4a", "v4b", "v6"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v5, Output())) == ["v5", "v4a", "v4b", "v6"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v5, Input())) == ["v5", "v4a", "v4b", "v6"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v5, Input())) == ["v5", "v4a", "v4b", "v6"]
        end

        @testset "Concat and elemwise connected" begin
            v1 = iv(6, "v1")
            v2 = av(v1, 2, "v2")
            v3a = av(v2, 2, "v3a")
            v3b = av(v2, 4, "v3b")
            v4 = cc(v3a, v3b; name="v4")
            v5 = "v5" >> v4 + v1
            v6 = av(v5, 3, "v6")

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v3a, Output())) == ["v3a", "v4", "v3b", "v5", "v1", "v6"]
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v3a, Output())) == ["v3a", "v4", "v5", "v1", "v6"]

            @test name.(all_in_Δsize_graph(LooseΔSizeGraph(), v4, Output())) == ["v4", "v3a", "v3b", "v5", "v1", "v6"]
            # When we start from v4, at least one of v3a and v3b must change, and we decide to add both in the set, perhaps we should just pick one?
            @test name.(all_in_Δsize_graph(TightΔSizeGraph(), v4, Output())) == ["v4", "v3a", "v5", "v1", "v6", "v3b"]
        end
    end

    @testset "SizeCycleDetector" begin
        using NaiveNASlib: isinsizecycle

        @testset "Simple cycle$label" for (label, inspre, inspost) in (
            ("", identity, identity),
            (" transparent before", v -> tv(v, "tvpre"), identity),
            (" transparent after", identity, v -> tv(v, "tvpost")),
            (" transparent before and after", v -> tv(v, "tvpre"),v -> tv(v, "tvpost")),
            (" concat before", v -> cc(v; name="ccpre"), identity),
            (" concat after", identity, v -> cc(v;name="ccpost")),
            (" concat before and after", v -> cc(v; name="ccpre"), v -> cc(v;name="ccpost")),
        )
            # Canonical size cycle: if we remove v3b we get the constraint that nout(v2) == nin(v5) == nout(v4) == 2 * nout(v2)
            # Also, if we remove v2 then v5 sees v1 through v4 too and we get the impossible size constraint
            # that nout(v1) == nin(v5) == nout(v4) == nout(v1) + nout(v3b) (where nout(v) > 0 ∀ v).
            v1 = iv(6, "v1")
            v2 = av(v1, 2, "v2")
            v3a = tv(v2, "v3a")
            v3b = av(inspre(v2), 4, "v3b")
            v4 = cc(v3a, inspost(v3b); name="v4")
            v5 = "v5" >> v4 + v1
            v6 = av(v5, 3, "v6")

            @testset "Vertex $(name(v))" for v in all_in_graph(v1)
                if v === v3b || v === v2
                    @test isinsizecycle(v) == true
                else
                    @test isinsizecycle(v) == false
                end
            end
        end

        @testset "Simple cycle with inputvertices" begin
            v1 = iv(6, "v1")
            v2 = iv(2, "v2")
            v3a = tv(v2, "v3a")
            v3b = av(v2, 4, "v3b")
            v4 = cc(v3a, v3b; name="v4")
            v5 = "v5" >> v4 + v1
            v6 = av(v5, 3, "v6")

            @testset "Vertex $(name(v))" for v in all_in_graph(v1)
                if v === v3b # v2 is not in sizecycle as it does not have ancestors
                    @test isinsizecycle(v) == true
                else
                    @test isinsizecycle(v) == false
                end
            end
        end

        @testset "Double absorbing in path -> no cycle" begin
            # Same as above, but now there are two size absorbing vertices in the non-transparent path
            # v3b1 does not see v5 as v3b2 blocks the line of sight and v3b2 does not see v2 as v3b1 block the line of sight
            v1 = iv(6, "v1")
            v2 = av(v1, 2, "v2")
            v3a = tv(v2, "v3a")
            v3b1 = av(v2, 2, "v3b1")
            v3b2 = av(v3b1, 4, "v3b2")
            v4 = cc(v3a, v3b2; name="v4")
            v5 = "v5" >> v4 + v1
            v6 = av(v5, 3, "v6")

            @testset "Vertex $(name(v))" for v in all_in_graph(v1)
                if v === v2
                    @test isinsizecycle(v) == true
                else
                    @test isinsizecycle(v) == false
                end
            end
        end

        @testset "Independent concats" begin
            # Counterexample to Simple cycle. Not a size cycle as the two paths go through different concatenations
            v1 = iv(2, "v1")
            v2 = av(v1, 2, "v2")
            v3a = tv(v2, "v3a")
            v3b = av(v2, 2, "v3b")
            v4a = cc(v3a; name="v4a")
            v4b = cc(v3b; name="v4b")
            v5 = "v5" >> v4a + v4b
            v6 = av(v5, 3, "v6")

            @testset "Vertex $(name(v))" for v in all_in_graph(v1)
                @test isinsizecycle(v) == false
            end
        end

        @testset "Concat and elemwise connected no cycle" begin
            v1 = iv(6, "v1")
            v2 = av(v1, 2, "v2")
            v3a = av(v2, 2, "v3a")
            v3b = av(v2, 4, "v3b")
            v4 = cc(v3a, v3b; name="v4")
            v5 = "v5" >> v4 + v1
            v6 = av(v5, 3, "v6")

            @testset "Vertex $(name(v))" for v in all_in_graph(v1)
                @test isinsizecycle(v) == false
            end

            @test remove!(v3a) == true
            @test isinsizecycle(v3b) == true
        end

        @testset "Concat and elemwise connected in cycle" begin
            v1 = iv(2, "v1")
            v2 = av(v1, 6, "v2")
            v3a = av(v2, 2, "v3a")
            v3b1 = av(v2, 3, "v3b1")
            v3b2 = av(v3b1, 4, "v3b2")
            v4 = cc(v3a, v3b2; name="v4")
            v5 = "v5" >> v4 + v2
            v6 = av(v5, 3, "v6")

            @testset "Vertex $(name(v))" for v in all_in_graph(v1)
                if v === v3a
                    @test isinsizecycle(v) == true
                else
                    @test isinsizecycle(v) == false
                end
            end
        end

        @testset "Size cycle when adding edge" begin
            v1 = av(iv(3, "in"), 3, "v1")
            v2 = av(v1, 5, "v2")
            v3 = tv(v2, "v3")
            ve = tv(v1, "ve")
            v4 = cc(v3; name="v4")
            v5 = "v5" >> v4 + v2

            @testset "Vertex $(name(v))" for v in all_in_graph(v1)
                @test isinsizecycle(v) == false
            end

            # Note: this is basically the default when adding edges to SizeStack
            # We first add it, and then we try to align the sizes
            # In this case alignment becomes impossible due to the created size cycle
            create_edge!(ve, v4; strategy=NoSizeChange())

            @testset "Vertex $(name(v)) after edge" for v in all_in_graph(v1)
                if v === v2 || v === v3
                    @test isinsizecycle(v) == true
                else
                    @test isinsizecycle(v) == false
                end
            end
        end
    end
end


function AnywhereLevelTwo()
    x = trace(:x, Normal(0.0, 1.0))
    trace(:loop, AnywhereInLoop)
    return x
end

function AnywhereLevelOne()
    x = trace(:x, Normal(0.0, 1.0))
    y = trace(:y, AnywhereLevelTwo)
    return x
end

function AnywhereTopLevel()
    x = trace(:x, Normal(0.0, 1.0))
    y = trace(:y, AnywhereLevelOne)
    return x
end

function AnywhereInLoop()
    x = trace(:q, Normal(0.0, 1.0))
    for i in 1:50
        z = trace(:q => i, Normal(0.0, 1.0))
    end
end

@testset "Constrained targets" begin

    @testset "Constrained by address" begin
        sel = target([(:x, ) => 5.0,
                      (:y, :q, :z) => 10.0])
        @test sel == sel
        @test haskey(sel, (:x, ))
        @test haskey(sel, :x)
        @test haskey(sel, (:y, :q, :z))
        @test sel[:y, :q, :z] == 10.0

        sel2 = target([(:z, :m) => 15.0])
        merge!(sel, sel2)
        @test sel == target([(:x, ) => 5.0,
                             (:m, :q, :z) => 10.0,
                             (:z, :m) => 15.0])
        arr = array(sel, Float64)
        @test arr == [5.0, 10.0, 15.0]
        back = target(sel, arr)
        @test back == sel
    end

    @testset "Unconstrained by address" begin
        sel = target([(:x, ), (:x, :q, :z)])
        @test sel == sel
        @test haskey(sel, (:x, ))
        @test haskey(sel, :x)
        @test haskey(sel, (:x, :q, :z))

        sel2 = target([(:z, :m)])
        merge!(sel, sel2)
        @test sel == target([(:x, ), (:x, :q, :z), (:z, :m)])
    end

    #@testset "Filtering" begin
    #    observations = target([(:x, ) => 5.0,
    #                           (:z, :x) => 5.0,
    #                           (:z, :z, :y) => 5.0])
    #    filtered = filter(x -> x == :y, x -> true, observations)
    #    @test has_top(filtered, (:z, :z, :y))
    #    filtered = filter(x -> x == :x, x -> true, observations)
    #    @test !has_top(filtered, (:z, :z, :y))
    #    @test has_top(filtered, :x)
    #    @test has_top(filtered, (:z, :x))
    #end
end


module Environment
export Bandit, step

struct Bandit
    p::Array{Real, 1}
end

function step(env::Bandit, action::Int64)
    k = length(env.p)
    rewards = env.p .>= rand(k)
    return rewards[action]
end

end#module


MOI.get(model::Optimizer, ::MOI.NumberOfVariables) = length(model.inner.variable_info)
MOI.get(model::Optimizer, ::MOI.ListOfVariableIndices) = VI.(1 : length(model.inner.variable_info))

function MOIU.allocate_variables(model::Optimizer, nvars::Integer)
    variable_info = model.inner.variable_info
    resize!(variable_info, nvars)
    @inbounds for i = 1 : nvars
        variable_info[i] = VariableInfo()
    end
    return VI.(1 : nvars)
end

MOIU.load_variables(model::Optimizer, nvars::Integer) = nothing

MOI.supports(model::Optimizer, ::MOI.VariableName, ::Type{VI}) = true

function MOIU.load(model::Optimizer, attr::MOI.VariableName, vi::VI, value)
    check_variable_indices(model, vi)
    model.inner.variable_info[vi.value].name = value
end

MOI.supports(::Optimizer, ::MOI.VariablePrimalStart, ::Type{VI}) = true

function MOIU.load(model::Optimizer, ::MOI.VariablePrimalStart, vi::VI, value::Union{Real, Nothing})
    check_variable_indices(model, vi)
    model.inner.variable_info[vi.value].start = value
    return
end


# Chapter 2.6 コンストラクタ
using CSV
using DataFrames
using Plots
using Measures

struct HousePriceData
    df
end

# コンストラクタ
function HousePriceData(path::AbstractString)
    df = CSV.read(path, DataFrame)
    HousePriceData(df)
end

# 建物の等級(MSSubClass)を限定した散布図(横軸GrLibArea, 縦軸SalePrice)をプロットするメソッド
# titles: タイトル(グラフ数のリスト)
# levels: 建物の東急(グラフ数のリスト)
function plot_scatter(data::HousePriceData, titles, levels)
    xrange = extrema(data.df[!, :GrLivArea])
    yrange = extrema(data.df[!, :SalePrice])

    plts = []
    for (title, lv) in zip(titles, levels)
        df_plot = data.df[data.df.MSSubClass .== lv, :]
        fig = scatter(df_plot[!, :GrLivArea],
                      df_plot[!, :SalePrice],
                      title = title,
                      xlims = xrange,
                      ylims = yrange,
                      label = "")
        xlabel!("GrLivArea")
        ylabel!("SalePrice")
        push!(plts, fig)
    end
    plot(plts..., layout = (:, 2), size = [600, 600], margin = 5mm)
end


using WAV
using SpeechAugment: speedWav

# speed parameter
maxspeed = 1.2
minspeed = 0.8
# read a wav file
wav = wavread("xxx.wav")[1]
# speedup speech by speed parameter
y = speedWav(wav, rand()*(maxspeed-minspeed) + minspeed)


using BinaryBuilder

name = "lapackpp"
version = v"2021.04.00"

# Collection of sources required to build PETSc. Avoid using the git repository, it will
# require building SOWING which fails in all non-linux platforms.
sources = [
    GitSource("https://bitbucket.org/icl/lapackpp.git", "31d969200a9f65390f56ac2ea48888bd10a13397")
]

# Bash recipe for building across all platforms

# Needs to add -Dcapi eventually once it's added to the cmake build system. Note yet available under CMAKAE toolchain.
script = raw"""
cd lapackpp
mkdir build && cd build
cmake \
  -DCMAKE_INSTALL_PREFIX=${prefix} \
  -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TARGET_TOOLCHAIN} \
  -DCMAKE_BUILD_TYPE="Release" \
  -Drun_result="0" \
  -Drun_result__TRYRUN_OUTPUT="ok" \
  -Dbuild_tests=no \
  -Duse_cmake_find_lapack=yes \
  ..
make -j${nproc}
make install
"""

# We attempt to build for all defined platforms
platforms = expand_cxxstring_abis(supported_platforms())
products = [
    LibraryProduct("liblapackpp", :liblapackpp)
]

dependencies = [
    Dependency("CompilerSupportLibraries_jll"),
    Dependency("OpenBLAS32_jll"),
    Dependency("blaspp_jll"; compat="2021.04.02")
]
# Build the tarballs.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; julia_compat="1.6", preferred_gcc_version=v"7")


for ( julia_fun, ippf_prefix )  in  [ (   :"insert julia function name",  "ippsFunctionBaseName"  ) ]
    for ( "TypeSignatures" ) in "AnArrayOfTuples"

        julia_fun! = symbol(string(julia_fun, '!')) # in-place version if it makes sense

        @eval begin
            function $(julia_fun!)( buffer, "julia function arguments" )
                sigLen = length( signal )
                outLen = length( buffer )
                @ippscall( $ippfsr,  (  "the c function argument types" ),
                                        "julia varables to pass to the c function")
                buffer
            end # function

            $(julia_fun)( "julia function arguments" ) = $(julia_fun!)( "create a buffer", "julia function arguments" )

        end # eval
    end # type loop
end # function loop


module SimulateMotif

######################### Dependencies #########################
using Distributions
################################################################

################# Exported methods and types ###################
export single_part_motif,
       k_parts_motif,
       gapped_k_parts_motif,
       mixture_k_parts_motifs,
       mixture_gapped_k_parts_motifs,
       read_fasta_that_has_ground_truth,
       DNAdataset
################################################################

###################### Load files ##############################
include("types.jl")
include("helpers.jl")
include("sample.jl")
include("DNAdataset.jl")

end


using Combinatorics
open("load_combos.sh", "w") do f
    for (a,b,c,d) in permutations(ARGS)
        write(f, "julia --project -e 'using $a; using $b; using $c; using $d' && \\\n")
    end
    write(f, "echo -e \\\\nDone!\n")
end


isdefined(:__precompile__) && __precompile__(true)
module InplaceOps

include("common.jl")
include("inplace_math.jl")

export @in1!, @in2!, @into!

inplace_sym(s::Symbol) = inplace_sym(Operator{s}())

inplace_sym{S}(::Operator{S}) = error("invalid operator")

inplace_sym(::Operator{:+}) = :add!
inplace_sym(::Operator{:-}) = :sub!
inplace_sym(::Operator{:*}) = :mul!
inplace_sym(::Operator{:\}) = :ldiv!
inplace_sym(::Operator{:/}) = :rdiv!

inplace_sym(::Operator{:.+}) = :badd!
inplace_sym(::Operator{:.-}) = :bsub!
inplace_sym(::Operator{:.*}) = :bmul!
inplace_sym(::Operator{:.\}) = :bldiv!
inplace_sym(::Operator{:./}) = :brdiv!

op_ctranspose{T}(x::AbstractArray{T}) = CTranspose(x)
op_ctranspose{T<:Real}(x::AbstractArray{T}) = Transpose(x)

op_transpose{T}(x::AbstractArray{T}) = Transpose(x)

typealias AbstractVMF Union{AbstractVecOrMat,Factorization}

#TODO: Most of the 2-argument A_foo_B methods overwrite B, though there are some exceptions (e.g. QRPackedQ)
mul!(::Type{Inplace{2}}, A::AbstractVMF, B::AbstractVMF) = A_mul_B!(A,B)
mul!(::Type{Inplace{2}}, A::Transpose  , B::AbstractVMF) = At_mul_B!(A.obj,B)
mul!(::Type{Inplace{2}}, A::CTranspose , B::AbstractVMF) = Ac_mul_B!(A.obj,B)

ldiv!(::Type{Inplace{2}}, A::AbstractVMF, B::AbstractVMF) = A_ldiv_B!(A,B)
ldiv!(::Type{Inplace{2}}, A::Transpose  , B::AbstractVMF) = At_ldiv_B!(A.obj,B)
ldiv!(::Type{Inplace{2}}, A::CTranspose , B::AbstractVMF) = Ac_ldiv_B!(A.obj,B)

rdiv!(::Type{Inplace{2}}, A::AbstractVMF, B::AbstractVMF) = A_rdiv_B!(A,B)
rdiv!(::Type{Inplace{2}}, A::Transpose  , B::AbstractVMF) = At_rdiv_B!(A.obj,B)
rdiv!(::Type{Inplace{2}}, A::CTranspose , B::AbstractVMF) = Ac_rdiv_B!(A.obj,B)

mul!(O::AbstractVMF, A::AbstractVMF, B::AbstractVMF) = A_mul_B!(O,A,B)
mul!(O::AbstractVMF, A::Transpose  , B::AbstractVMF) = At_mul_B!(O,A.obj,B)
mul!(O::AbstractVMF, A::CTranspose , B::AbstractVMF) = Ac_mul_B!(O,A.obj,B)
mul!(O::AbstractVMF, A::AbstractVMF, B::Transpose  ) = A_mul_Bt!(O,A,B.obj)
mul!(O::AbstractVMF, A::Transpose  , B::Transpose  ) = At_mul_Bt!(O,A.obj,B.obj)
mul!(O::AbstractVMF, A::CTranspose , B::Transpose  ) = Ac_mul_Bt!(O,A.obj,B.obj)
mul!(O::AbstractVMF, A::AbstractVMF, B::CTranspose ) = A_mul_Bc!(O,A,B.obj)
mul!(O::AbstractVMF, A::Transpose  , B::CTranspose ) = At_mul_Bc!(O,A.obj,B.obj)
mul!(O::AbstractVMF, A::CTranspose , B::CTranspose ) = Ac_mul_Bc!(O,A.obj,B.obj)

ldiv!(O::AbstractVMF, A::AbstractVMF, B::AbstractVMF) = A_ldiv_B!(O,A,B)
ldiv!(O::AbstractVMF, A::Transpose  , B::AbstractVMF) = At_ldiv_B!(O,A.obj,B)
ldiv!(O::AbstractVMF, A::CTranspose , B::AbstractVMF) = Ac_ldiv_B!(O,A.obj,B)
ldiv!(O::AbstractVMF, A::AbstractVMF, B::Transpose  ) = A_ldiv_Bt!(O,A,B.obj)
ldiv!(O::AbstractVMF, A::Transpose  , B::Transpose  ) = At_ldiv_Bt!(O,A.obj,B.obj)
ldiv!(O::AbstractVMF, A::CTranspose , B::Transpose  ) = Ac_ldiv_Bt!(O,A.obj,B.obj)
ldiv!(O::AbstractVMF, A::AbstractVMF, B::CTranspose ) = A_ldiv_Bc!(O,A,B.obj)
ldiv!(O::AbstractVMF, A::Transpose  , B::CTranspose ) = At_ldiv_Bc!(O,A.obj,B.obj)
ldiv!(O::AbstractVMF, A::CTranspose , B::CTranspose ) = Ac_ldiv_Bc!(O,A.obj,B.obj)

rdiv!(O::AbstractVMF, A::AbstractVMF, B::AbstractVMF) = A_rdiv_B!(O,A,B)
rdiv!(O::AbstractVMF, A::Transpose  , B::AbstractVMF) = At_rdiv_B!(O,A.obj,B)
rdiv!(O::AbstractVMF, A::CTranspose , B::AbstractVMF) = Ac_rdiv_B!(O,A.obj,B)
rdiv!(O::AbstractVMF, A::AbstractVMF, B::Transpose  ) = A_rdiv_Bt!(O,A,B.obj)
rdiv!(O::AbstractVMF, A::Transpose  , B::Transpose  ) = At_rdiv_Bt!(O,A.obj,B.obj)
rdiv!(O::AbstractVMF, A::CTranspose , B::Transpose  ) = Ac_rdiv_Bt!(O,A.obj,B.obj)
rdiv!(O::AbstractVMF, A::AbstractVMF, B::CTranspose ) = A_rdiv_Bc!(O,A,B.obj)
rdiv!(O::AbstractVMF, A::Transpose  , B::CTranspose ) = At_rdiv_Bc!(O,A.obj,B.obj)
rdiv!(O::AbstractVMF, A::CTranspose , B::CTranspose ) = Ac_rdiv_Bc!(O,A.obj,B.obj)

badd!{N}(::Type{Inplace{N}}, As...) = broadcast!(+,As[N],As...)
bsub!{N}(::Type{Inplace{N}}, As...) = broadcast!(-,As[N],As...)
bmul!{N}(::Type{Inplace{N}}, As...) = broadcast!(*,As[N],As...)
bldiv!{N}(::Type{Inplace{N}}, As...) = broadcast!(\,As[N],As...)
brdiv!{N}(::Type{Inplace{N}}, As...) = broadcast!(/,As[N],As...)
add!{N}(t::Type{Inplace{N}}, As...) = _add!(t, As...)
sub!{N}(t::Type{Inplace{N}}, As...) = _sub!(t, As...)

badd!(O::AbstractArray, As...) = broadcast!(+,O,As...)
bsub!(O::AbstractArray, As...) = broadcast!(-,O,As...)
bmul!(O::AbstractArray, As...) = broadcast!(*,O,As...)
bldiv!(O::AbstractArray, As...) = broadcast!(\,O,As...)
brdiv!(O::AbstractArray, As...) = broadcast!(/,O,As...)
add!(O::AbstractArray, As...) = _add!(O, As...)
sub!(O::AbstractArray, As...) = _sub!(O, As...)

replace_t(ex) = esc(ex)
function replace_t(ex::Expr)
    if ex.head == Symbol("'")
        :(op_ctranspose($(esc(ex.args[1]))))
    elseif ex.head == Symbol(".'")
        :(op_transpose($(esc(ex.args[1]))))
    else
        esc(ex)
    end
end

macro in1!(ex)
    ex.head == :call || error("incorrect macro usage")
    Expr(:call,inplace_sym(ex.args[1]),:(Inplace{1}),[replace_t(a) for a in ex.args[2:end]]...)
end

macro in2!(ex)
    ex.head == :call || error("incorrect macro usage")
    Expr(:call,inplace_sym(ex.args[1]),:(Inplace{2}),[replace_t(a) for a in ex.args[2:end]]...)
end

macro into!(ex)
    ex.head == :(=) || error("incorrect macro usage")
    out, ex = ex.args
    Expr(:call,inplace_sym(ex.args[1]),esc(out),[replace_t(a) for a in ex.args[2:end]]...)
end

end  # module


using VML, DataFrames

# add single result to dataframe
function addresult!(df::DataFrame, op, res)
    push!(df,[op, res[2], res[3], res[4]])
end

# wrap functions to avoid global scoping while testing
sqrt_jvectorized{T<:Number}(a::Vector{T}) =
    Base.sqrt(a)
sqrt_jbroadcast{T<:Number}(a::Vector{T}) =
    broadcast(sqrt,a)
sqrt_jbroadcast!{T<:Number}(dest::Vector{T}, a::Vector{T}) =
    broadcast!(sqrt,dest,a)
sqrt_jmap{T<:Number}(a::Vector{T}) =
    map!(sqrt,a)
sqrt_jmap!{T<:Number}(dest::Vector{T}, a::Vector{T}) =
    map!(sqrt,dest,a)
sqrt_jloop{T<:Number}(dest::Vector{T},a::Vector{T}) =
    @inbounds for i in eachindex(a) dest[i]=sqrt(a[i]) end

function sqrt_bench()
    # define vector size and floating precision
    n = 1_000
    T = Float64
    df = DataFrame(SQRT=[], CPU=Float64[], ALLOC=Int64[], GC=Float64[])

    # allocate vectors
    @timed dest  = ones(T,n)
    r1 = (@timed a = rand(T,n))

    # bench
    gc()
    gc_enable(false)
    addresult!(df, symbol("Allocation")        , r1)
    addresult!(df, symbol("Julia vectorized")  , @timed sqrt_jvectorized(a))
    addresult!(df, symbol("Julia broadcast")   , @timed sqrt_jbroadcast(a))
    addresult!(df, symbol("Julia broadcast!")  , @timed sqrt_jbroadcast!(dest,a))
    addresult!(df, symbol("Julia map")         , @timed sqrt_jmap(a))
    addresult!(df, symbol("Julia map!")        , @timed sqrt_jmap!(dest,a))
    addresult!(df, symbol("VML (allocation)")    , @timed VML.sqrt(a))
    addresult!(df, symbol("VML (in-place)")      , @timed VML.sqrt!(dest,a))
    gc_enable(true)

    n, df
end

n, df = sqrt_bench()

# println(df)
df[:CPU] *= 1e9/n
println(df)

path = Pkg.dir("MKL") * "/benchmark/final/"
writetable(path * "sqrt_memoryusage_data.csv", df, separator=',')


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     cell_metadata_filter: -all
#     formats: jl:light,ipynb
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.3.2
#   kernelspec:
#     display_name: Julia 1.1.0
#     language: julia
#     name: julia-1.1
# ---

import DrWatson: quickactivate
quickactivate(@__DIR__, "Chemostat_Rath2017")
Base.active_project() |> println

# +
using Distributed
using Serialization
using Dates

nworkers = 1 # length(Sys.cpu_info()) - 1
length(workers()) < nworkers && addprocs(nworkers; exeflags = "--project")
println("Working in: ", workers())
# -
@everywhere begin
    using Serialization
    using Distributed

    import Chemostat
    Ch = Chemostat
end

# ### Initializing everywhere

@everywhere begin

    const obj_ider = "biom";
    const cost_ider ="tot_cost"

    const ep_alpha = 1e9
    const ep_epsconv = 1e-9 # This is an exageration!!
    const ep_ochlen = 100

    const hr_b0_nsamples = 100_000 #10_000_000
    const hr_nsamples = 1000 # 10_000
    const intake_info = Dict("gt" => Dict("ub" => 100.0, "c" => 10.0));

    const ξs = Ch.Utils.logspace(-1,1,20);
    const ξs_str = round.(ξs, digits = 2);

    const β0 = 0.0 # It is important to include beta = 0 if doing HR
    const βs = [β0; Ch.Utils.logspace(-1, 3.8, 20)]
    const βs_str = round.(βs, digits = 2);
end

println("ep_alpha: ", ep_alpha)
println("ep_epsconv: ", ep_epsconv)
println("βs: ", βs_str)
println("ξs: ", ξs_str)

# RES IDS
# Collect all the computed results ids for bundling
const chnl = RemoteChannel() do
    Channel{Any}(10)
end
const res_ids = []
const collector = @async while true
    id = take!(chnl)
    push!(res_ids, id)
end;

@everywhere begin
    const base_model = Ch.Test.toy_model();
    function prepare_model(ξ)
        model = base_model |> deepcopy
        model = Ch.SteadyState.apply_bound!(model, ξ, intake_info |> deepcopy)
        model = Ch.LP.fva_preprocess(model; verbose = false)
    end
end

# cache dir
cache_dir = "cache"
!isdir(cache_dir) && mkdir(cache_dir)
@assert isdir(cache_dir)
Ch.Utils.set_cache_dir(cache_dir)

# ### EP

# SIMULATION
# Any of the loops can be parallelized by just
# changing one of the 'map' functions
map(ξs) do ξ

    # HASH SIM
    sim_hash = (:TOY_SIMULATION, ξ)

    # FBA EP
    dat = Ch.SimulationUtils.cached_simulation(;
        epochlen = ep_ochlen,
        verbose = true,
        sim_id = sim_hash,
        get_model = () -> prepare_model(ξ),
        objider = obj_ider,
        beta_info = [(obj_ider, βs)],
        costider = cost_ider,
        clear_cache = false,
        use_seed = true,
        epmodel_kwargs = Dict(:alpha => ep_alpha),
        epconv_kwargs = Dict(:epsconv => ep_epsconv)
    )

    ## HR
    model = prepare_model(ξ)
    hrout_β0 = nothing # seed
    Ch.Utils.tagprintln_inmw("DOING HR ")
    for (βi, β) in enumerate(βs)

        Ch.Utils.tagprintln_inmw("DOING BETA ", βi)
        if β == β0 # hrout seed
                hrout_β0 = Ch.MaxEntHR.maxent_hr(model,
                            nsamples = hr_b0_nsamples,
                            drop_samples = false, verbose = false);
            hrout = Ch.Utils.HRout(hrout_β0, drop_samples = true) # a copy without samples
        else
            isnothing(hrout_β0) && (error("The minimum β must be cero!!!"); return)

            hrout = Ch.MaxEntHR.maxent_hr(model, hrout_β0, obj_ider,
                β * 10.0^(-sqrt(ℯ)); # I have no idea why these work! TODO find it!
                nsamples = hr_nsamples,
                maxiter = 1e16,
                verbose = false
            )
        end
        dat[(:hr, βi)] = hrout
    end

    ## SAVING DATA
    res_id = (:RESULT, sim_hash)
    Ch.Utils.save_cache(res_id, (ξ, βs, model, dat);
        headline = "CATCHING RESULTS\n")

    ## PASSING ID TO MASTER
    put!(chnl, res_id)

    GC.gc()
    return nothing
end; # map(ξs) do ξ

for ξ in ξs
    m = prepare_model(ξ)
    Ch.LP.fba(m, obj_ider, cost_ider)
end

# ### Parallel loop

# COLLECTING RESULTS
Ch.Utils.tagprintln_inmw("COLLECTING RESULTS ")
sleep(1) # wait for collector to get all ids
const bundle = Ch.Utils.ChstatBundle()
for id in res_ids

    ξ_, βs_, model_, dat_ = Ch.Utils.load_cache(id; verbose = false)

    bundle[ξ_, :net] = model_
    bundle[ξ_, :fba] = dat_[:fba]

    for (βi, β_) in βs_ |> enumerate
        bundle[ξ_, β_, :ep] = dat_[(:ep, βi)]
        bundle[ξ_, β_, :hr] = dat_[(:hr, βi)]
    end
end

# ### Saving results

### Catching
cache_file = "toy_model_cache.jls"
serialize(cache_file, bundle)
println(relpath(cache_file), " created!!!")

# ### Deleting temporal caches

Ch.Utils.tagprintln_inmw("DELETING TEMPORAL CACHE ")
Ch.Utils.delete_temp_caches()


function default_pre(feas::Bool,X::Vector{Interval{T}},UBD::Float64,
                     k::Int64,pos::Int64,opt,LBDn::Float64,UBDn::Float64,x::BnBSolver,
                     y::BnBModel{Interval{T}}) where {T<:AbstractFloat}
   return feas,X
end

function default_pre(feas::Bool,X::Vector{MCInterval{T}},UBD::Float64,
                     k::Int64,pos::Int64,opt,LBDn::Float64,UBDn::Float64,x::BnBSolver,
                     y::BnBModel{MCInterval{T}}) where {T<:AbstractFloat}
   return feas,X
end

function default_post(feas::Bool,X::Vector{Interval{T}},k::Int64,
                     pos::Int64,opt,tempL,tempU,LBD,UBD) where {T<:AbstractFloat}
    return feas,X
end

function default_post(feas::Bool,X::Vector{MCInterval{T}},k::Int64,
                     pos::Int64,opt,tempL,tempU,LBD,UBD) where {T<:AbstractFloat}
    return feas,X
end


using ColorTypes
using ColorTypes.FixedPointNumbers
using ColorTypes.Random
using Test

# dummy type
struct C5{T} <: Color{T,5}
    c1::T; c2::T; c3::T; c4::T; c5::T
end

@testset "rand" begin
    function all_in_range(c::C) where {C}
        all(((f, gmin, gmax),) -> gmin <= getfield(c, f) <= gmax,
            zip(ColorTypes.colorfields(C), gamutmin(C), gamutmax(C)))
    end
    eltypes = (N0f8, N2f6, N0f16, N2f14, N0f32, N2f30, Float16, Float32, Float64)
    @testset "rand($C)" for C in (
            (Gray{T} for T in eltypes)...,
            (RGB{T} for T in eltypes)...,
            AGray{Float32}, GrayA{Float64},
            ARGB{Float32}, RGBA{N0f16}, XRGB{N0f8}, RGBX{Float64},
            BGR{Float16}, ABGR{N0f32}, BGRA{N2f14},
            HSV{Float32}, HSL{Float64}, ALab{Float32}, LCHabA{Float16},
            Gray, AGray, GrayA,
            RGB, ARGB, RGBA, BGR, ABGR, BGRA, XRGB, RGBX,
            HSV, HSL, Lab, LCHab, YIQ,
            AHSV, HSLA)
        if C <: Transparent3 && !(C <: TransparentRGB)
            @test_broken rand(C)
            continue
        end
        CC = isconcretetype(C) ? C : C{Float64}
        c = rand(C)
        @test c isa CC
        @test all_in_range(c)
        a = rand(C, (3, 5))
        @test a isa Array{CC,2}
        @test eltype(a) === CC
        @test size(a) == (3, 5)
        @test all(all_in_range, a)
        ap = a'
        @test ap[1, 1] == a[1, 1]
    end
    @testset "rand($C)" for C in (Gray24, AGray32)
        c = rand(C)
        b = c.color
        @test b&0xff == (b>>8)&0xff == (b>>16)&0xff
        @test c isa C
        a = rand(C, 3, 5)
        for el in a
            b = el.color
            @test b&0xff == (b>>8)&0xff == (b>>16)&0xff
        end
        @test eltype(a) === C
        @test size(a) == (3, 5)
    end
    @test_broken rand(RGB24)
    @test_broken rand(ARGB32)
    @test_broken rand(MersenneTwister(), RGB{N0f8})
    @test_broken rand!(Gray{Float32}[0.0, 0.1])
    @test_broken all(all_in_range, rand(ARGB{Q3f12}, 1))
    @test_broken all_in_range(LCHab(50, 10, 359))
    @test_broken all_in_range(YIQ(0.5, 0.59, 0.0))
    @test_broken !all_in_range(YIQ(0.5, 0.0, -0.53))
end

@testset "mapc" begin
    @test @inferred(mapc(sqrt, Gray{N0f8}(0.04))) === Gray(sqrt(N0f8(0.04)))
    @test @inferred(mapc(sqrt, AGray{N0f8}(0.04, 0.4))) === AGray(sqrt(N0f8(0.04)), sqrt(N0f8(0.4)))
    @test @inferred(mapc(sqrt, GrayA{N0f8}(0.04, 0.4))) === GrayA(sqrt(N0f8(0.04)), sqrt(N0f8(0.4)))
    @test @inferred(mapc(x->2x, RGB{N0f8}(0.04,0.2,0.3))) === RGB(map(x->2*N0f8(x), (0.04,0.2,0.3))...)
    @test @inferred(mapc(sqrt, RGBA{N0f8}(0.04,0.2,0.3,0.7))) === RGBA(map(x->sqrt(N0f8(x)), (0.04,0.2,0.3,0.7))...)
    @test @inferred(mapc(x->1.5f0x, RGBA{N0f8}(0.04,0.2,0.3,0.4))) === RGBA(map(x->1.5f0*N0f8(x), (0.04,0.2,0.3,0.4))...)
    @test @inferred(mapc(sqrt, C5{N0f8}(0.04,0.2,0.3,0.7,0.1))) === C5(map(x->sqrt(N0f8(x)), (0.04,0.2,0.3,0.7,0.1))...)

    @test @inferred(mapc(max, Gray{N0f8}(0.2), Gray{N0f8}(0.3))) === Gray{N0f8}(0.3)
    @test @inferred(mapc(-, AGray{Float32}(0.3), AGray{Float32}(0.2))) === AGray{Float32}(0.3f0-0.2f0,0.0)
    @test @inferred(mapc(min, RGB{N0f8}(0.2,0.8,0.7), RGB{N0f8}(0.5,0.2,0.99))) === RGB{N0f8}(0.2,0.2,0.7)
    @test @inferred(mapc(+, RGBA{N0f8}(0.2,0.8,0.7,0.3), RGBA{Float32}(0.5,0.2,0.99,0.5))) === RGBA(0.5f0+N0f8(0.2),0.2f0+N0f8(0.8),0.99f0+N0f8(0.7),0.5f0+N0f8(0.3))
    @test @inferred(mapc(+, HSVA(0.1,0.8,0.3,0.5), HSVA(0.5,0.5,0.5,0.3))) === HSVA(0.1+0.5,0.8+0.5,0.3+0.5,0.5+0.3)
    @test @inferred(mapc(+, C5(0.1,0.8,0.3,0.5,0.2), C5(0.5,0.5,0.5,0.5,0.5))) === C5(0.1+0.5,0.8+0.5,0.3+0.5,0.5+0.5,0.2+0.5)

    @test_throws ArgumentError mapc(min, RGB{N0f8}(0.2,0.8,0.7), BGR{N0f8}(0.5,0.2,0.99))
    @test @inferred(mapc(abs, -2)) === 2
end

@testset "reducec" begin
    @test @inferred(reducec(+, 0.0, Gray(0.3))) === 0.3
    @test @inferred(reducec(+, 1.0, Gray(0.3))) === 1.3
    @test @inferred(reducec(+, 0, Gray(0.3))) === 0.3
    @test @inferred(reducec(+, 0.0, AGray(0.3, 0.8))) === 0.3 + 0.8
    @test @inferred(reducec(+, 0.0, RGB(0.3, 0.8, 0.5))) === (0.3 + 0.8) + 0.5
    @test @inferred(reducec(+, 0.0, RGBA(0.3, 0.8, 0.5, 0.7))) === ((0.3 + 0.8) + 0.5) + 0.7
    @test @inferred(reducec(+, 0.0, C5(0.3, 0.8, 0.5, 0.7, 0.2))) === (((0.3 + 0.8) + 0.5) + 0.7) + 0.2
    @test @inferred(reducec(&, true, Gray(true)))
    @test !(@inferred(reducec(&, false, Gray(true))))
    @test !(@inferred(reducec(&, true, Gray(false))))

    @test @inferred(reducec(+, 0.0, 0.3)) === 0.3
    @test @inferred(reducec(+, 0, 0.3)) === 0.3
    @test @inferred(reducec(&, true, true))
    @test !(@inferred(reducec(&, false, true)))
    @test !(@inferred(reducec(&, true, false)))
end

@testset "mapreducec" begin
    @test @inferred(mapreducec(x->x^2, +, 0.0, Gray(0.3))) === 0.3^2
    @test @inferred(mapreducec(x->x^2, +, 1.0, Gray(0.3))) === 1 + 0.3^2
    @test @inferred(mapreducec(x->x^2, +, 0, Gray(0.3))) === 0.3^2
    @test @inferred(mapreducec(x->x^2, +, 0.0, AGray(0.3, 0.8))) === 0.3^2 + 0.8^2
    @test @inferred(mapreducec(x->x^2, +, 0.0, RGB(0.3, 0.8, 0.5))) === (0.3^2 + 0.8^2) + 0.5^2
    @test @inferred(mapreducec(x->x^2, +, 0.0, RGBA(0.3, 0.8, 0.5, 0.7))) === ((0.3^2 + 0.8^2) + 0.5^2) + 0.7^2
    @test @inferred(mapreducec(x->x^2, +, 0.0, C5(0.3, 0.8, 0.5, 0.7, 0.2))) === (((0.3^2 + 0.8^2) + 0.5^2) + 0.7^2) + 0.2^2

    @test !(@inferred(mapreducec(x->!x, &, true, Gray(true))))
    @test !(@inferred(mapreducec(x->!x, &, false, Gray(true))))
    @test @inferred(mapreducec(x->!x, &, true, Gray(false)))
    @test !@inferred(mapreducec(x->!x, &, false, Gray(false)))

    @test @inferred(mapreducec(x->x^2, +, 0.0, 0.3)) === 0.3^2
    @test @inferred(mapreducec(x->x^2, +, 1.0, 0.3)) === 1 + 0.3^2
    @test @inferred(mapreducec(x->x^2, +, 0, 0.3)) === 0.3^2
    @test !(@inferred(mapreducec(x->!x, &, true, true)))
    @test !(@inferred(mapreducec(x->!x, &, false, true)))
    @test @inferred(mapreducec(x->!x, &, true, false))
    @test !@inferred(mapreducec(x->!x, &, false, false))
end


using IterableTables
using IterableTables: getiterator
using Phylo.API

"""
    getinternalnodes(t::AbstractTree)
Function to retrieve only the internal nodes from a tree, `t`, which does not
include tips or root.

"""
getinternalnodes(t::AbstractTree) = collect(nodenamefilter(isinternal, t))

"""
    droptips!(tree::AbstractTree{OneTree}, tips)

Function to drop tips from a phylogenetic tree `tree`, which are found in
the vector of tips or tip names, `tips`.
"""
function droptips! end
@traitfn function droptips!(tree::T, tips::AbstractVector{N}) where
    {N, RT, T <: AbstractTree{OneTree, RT, N}; !MatchNodeType{T, N}}
    return droptips!(tree, [getnode(tree, tip) for tip in tips])
end

@traitfn function droptips!(tree::T, tips::AbstractVector{N}) where
    {N, RT, T <: AbstractTree{OneTree, RT}; MatchNodeType{T, N}}
    keep_tips = [tip for tip in getleaves(tree) if tip ∉ tips]
    tipnames = [getnodename(tree, tip) for tip in tips]

    # Remove nodes that are not in tip names
    for tip in tips
        _deletenode!(tree, tip)
    end

    # Remove nodes that are related to the cut tips
    while length(setdiff(collect(nodefilter(isleaf, tree)), keep_tips)) > 0
        nodes = setdiff(collect(nodefilter(isleaf, tree)), keep_tips)
        map(x -> deletenode!(tree, x), nodes)
    end

    # Merge internal nodes that no longer have multiple children
    while any(map(x -> length(getchildren(tree, x)) .< 2,
                  getinternalnodes(tree)))
        inner_nodes = getinternalnodes(tree)
        remove_nodes = findall(map(x->length(getchildren(tree, x)) .< 2,
                                   inner_nodes))
        for i in remove_nodes
            parent = getparent(tree, inner_nodes[i])
            parentbranch = getinbound(tree, inner_nodes[i])

            child = getchildren(tree, inner_nodes[i])[1]
            childbranch = getoutbounds(tree, getnode(tree, inner_nodes[i]))[1]

            len = distance(tree, parent, child)

            deletebranch!(tree, parentbranch)
            deletebranch!(tree, childbranch)
            deletenode!(tree, inner_nodes[i])

            createbranch!(tree, parent, child, len)
        end
    end

    # Remove root if it no longer has two branches
    root = first(nodefilter(isroot, tree))
    if length(getchildren(tree, root)) < 2
        deletenode!(tree, root)
    end

    if !isempty(getleafinfo(tree))
        li = leafinfotype(typeof(tree))(Iterators.
                                        filter(line -> line[1] ∉ tipnames,
                                               getiterator(getleafinfo(tree))))
        setleafinfo!(tree, li)
    end
    return tipnames
end

"""
    keeptips!(tree::AbstractTree{OneTree}, tips)

Function to keep only the tips in a phylogenetic tree, `tree`, that are found in
the vector of tips or tip names, `tips`.
"""
function keeptips! end
@traitfn function keeptips!(tree::T, tips::AbstractVector{N}) where
    {N, RT, T <: AbstractTree{OneTree, RT, N}; !MatchNodeType{T, N}}
    return keeptips!(tree, [getnode(tree, tip) for tip in tips])
end

@traitfn function keeptips!(tree::T, tips::AbstractVector{N}) where
    {N, RT, T <: AbstractTree{OneTree, RT}; MatchNodeType{T, N}}
    drop_tips = [tip for tip in getleaves(tree) if tip ∉ tips]
    return droptips!(tree, drop_tips)
end


using Test

@timed_testset "sequential/" begin
    include("sequential/generated_weights.jl")
    if Base.find_package("Gurobi") != nothing
        # Skip these tests if Gurobi is not installed.
        # The corresponding networks are too large for CBC to deal with.
        include("sequential/trained_weights.jl")
    end
end