# Test Core data accessors
@testset "Core Data Accessers" begin
    # Setup data
    m = InfiniteModel()
    ind_idx = IndependentParameterIndex(1)
    fin_idx = FiniteParameterIndex(1)
    set = IntervalSet(0, 1)
    supps_dict = SortedDict{Float64, Set{DataType}}(0. => Set{DataType}([UserDefined]))
    method = InfiniteOpt.DefaultDerivativeMethod
    ind_param = IndependentParameter(set, supps_dict, 5, method)
    fin_param = FiniteParameter(42)
    ind_object = ScalarParameterData(ind_param, 1, 1, "ind")
    fin_object = ScalarParameterData(fin_param, -1, -1, "fin")
    ind_pref = IndependentParameterRef(m, ind_idx)
    fin_pref = FiniteParameterRef(m, fin_idx)
    ind_gvref = GeneralVariableRef(m, 1, IndependentParameterIndex)
    fin_gvref = GeneralVariableRef(m, 1, FiniteParameterIndex)
    bad_ind_pref = IndependentParameterRef(m, IndependentParameterIndex(-1))
    bad_fin_pref = FiniteParameterRef(m, FiniteParameterIndex(-1))
    # test dispatch_variable_ref
    @testset "dispatch_variable_ref" begin
        @test dispatch_variable_ref(m, ind_idx) == ind_pref
        @test dispatch_variable_ref(ind_gvref) == ind_pref
        @test dispatch_variable_ref(m, fin_idx) == fin_pref
        @test dispatch_variable_ref(fin_gvref) == fin_pref
    end
    # test _add_data_object
    @testset "_add_data_object" begin
        @test InfiniteOpt._add_data_object(m, ind_object) == ind_idx
        @test InfiniteOpt._add_data_object(m, fin_object) == fin_idx
        @test InfiniteOpt._param_object_indices(m)[end] == ind_idx
    end
    # test _data_dictionary
    @testset "_data_dictionary" begin
        @test InfiniteOpt._data_dictionary(ind_pref) === m.independent_params
        @test InfiniteOpt._data_dictionary(ind_gvref) === m.independent_params
        @test InfiniteOpt._data_dictionary(fin_pref) === m.finite_params
        @test InfiniteOpt._data_dictionary(fin_gvref) === m.finite_params
        @test InfiniteOpt._data_dictionary(m, IndependentParameter) === m.independent_params
        @test InfiniteOpt._data_dictionary(m, FiniteParameter) === m.finite_params
    end
    # test _data_object
    @testset "_data_object" begin
        @test InfiniteOpt._data_object(ind_pref) == ind_object
        @test InfiniteOpt._data_object(ind_gvref) == ind_object
        @test InfiniteOpt._data_object(fin_pref) == fin_object
        @test InfiniteOpt._data_object(fin_gvref) == fin_object
        @test_throws ErrorException InfiniteOpt._data_object(bad_ind_pref)
        @test_throws ErrorException InfiniteOpt._data_object(bad_fin_pref)
    end
    # test _core_variable_object
    @testset "_core_variable_object" begin
        @test InfiniteOpt._core_variable_object(ind_pref) == ind_param
        @test InfiniteOpt._core_variable_object(ind_gvref) == ind_param
        @test InfiniteOpt._core_variable_object(fin_pref) == fin_param
        @test InfiniteOpt._core_variable_object(fin_gvref) == fin_param
    end
    # test _parameter_number
    @testset "_parameter_number" begin
        @test InfiniteOpt._parameter_number(ind_pref) == 1
        @test InfiniteOpt._parameter_number(ind_gvref) == 1
    end
    # test _parameter_numbers
    @testset "_parameter_numbers" begin
        @test InfiniteOpt._parameter_numbers(ind_pref) == [1]
        @test InfiniteOpt._parameter_numbers(ind_gvref) == [1]
    end
    # test _object_number
    @testset "_object_number" begin
        @test InfiniteOpt._object_number(ind_pref) == 1
        @test InfiniteOpt._object_number(ind_gvref) == 1
    end
    # test _object_numbers
    @testset "_object_numbers" begin
        @test InfiniteOpt._object_numbers(ind_pref) == [1]
        @test InfiniteOpt._object_numbers(ind_gvref) == [1]
    end
    # test _adaptive_data_update
    @testset "_adaptive_data_update" begin
        # test change of same type
        p = IndependentParameter(IntervalSet(0, 2), supps_dict, 12, method)
        data = InfiniteOpt._data_object(ind_pref)
        @test InfiniteOpt._adaptive_data_update(ind_pref, p, data) isa Nothing
        @test InfiniteOpt._core_variable_object(ind_pref) == p
        # test change of different types
        p = IndependentParameter(IntervalSet(0, 2), supps_dict, 12, TestMethod())
        data = InfiniteOpt._data_object(ind_pref)
        @test InfiniteOpt._adaptive_data_update(ind_pref, p, data) isa Nothing
        @test InfiniteOpt._core_variable_object(ind_pref) == p
    end
    # test _set_core_variable_object
    @testset "_set_core_variable_object" begin
        @test InfiniteOpt._set_core_variable_object(ind_pref, ind_param) isa Nothing
        @test InfiniteOpt._set_core_variable_object(fin_pref, fin_param) isa Nothing
    end
    # test _delete_data_object
    @testset "_delete_data_object" begin
        @test is_valid(m, ind_pref)
        @test is_valid(m, fin_pref)
        @test InfiniteOpt._delete_data_object(ind_pref) isa Nothing
        @test InfiniteOpt._delete_data_object(fin_pref) isa Nothing
        @test !is_valid(m, ind_pref)
        @test !is_valid(m, fin_pref)
    end
end

# Test macro methods
@testset "Macro Helpers" begin
    @testset "Symbol Methods" begin
        @test InfiniteOpt._is_set_keyword(:(lower_bound = 0))
    end
    # test ParameterInfoExpr datatype
    @testset "_ParameterInfoExpr" begin
        @test InfiniteOpt._ParameterInfoExpr isa DataType
        @test InfiniteOpt._ParameterInfoExpr(ones(Bool, 8)...).has_lb
        @test !InfiniteOpt._ParameterInfoExpr().has_lb
    end
    # test InfiniteOpt._set_lower_bound_or_error
    @testset "InfiniteOpt._set_lower_bound_or_error" begin
        info = InfiniteOpt._ParameterInfoExpr()
        # test normal operation
        @test isa(InfiniteOpt._set_lower_bound_or_error(error, info, 0), Nothing)
        @test info.has_lb && info.lower_bound == 0
        # test double/lack of input errors
        @test_throws ErrorException InfiniteOpt._set_lower_bound_or_error(error,
                                                                   info, 0)
        info.has_lb = false; info.has_dist = true
        @test_throws ErrorException InfiniteOpt._set_lower_bound_or_error(error,
                                                                   info, 0)
        info.has_dist = false; info.has_set = true
        @test_throws ErrorException InfiniteOpt._set_lower_bound_or_error(error,
                                                                   info, 0)
    end
    # test InfiniteOpt._set_upper_bound_or_error
    @testset "InfiniteOpt._set_upper_bound_or_error" begin
        info = InfiniteOpt._ParameterInfoExpr()
        # test normal operation
        @test isa(InfiniteOpt._set_upper_bound_or_error(error, info, 0), Nothing)
        @test info.has_ub && info.upper_bound == 0
        # test double/lack of input errors
        @test_throws ErrorException InfiniteOpt._set_upper_bound_or_error(error,
                                                                   info, 0)
        info.has_ub = false; info.has_dist = true
        @test_throws ErrorException InfiniteOpt._set_upper_bound_or_error(error,
                                                                   info, 0)
        info.has_dist = false; info.has_set = true
        @test_throws ErrorException InfiniteOpt._set_upper_bound_or_error(error,
                                                                   info, 0)
    end
    # _dist_or_error
    @testset "_dist_or_error" begin
        info = InfiniteOpt._ParameterInfoExpr()
        # test normal operation
        @test isa(InfiniteOpt._dist_or_error(error, info, 0), Nothing)
        @test info.has_dist && info.distribution == 0
        # test double/lack of input errors
        @test_throws ErrorException InfiniteOpt._dist_or_error(error, info, 0)
        info.has_dist = false; info.has_lb = true
        @test_throws ErrorException InfiniteOpt._dist_or_error(error, info, 0)
        info.has_lb = false; info.has_set = true
        @test_throws ErrorException InfiniteOpt._dist_or_error(error, info, 0)
    end
    # _set_or_error
    @testset "_set_or_error" begin
        info = InfiniteOpt._ParameterInfoExpr()
        # test normal operation
        @test isa(InfiniteOpt._set_or_error(error, info, 0), Nothing)
        @test info.has_set && info.set == 0
        # test double/lack of input errors
        @test_throws ErrorException InfiniteOpt._set_or_error(error, info, 0)
        info.has_set = false; info.has_lb = true
        @test_throws ErrorException InfiniteOpt._set_or_error(error, info, 0)
        info.has_lb = false; info.has_dist = true
        @test_throws ErrorException InfiniteOpt._set_or_error(error, info, 0)
    end
    # _constructor_set
    @testset "InfiniteOpt._constructor_set" begin
        info = InfiniteOpt._ParameterInfoExpr()
        @test_throws ErrorException InfiniteOpt._constructor_set(error, info)
        info.has_lb = true; info.lower_bound = 0
        @test_throws ErrorException InfiniteOpt._constructor_set(error, info)
        info.has_ub = true; info.upper_bound = 1
        check = :(isa($(info.lower_bound), Real))
        expected = :($(check) ? IntervalSet($(info.lower_bound), $(info.upper_bound)) : error("Bounds must be a number."))
        @test InfiniteOpt._constructor_set(error, info) == expected
        info = InfiniteOpt._ParameterInfoExpr(distribution = Normal())
        check = :(isa($(info.distribution), Distributions.UnivariateDistribution))
        expected = :($(check) ? UniDistributionSet($(info.distribution)) : error("Distribution must be a Distributions.UnivariateDistribution."))
        @test InfiniteOpt._constructor_set(error, info) == expected
        info = InfiniteOpt._ParameterInfoExpr(set = IntervalSet(0, 1))
        check1 = :(isa($(info.set), InfiniteScalarSet))
        check2 = :(isa($(info.set), Distributions.UnivariateDistribution))
        expected = :($(check1) ? $(info.set) : ($(check2) ? UniDistributionSet($(info.set)) : error("Set must be a subtype of InfiniteScalarSet.")))
        @test InfiniteOpt._constructor_set(error, info) == expected
    end

    # _parse_one_operator_parameter
    @testset "_parse_one_operator_parameter" begin
        info = InfiniteOpt._ParameterInfoExpr()
        @test isa(InfiniteOpt._parse_one_operator_parameter(error, info,
                                                          Val(:<=), 0), Nothing)
        @test info.has_ub && info.upper_bound == 0
        @test isa(InfiniteOpt._parse_one_operator_parameter(error, info,
                                                          Val(:>=), 0), Nothing)
        @test info.has_lb && info.lower_bound == 0
        info = InfiniteOpt._ParameterInfoExpr()
        @test isa(InfiniteOpt._parse_one_operator_parameter(error, info,
                                                          Val(:in), esc(0)), Nothing)
        @test info.has_set && info.set == esc(0)
        info = InfiniteOpt._ParameterInfoExpr()
        @test isa(InfiniteOpt._parse_one_operator_parameter(error, info,
                                                          Val(:in), esc(:([0, 1]))), Nothing)
        @test info.has_lb && info.has_ub
        @test_throws ErrorException InfiniteOpt._parse_one_operator_parameter(error, info,
                                                                              Val(:d), 0)
    end
    # _parse_ternary_parameter
    @testset "_parse_ternary_parameter" begin
        info = InfiniteOpt._ParameterInfoExpr()
        @test isa(InfiniteOpt._parse_ternary_parameter(error, info, Val(:<=), 0,
                                                       Val(:<=), 0), Nothing)
        @test info.has_ub && info.upper_bound == 0
        @test info.has_lb && info.lower_bound == 0
        info = InfiniteOpt._ParameterInfoExpr()
        @test isa(InfiniteOpt._parse_ternary_parameter(error, info, Val(:>=), 0,
                                                       Val(:>=), 0), Nothing)
        @test info.has_ub && info.upper_bound == 0
        @test info.has_lb && info.lower_bound == 0
        @test_throws ErrorException InfiniteOpt._parse_ternary_parameter(error,
                                                 info, Val(:<=), 0, Val(:>=), 0)
    end
    # _parse_parameter
    @testset "_parse_parameter" begin
        info = InfiniteOpt._ParameterInfoExpr()
        @test InfiniteOpt._parse_parameter(error, info, :<=, :x, 0) == :x
        @test info.has_ub && info.upper_bound == 0
        @test InfiniteOpt._parse_parameter(error, info, :<=, 0, :x) == :x
        @test info.has_lb && info.lower_bound == 0
        info = InfiniteOpt._ParameterInfoExpr()
        @test InfiniteOpt._parse_parameter(error, info, 0, :<=, :x, :<=, 0) == :x
        @test info.has_ub && info.upper_bound == 0
        @test info.has_lb && info.lower_bound == 0
    end
end

# Test parameter definition methods
@testset "Definition" begin
    # _check_supports_in_bounds
    @testset "_check_supports_in_bounds" begin
        set = IntervalSet(0, 1)
        @test isa(InfiniteOpt._check_supports_in_bounds(error, 0, set), Nothing)
        @test_throws ErrorException InfiniteOpt._check_supports_in_bounds(error,
                                                                        -1, set)
        @test_throws ErrorException InfiniteOpt._check_supports_in_bounds(error,
                                                                         2, set)
        set = UniDistributionSet(Uniform())
        @test isa(InfiniteOpt._check_supports_in_bounds(error, 0, set), Nothing)
        @test_throws ErrorException InfiniteOpt._check_supports_in_bounds(error,
                                                                        -1, set)
        @test_throws ErrorException InfiniteOpt._check_supports_in_bounds(error,
                                                                         2, set)
    end
    # build_independent_parameter
    @testset "build_parameter (IndependentParameter)" begin
        set = IntervalSet(0, 1)
        supps = 0.
        supps_dict = SortedDict{Float64, Set{DataType}}(0. => Set([UserDefined]))
        method = TestMethod()
        @test build_parameter(error, set, supports = supps).set == set
        @test build_parameter(error, set, supports = supps).supports == supps_dict
        @test_throws ErrorException build_parameter(error, set, bob = 42)
        warn = "Ignoring num_supports since supports is not empty."
        @test_logs (:warn, warn) build_parameter(error, set,
                                            supports = [0, 1], num_supports = 2)
        repeated_supps = [1, 1]
        expected = IndependentParameter(set, SortedDict{Float64, Set{DataType}}(1. => Set{DataType}()), 5, method)
        warn = "Support points are not unique, eliminating redundant points."
        @test_logs (:warn, warn) build_parameter(error, set, supports = repeated_supps,
                                                 derivative_method = method) == expected
        set = UniDistributionSet(Normal())
        @test length(build_parameter(error, set, num_supports = 5).supports) == 5
        @test build_parameter(error, set, derivative_method = method).derivative_method == method
    end
    # build_finite_parameter
    @testset "build_parameter (FiniteParameter)" begin
        @test_throws ErrorException build_parameter(error, 1, bob = 42)
        expected = FiniteParameter(1)
        @test build_parameter(error, 1) == expected
    end

    # add_parameter
    @testset "add_parameter" begin
        m = InfiniteModel()
        method = InfiniteOpt.DefaultDerivativeMethod
        param = IndependentParameter(IntervalSet(0, 1),
                                    SortedDict{Float64, Set{DataType}}(), 5, method)
        expected = GeneralVariableRef(m, 1, IndependentParameterIndex, -1)
        @test add_parameter(m, param) == expected
        @test InfiniteOpt._core_variable_object(expected) == param
        @test InfiniteOpt._param_object_indices(m)[InfiniteOpt._object_number(expected)] == index(expected)
        param = FiniteParameter(1.5)
        expected = GeneralVariableRef(m, 1, FiniteParameterIndex, -1)
        @test add_parameter(m, param) == expected
        @test InfiniteOpt._core_variable_object(expected) == param
    end
end

# Test Reference Queries
@testset "Basic Reference Queries" begin
    m = InfiniteModel()
    p = build_parameter(error, IntervalSet(0, 1), derivative_method = TestMethod())
    pref = add_parameter(m, p)
    dpref = dispatch_variable_ref(pref)
    # JuMP.index
    @testset "JuMP.index" begin
        @test JuMP.index(pref) == IndependentParameterIndex(1)
        @test JuMP.index(dpref) == IndependentParameterIndex(1)
    end
    # JuMP.owner_model
    @testset "JuMP.owner_model" begin
        @test owner_model(pref) === m
        @test owner_model(dpref) === m
    end
    # has_derivative_supports
    @testset "has_derivative_supports" begin
        @test !has_derivative_supports(pref)
        InfiniteOpt._data_object(pref).has_derivative_supports = true
        @test has_derivative_supports(dpref)
    end
    # _set_has_derivative_supports
    @testset "_set_has_derivative_supports" begin
        @test InfiniteOpt._set_has_derivative_supports(pref, true) isa Nothing
        @test has_derivative_supports(pref)
        @test InfiniteOpt._set_has_derivative_supports(dpref, false) isa Nothing
        @test !has_derivative_supports(pref)
    end
    # derivative_method
    @testset "derivative_method" begin
        @test derivative_method(pref) isa TestMethod
        @test derivative_method(dpref) isa TestMethod
    end
    # has_internal_supports
    @testset "has_internal_supports" begin
        @test !has_internal_supports(pref)
        InfiniteOpt._data_object(pref).has_internal_supports = true
        @test has_internal_supports(dpref)
    end
    # _set_has_internal_supports
    @testset "_set_has_internal_supports" begin
        @test InfiniteOpt._set_has_internal_supports(pref, true) isa Nothing
        @test has_internal_supports(pref)
        @test InfiniteOpt._set_has_internal_supports(dpref, false) isa Nothing
        @test !has_internal_supports(pref)
    end
    # has_derivative_constraints
    @testset "has_derivative_constraints" begin
        @test !has_derivative_constraints(pref)
        InfiniteOpt._data_object(pref).has_deriv_constrs = true
        @test has_derivative_constraints(dpref)
    end
    # _set_has_derivative_constraints
    @testset "_set_has_derivative_constraints" begin
        @test InfiniteOpt._set_has_derivative_constraints(pref, true) isa Nothing
        @test has_derivative_constraints(pref)
        @test InfiniteOpt._set_has_derivative_constraints(dpref, false) isa Nothing
        @test !has_derivative_constraints(pref)
    end
end

# Test name methods
@testset "Name" begin
    m = InfiniteModel()
    param = build_parameter(error, IntervalSet(0, 1))
    pref = add_parameter(m, param, "test")
    dpref = dispatch_variable_ref(pref)
    bad = TestVariableRef(m, TestIndex(-1))
    bad_pref = FiniteParameterRef(m, FiniteParameterIndex(-1))
    # JuMP.name
    @testset "JuMP.name" begin
        @test_throws ArgumentError name(bad)
        @test name(pref) == "test"
        @test name(dpref) == "test"
        @test name(bad_pref) == ""
    end
    # JuMP.set_name
    @testset "JuMP.set_name" begin
        @test_throws ArgumentError set_name(bad, "test")
        @test isa(set_name(pref, "new"), Nothing)
        @test name(pref) == "new"
        @test isa(set_name(dpref, "test"), Nothing)
        @test name(dpref) == "test"
    end
    # _make_parameter_ref
    @testset "_make_parameter_ref" begin
        @test InfiniteOpt._make_parameter_ref(m, IndependentParameterIndex(1)) == pref
    end
    # _param_name_dict
    @testset "_param_name_dict" begin
        @test isa(InfiniteOpt._param_name_dict(m), Nothing)
    end
    # _update_param_name_dict
    @testset "_update_param_name_dict" begin
        m.name_to_param = Dict{String, AbstractInfOptIndex}()
        @test InfiniteOpt._update_param_name_dict(m, m.independent_params) isa Nothing
        @test InfiniteOpt._update_param_name_dict(m, m.dependent_params) isa Nothing
        @test m.name_to_param["test"] == IndependentParameterIndex(1)
        m.name_to_param = nothing
    end
    # parameter_by_name
    @testset "parameter_by_name" begin
        @test parameter_by_name(m, "test") == pref
        @test isa(parameter_by_name(m, "test2"), Nothing)
        pref = add_parameter(m, param, "test")
        m.name_to_param = nothing
        @test_throws ErrorException parameter_by_name(m, "test")
    end
end

# Test the parameter macro
@testset "Macro" begin
    m = InfiniteModel()
    # single parameter
    @testset "Single" begin
        pref = GeneralVariableRef(m, 1, IndependentParameterIndex, -1)
        @test @independent_parameter(m, 0 <= a <= 1) == pref
        @test InfiniteOpt._core_variable_object(pref).set == IntervalSet(0, 1)
        @test name(pref) == "a"
        pref = GeneralVariableRef(m, 2, IndependentParameterIndex, -1)
        @test @independent_parameter(m, b in Normal(), supports = [1; 2]) == pref
        @test InfiniteOpt._core_variable_object(pref).set == UniDistributionSet(Normal())
        @test InfiniteOpt._core_variable_object(pref).supports == SortedDict(i => Set{DataType}([UserDefined]) for i in [1,2])
        pref = GeneralVariableRef(m, 3, IndependentParameterIndex, -1)
        @test @independent_parameter(m, c in IntervalSet(0, 1)) == pref
        @test InfiniteOpt._core_variable_object(pref).set == IntervalSet(0, 1)
        pref = GeneralVariableRef(m, 4, IndependentParameterIndex, -1)
        @test @independent_parameter(m, set = IntervalSet(0, 1),
                                  base_name = "d") == pref
        @test name(pref) == "d"
        pref = GeneralVariableRef(m, 5, IndependentParameterIndex, -1)
        @test @independent_parameter(m, set = IntervalSet(0, 1)) == pref
        @test name(pref) == ""
        pref = GeneralVariableRef(m, 6, IndependentParameterIndex, -1)
        @test @independent_parameter(m, z in [0, 1], derivative_method = TestMethod()) == pref
        @test InfiniteOpt._core_variable_object(pref).set == IntervalSet(0, 1)
        @test name(pref) == "z"
        @test derivative_method(pref) isa TestMethod
        @test InfiniteOpt._param_object_indices(m)[InfiniteOpt._object_number(pref)] == index(pref)
    end
    # multiple parameters
    @testset "Array" begin
        prefs = [GeneralVariableRef(m, 7, IndependentParameterIndex, -1),
                 GeneralVariableRef(m, 8, IndependentParameterIndex, -1)]
        @test @independent_parameter(m, 0 <= e[1:2] <= 1) == prefs
        @test InfiniteOpt._core_variable_object(prefs[1]).set == IntervalSet(0, 1)
        @test InfiniteOpt._core_variable_object(prefs[2]).set == IntervalSet(0, 1)
        prefs = [GeneralVariableRef(m, 9, IndependentParameterIndex, -1),
                 GeneralVariableRef(m, 10, IndependentParameterIndex, -1)]
        @test @independent_parameter(m, [1:2], set = IntervalSet(0, 1)) == prefs
        @test InfiniteOpt._core_variable_object(prefs[1]).set == IntervalSet(0, 1)
        @test InfiniteOpt._core_variable_object(prefs[2]).set == IntervalSet(0, 1)
        prefs = [GeneralVariableRef(m, 11, IndependentParameterIndex, -1),
                 GeneralVariableRef(m, 12, IndependentParameterIndex, -1)]
        sets = [IntervalSet(0, 1), IntervalSet(-1, 2)]
        @test @independent_parameter(m, f[i = 1:2], set = sets[i]) == prefs
        @test InfiniteOpt._core_variable_object(prefs[1]).set == IntervalSet(0, 1)
        @test InfiniteOpt._core_variable_object(prefs[2]).set == IntervalSet(-1, 2)
        prefs = [GeneralVariableRef(m, 13, IndependentParameterIndex, -1),
                 GeneralVariableRef(m, 14, IndependentParameterIndex, -1)]
        @test @independent_parameter(m, [i = 1:2], set = sets[i]) == prefs
        @test InfiniteOpt._core_variable_object(prefs[1]).set == IntervalSet(0, 1)
        @test InfiniteOpt._core_variable_object(prefs[2]).set == IntervalSet(-1, 2)
        prefs = [GeneralVariableRef(m, 15, IndependentParameterIndex, -1),
                 GeneralVariableRef(m, 16, IndependentParameterIndex, -1)]
        @test @independent_parameter(m, [0, -1][i] <= g[i = 1:2] <= [1, 2][i]) == prefs
        @test InfiniteOpt._core_variable_object(prefs[1]).set == IntervalSet(0, 1)
        @test InfiniteOpt._core_variable_object(prefs[2]).set == IntervalSet(-1, 2)
        prefs = [GeneralVariableRef(m, 17, IndependentParameterIndex, -1),
                 GeneralVariableRef(m, 18, IndependentParameterIndex, -1)]
        prefs = convert(JuMP.Containers.SparseAxisArray, prefs)
        @test @independent_parameter(m, 0 <= i[1:2] <= 1,
                                  container = SparseAxisArray) == prefs
        @test InfiniteOpt._core_variable_object(prefs[1]).set == IntervalSet(0, 1)
        @test InfiniteOpt._core_variable_object(prefs[2]).set == IntervalSet(0, 1)
        @test InfiniteOpt._param_object_indices(m)[InfiniteOpt._object_number(prefs[2])] == index(prefs[2])
    end
    # test for errors
    @testset "Errors" begin
        @test_macro_throws ErrorException @independent_parameter(m, 0 <= [1:2] <= 1)
        @test_macro_throws ErrorException @independent_parameter(m, 0 <= "bob" <= 1)
        @test_macro_throws ErrorException @independent_parameter(m, 0 <= a <= 1)
        @test_macro_throws ErrorException @independent_parameter(m, 0 <= j)
        @test_macro_throws ErrorException @independent_parameter(m, j)
        @test_macro_throws ErrorException @independent_parameter(m, j, foo = 42)
        @test_macro_throws ErrorException @independent_parameter(m, j in Multinomial(3, [1/3, 1/3]))
        @test_macro_throws ErrorException @independent_parameter(m, 0 <= k <= 1, Int)
    end
end

# Test if used
@testset "Used" begin
    m = InfiniteModel()
    @independent_parameter(m, pref1 in [0, 1])
    @finite_parameter(m, pref2, 1)
    dpref1 = dispatch_variable_ref(pref1)
    dpref2 = dispatch_variable_ref(pref2)
    bad_pref = IndependentParameterRef(m, IndependentParameterIndex(-1))
    # _infinite_variable_dependencies
    @testset "_infinite_variable_dependencies" begin
        @test InfiniteOpt._infinite_variable_dependencies(pref1) == InfiniteVariableIndex[]
        @test InfiniteOpt._infinite_variable_dependencies(dpref1) == InfiniteVariableIndex[]
        @test InfiniteOpt._infinite_variable_dependencies(pref2) == InfiniteVariableIndex[]
        @test InfiniteOpt._infinite_variable_dependencies(dpref2) == InfiniteVariableIndex[]
        @test_throws ErrorException InfiniteOpt._infinite_variable_dependencies(bad_pref)
    end
    # _parameter_function_dependencies
    @testset "_parameter_function_dependencies" begin
        @test InfiniteOpt._parameter_function_dependencies(pref1) == ParameterFunctionIndex[]
        @test InfiniteOpt._parameter_function_dependencies(dpref1) == ParameterFunctionIndex[]
        @test InfiniteOpt._parameter_function_dependencies(pref2) == ParameterFunctionIndex[]
        @test InfiniteOpt._parameter_function_dependencies(dpref2) == ParameterFunctionIndex[]
        @test_throws ErrorException InfiniteOpt._parameter_function_dependencies(bad_pref)
    end
    # _derivative_dependencies
    @testset "_derivative_dependencies" begin
        @test InfiniteOpt._derivative_dependencies(pref1) == DerivativeIndex[]
        @test InfiniteOpt._derivative_dependencies(dpref1) == DerivativeIndex[]
        @test InfiniteOpt._derivative_dependencies(pref2) == DerivativeIndex[]
        @test InfiniteOpt._derivative_dependencies(dpref2) == DerivativeIndex[]
        @test_throws ErrorException InfiniteOpt._derivative_dependencies(bad_pref)
    end
    # _measure_dependencies
    @testset "_measure_dependencies" begin
        @test InfiniteOpt._measure_dependencies(pref1) == MeasureIndex[]
        @test InfiniteOpt._measure_dependencies(dpref1) == MeasureIndex[]
        @test InfiniteOpt._measure_dependencies(pref2) == MeasureIndex[]
        @test InfiniteOpt._measure_dependencies(dpref2) == MeasureIndex[]
        @test_throws ErrorException InfiniteOpt._measure_dependencies(bad_pref)
    end
    # _constraint_dependencies
    @testset "_constraint_dependencies" begin
        @test InfiniteOpt._constraint_dependencies(pref1) == ConstraintIndex[]
        @test InfiniteOpt._constraint_dependencies(dpref1) == ConstraintIndex[]
        @test InfiniteOpt._constraint_dependencies(pref2) == ConstraintIndex[]
        @test InfiniteOpt._constraint_dependencies(dpref2) == ConstraintIndex[]
        @test_throws ErrorException InfiniteOpt._constraint_dependencies(bad_pref)
    end
    # used_by_constraint
    @testset "used_by_constraint" begin
        @test !used_by_constraint(pref1)
        @test !used_by_constraint(pref2)
        @test !used_by_constraint(dpref1)
        @test !used_by_constraint(dpref2)
        push!(InfiniteOpt._constraint_dependencies(dpref1), ConstraintIndex(1))
        push!(InfiniteOpt._constraint_dependencies(dpref2), ConstraintIndex(1))
        @test used_by_constraint(pref1)
        @test used_by_constraint(pref2)
        @test used_by_constraint(dpref1)
        @test used_by_constraint(dpref2)
        popfirst!(InfiniteOpt._constraint_dependencies(dpref1))
        popfirst!(InfiniteOpt._constraint_dependencies(dpref2))
    end
    # used_by_measure
    @testset "used_by_measure" begin
        @test !used_by_measure(pref1)
        @test !used_by_measure(pref2)
        @test !used_by_measure(dpref1)
        @test !used_by_measure(dpref2)
        push!(InfiniteOpt._measure_dependencies(dpref1), MeasureIndex(1))
        push!(InfiniteOpt._measure_dependencies(dpref2), MeasureIndex(1))
        @test used_by_measure(pref1)
        @test used_by_measure(pref2)
        @test used_by_measure(dpref1)
        @test used_by_measure(dpref2)
        popfirst!(InfiniteOpt._measure_dependencies(dpref1))
        popfirst!(InfiniteOpt._measure_dependencies(dpref2))
    end
    # used_by_infinite_variable
    @testset "used_by_infinite_variable" begin
        @test !used_by_infinite_variable(pref1)
        @test !used_by_infinite_variable(pref2)
        @test !used_by_infinite_variable(dpref1)
        @test !used_by_infinite_variable(dpref2)
        push!(InfiniteOpt._infinite_variable_dependencies(dpref1), InfiniteVariableIndex(1))
        @test used_by_infinite_variable(pref1)
        @test used_by_infinite_variable(dpref1)
        popfirst!(InfiniteOpt._infinite_variable_dependencies(dpref1))
    end
    # used_by_parameter_function
    @testset "used_by_parameter_function" begin
        @test !used_by_parameter_function(pref1)
        @test !used_by_parameter_function(pref2)
        @test !used_by_parameter_function(dpref1)
        @test !used_by_parameter_function(dpref2)
        push!(InfiniteOpt._parameter_function_dependencies(dpref1), ParameterFunctionIndex(1))
        @test used_by_parameter_function(pref1)
        @test used_by_parameter_function(dpref1)
        popfirst!(InfiniteOpt._parameter_function_dependencies(dpref1))
    end
    # used_by_derivative
    @testset "used_by_derivative" begin
        @test !used_by_derivative(pref1)
        @test !used_by_derivative(pref2)
        @test !used_by_derivative(dpref1)
        @test !used_by_derivative(dpref2)
        push!(InfiniteOpt._derivative_dependencies(dpref1), DerivativeIndex(1))
        @test used_by_derivative(pref1)
        @test used_by_derivative(dpref1)
        popfirst!(InfiniteOpt._derivative_dependencies(dpref1))
    end
    # used_by_objective
    @testset "used_by_objective" begin
        @test !used_by_objective(pref1)
        @test !used_by_objective(dpref1)
        @test !used_by_objective(pref2)
        @test !used_by_objective(dpref2)
        InfiniteOpt._data_object(pref2).in_objective = true
        @test used_by_objective(pref2)
        @test used_by_objective(dpref2)
        InfiniteOpt._data_object(pref2).in_objective = false
    end
    # is_used
    @testset "is_used" begin
        @test !is_used(pref1)
        @test !is_used(pref2)
        @test !is_used(dpref1)
        @test !is_used(dpref2)
        push!(InfiniteOpt._constraint_dependencies(dpref1), ConstraintIndex(1))
        push!(InfiniteOpt._constraint_dependencies(dpref2), ConstraintIndex(1))
        @test is_used(pref1)
        @test is_used(pref2)
        @test is_used(dpref1)
        @test is_used(dpref2)
        popfirst!(InfiniteOpt._constraint_dependencies(dpref1))
        popfirst!(InfiniteOpt._constraint_dependencies(dpref2))
        push!(InfiniteOpt._measure_dependencies(dpref1), MeasureIndex(1))
        push!(InfiniteOpt._measure_dependencies(dpref2), MeasureIndex(1))
        @test is_used(pref1)
        @test is_used(pref2)
        @test is_used(dpref1)
        @test is_used(dpref2)
        popfirst!(InfiniteOpt._measure_dependencies(dpref1))
        popfirst!(InfiniteOpt._measure_dependencies(dpref2))
        push!(InfiniteOpt._infinite_variable_dependencies(dpref1), InfiniteVariableIndex(1))
        @test is_used(pref1)
        @test is_used(dpref1)
        popfirst!(InfiniteOpt._infinite_variable_dependencies(dpref1))
        push!(InfiniteOpt._parameter_function_dependencies(dpref1), ParameterFunctionIndex(1))
        @test is_used(pref1)
        @test is_used(dpref1)
        popfirst!(InfiniteOpt._parameter_function_dependencies(dpref1))
    end
end

# Test derivative methods
@testset "Derivative Methods" begin
    m = InfiniteModel()
    @independent_parameter(m, pref in [0, 1])
    dpref = dispatch_variable_ref(pref)
    func = (x) -> NaN
    num = 0.
    info = VariableInfo{Float64, Float64, Float64, Function}(true, num, true,
                                        num, true, num, false, func, false, false)
    d = Derivative(info, true, pref, pref) # this is wrong but that is ok
    object = VariableData(d)
    idx = InfiniteOpt._add_data_object(m, object)
    push!(InfiniteOpt._derivative_dependencies(pref), idx)
    dref = DerivativeRef(m, idx)
    gdref = GeneralVariableRef(m, idx.value, DerivativeIndex)
    cref = @constraint(m, gdref == 0)
    # test _reset_derivative_evaluations
    @testset "_reset_derivative_evaluations" begin
        # test empty
        @test InfiniteOpt._reset_derivative_evaluations(dpref) isa Nothing
        # test warning
        InfiniteOpt._set_has_derivative_constraints(pref, true)
        @test push!(InfiniteOpt._derivative_constraint_dependencies(dref), index(cref)) isa Vector
        warn = "Support/method changes will invalidate existing derivative evaluation " *
               "constraints that have been added to the InfiniteModel. Thus, " *
               "these are being deleted."
        @test_logs (:warn, warn) InfiniteOpt._reset_derivative_evaluations(dpref) isa Nothing
        @test !has_derivative_constraints(pref)
        @test !is_valid(m, cref)
        # test has derivative supports to deal with
        supps = SortedDict{Float64, Set{DataType}}(42 => Set([InternalLabel]))
        param = IndependentParameter(IntervalSet(0, 1), supps, 12, TestGenMethod())
        InfiniteOpt._set_core_variable_object(dpref, param)
        InfiniteOpt._set_has_derivative_supports(pref, true)
        @test InfiniteOpt._reset_derivative_evaluations(dpref) isa Nothing
        @test !has_derivative_supports(dpref)
        @test length(InfiniteOpt._core_variable_object(dpref).supports) == 0
    end
    # test set_derivative_method
    @testset "set_derivative_method" begin
        push!(InfiniteOpt._constraint_dependencies(dpref), ConstraintIndex(1))
        @test set_derivative_method(pref, TestMethod()) isa Nothing
        @test derivative_method(dpref) isa TestMethod
    end
end

# Test parameter set methods
@testset "Infinite Set" begin
    m = InfiniteModel()
    @independent_parameter(m, pref_gen in [0, 1])
    pref_disp = dispatch_variable_ref(pref_gen)
    bad = Bad()
    bad_pref = IndependentParameterRef(m, IndependentParameterIndex(-1))
    # _parameter_set
    @testset "_parameter_set" begin
        @test InfiniteOpt._parameter_set(pref_disp) == IntervalSet(0, 1)
        @test_throws ErrorException InfiniteOpt._parameter_set(bad_pref)
    end
    # _update_parameter_set
    @testset "_update_parameter_set " begin
        push!(InfiniteOpt._constraint_dependencies(pref_disp), ConstraintIndex(1))
        @test isa(InfiniteOpt._update_parameter_set(pref_disp,
                                                    IntervalSet(1, 2)), Nothing)
        @test InfiniteOpt._parameter_set(pref_disp) == IntervalSet(1, 2)
    end
    # infinite_set
    @testset "infinite_set" begin
        @test_throws ArgumentError infinite_set(bad)
        @test infinite_set(pref_disp) == IntervalSet(1, 2)
        @test infinite_set(pref_gen) == IntervalSet(1, 2)
        @test_throws ErrorException infinite_set(bad_pref)
    end
    # set_infinite_set
    @testset "set_infinite_set" begin
        @test_throws ArgumentError set_infinite_set(bad, IntervalSet(0, 1))
        @test isa(set_infinite_set(pref_disp, IntervalSet(2, 3)), Nothing)
        @test infinite_set(pref_disp) == IntervalSet(2, 3)
        @test isa(set_infinite_set(pref_gen, IntervalSet(1, 3)), Nothing)
        @test infinite_set(pref_gen) == IntervalSet(1, 3)
        @test set_infinite_set(pref_gen, UniDistributionSet(Normal())) isa Nothing
        @test infinite_set(pref_disp) isa UniDistributionSet
        push!(InfiniteOpt._data_object(pref_gen).measure_indices, MeasureIndex(1))
        @test_throws ErrorException set_infinite_set(pref_gen, IntervalSet(1, 3))
    end
end

# Test parameter support methods
@testset "Supports" begin
    m = InfiniteModel()
    @independent_parameter(m, pref in [0, 1], sig_digits = 5)
    @independent_parameter(m, pref2 in [0, 1])
    pref_disp = dispatch_variable_ref(pref)
    bad = Bad()
    push!(InfiniteOpt._data_object(pref).constraint_indices, ConstraintIndex(1))
    # _parameter_supports
    @testset "_parameter_supports" begin
        @test InfiniteOpt._parameter_supports(pref_disp) == SortedDict{Float64, Set{DataType}}()
    end
    @testset "_parameter_support_values" begin
        @test InfiniteOpt._parameter_support_values(pref_disp) == Float64[]
    end
    # _update_parameter_supports
    @testset "_update_parameter_supports " begin
        dict = SortedDict{Float64, Set{DataType}}(1. => Set{DataType}([MCSample]))
        @test isa(InfiniteOpt._update_parameter_supports(pref_disp, dict), Nothing)
        @test InfiniteOpt._parameter_support_values(pref_disp) == [1.]
    end
    # significant_digits
    @testset "significant_digits" begin
        @test significant_digits(pref) == 5
    end
    # num_supports
    @testset "num_supports" begin
        @test_throws ArgumentError num_supports(bad)
        @test num_supports(pref_disp) == 1
        @test num_supports(pref_disp, label = UserDefined) == 0
        @test num_supports(pref) == 1
        @test num_supports(pref, label = UserDefined) == 0
        @test num_supports(pref, label = SampleLabel) == 1
        @test num_supports(pref, label = All) == 1
    end
    # has_supports
    @testset "has_supports" begin
        @test_throws ArgumentError has_supports(bad)
        @test has_supports(pref_disp)
        @test has_supports(pref)
        InfiniteOpt._update_parameter_supports(pref_disp, SortedDict{Float64, Set{DataType}}())
        @test !has_supports(pref_disp)
        @test !has_supports(pref)
    end
    # supports
    @testset "supports" begin
        @test supports(pref_disp) == []
        @test supports(pref) == []
        dict = SortedDict{Float64, Set{DataType}}(1. => Set{DataType}([MCSample]))
        InfiniteOpt._update_parameter_supports(pref_disp, dict)
        @test supports(pref_disp) == [1.]
        @test supports(pref) == [1.]
        @test supports(pref, label = MCSample) == [1.]
        @test supports(pref, label = All) == [1.]
    end
    # supports (vector)
    @testset "supports (vector)" begin
        # test simple case
        @test supports([pref_disp], label = SampleLabel) == ones(1, 1)
        # test typical combinatorial case
        supps = [[-1, 0, 1], [-1, 1]]
        @infinite_parameter(m, x[i = 1:2] in [-1, 1], supports = supps[i], independent = true)
        @test supports(x) == Float64[-1 0 1 -1 0 1; -1 -1 -1 1 1 1]
        # test non-combinatorial case
        @test_throws ErrorException supports(x, use_combinatorics = false)
        @test set_supports(x[2], supps[1], force = true) isa Nothing
        @test supports(x, use_combinatorics = false) == Float64[-1 0 1; -1 0 1]
    end
    # set_supports
    @testset "set_supports" begin
        @test_throws ArgumentError set_supports(bad, [0, 1])
        @test isa(set_supports(pref_disp, [0, 1], force = true), Nothing)
        @test isa(set_supports(pref, [0, 1], force = true), Nothing)
        @test supports(pref) == [0., 1.]
        @test_throws ErrorException set_supports(pref, [2, 3])
        warn = "Support points are not unique, eliminating redundant points."
        @test_logs (:warn, warn) set_supports(pref, [1, 1], force = true)
        @test_throws ErrorException set_supports(pref, [0.5])
        @test !has_internal_supports(pref)
    end
    # add_supports
    @testset "add_supports" begin
        @test_throws ArgumentError add_supports(bad, 0.5)
        @test isa(add_supports(pref_disp, 0.25), Nothing)
        @test isa(add_supports(pref, 0.5), Nothing)
        @test supports(pref) == [0.25, 0.5, 1.]
        @test isa(add_supports(pref, [0, 0.25, 1], check = false), Nothing)
        @test supports(pref) == [0, 0.25, 0.5, 1.]
        @test add_supports(pref, 0.2, label = InternalLabel) isa Nothing
        @test supports(pref) == [0, 0.25, 0.5, 1.]
        @test supports(pref, label = All) == [0, 0.2, 0.25, 0.5, 1.]
        @test has_internal_supports(pref)
    end
    # delete_supports
    @testset "delete_supports" begin
        # test bad parameter
        @test_throws ArgumentError delete_supports(bad)
        # test label deletion
        set_derivative_method(pref, TestGenMethod())
        InfiniteOpt._set_has_derivative_supports(pref, true)
        add_supports(pref, 0.1, label = UniformGrid)
        @test delete_supports(pref_disp, label = UniformGrid) isa Nothing
        @test !has_derivative_supports(pref)
        @test !has_internal_supports(pref)
        @test supports(pref, label = All) == [0, 0.25, 0.5, 1.]
        # test total deletion
        @test isa(delete_supports(pref), Nothing)
        @test !has_derivative_supports(pref)
        @test !has_internal_supports(pref)
        @test supports(pref) == []
        # test array input
        @test isa(delete_supports([pref]), Nothing)
        # prepare to test derivative constraints
        func = (x) -> NaN
        num = 0.
        info = VariableInfo{Float64, Float64, Float64, Function}(true, num, true,
                                            num, true, num, false, func, false, false)
        deriv = Derivative(info, true, pref, pref)
        object = VariableData(deriv)
        idx = InfiniteOpt._add_data_object(m, object)
        push!(InfiniteOpt._derivative_dependencies(pref), idx)
        dref = DerivativeRef(m, idx)
        gdref = GeneralVariableRef(m, 1, DerivativeIndex)
        cref = @constraint(m, gdref == 0)
        InfiniteOpt._set_has_derivative_constraints(pref, true)
        @test push!(InfiniteOpt._derivative_constraint_dependencies(dref), index(cref)) isa Vector
        # test derivative constraint warning
        warn = "Deleting supports invalidated derivative evaluations. Thus, these " *
               "are being deleted as well."
        @test_logs (:warn, warn) delete_supports(pref) isa Nothing
        @test !has_derivative_constraints(pref)
        @test !is_valid(m, cref)
        # test measure error
        push!(InfiniteOpt._data_object(pref).measure_indices, MeasureIndex(1))
        @test_throws ErrorException delete_supports(pref)
    end
end

# Test lower bound functions
@testset "Lower Bound" begin
    m = InfiniteModel()
    @independent_parameter(m, pref1 in [0, 1])
    @independent_parameter(m, pref2 in Normal())
    @independent_parameter(m, pref3 in BadScalarSet())
    bad = TestVariableRef(m, TestIndex(-1))
    # JuMP.has_lower_bound
    @testset "JuMP.has_lower_bound" begin
        @test_throws ArgumentError has_lower_bound(bad)
        @test has_lower_bound(dispatch_variable_ref(pref1))
        @test has_lower_bound(pref1)
        @test has_lower_bound(dispatch_variable_ref(pref2))
        @test has_lower_bound(pref2)
    end
    # JuMP.lower_bound
    @testset "JuMP.lower_bound" begin
        @test_throws ArgumentError lower_bound(bad)
        @test lower_bound(dispatch_variable_ref(pref1)) == 0
        @test lower_bound(dispatch_variable_ref(pref2)) == -Inf
        @test lower_bound(pref1) == 0
        @test lower_bound(pref2) == -Inf
        @test_throws ErrorException lower_bound(pref3)
    end
    # JuMP.set_lower_bound
    @testset "JuMP.set_lower_bound" begin
        @test_throws ArgumentError set_lower_bound(bad, 2)
        @test_throws ErrorException set_lower_bound(dispatch_variable_ref(pref1), 2)
        @test_throws ErrorException set_lower_bound(dispatch_variable_ref(pref3), 2)
        @test_throws ErrorException set_lower_bound(pref1, 2)
        @test_throws ErrorException set_lower_bound(pref3, 2)
        @test isa(set_lower_bound(dispatch_variable_ref(pref1), -1), Nothing)
        @test lower_bound(pref1) == -1
        @test isa(set_lower_bound(pref1, -2), Nothing)
        @test lower_bound(pref1) == -2
    end
end

# Test upper bound functions
@testset "Upper Bound" begin
    m = InfiniteModel()
    @independent_parameter(m, pref1 in [0, 1])
    @independent_parameter(m, pref2 in Normal())
    @independent_parameter(m, pref3 in BadScalarSet())
    bad = TestVariableRef(m, TestIndex(-1))
    # JuMP.has_upper_bound
    @testset "JuMP.has_upper_bound" begin
        @test_throws ArgumentError has_upper_bound(bad)
        @test has_upper_bound(dispatch_variable_ref(pref1))
        @test has_upper_bound(dispatch_variable_ref(pref2))
        @test has_upper_bound(pref1)
        @test has_upper_bound(pref2)
    end
    # JuMP.lower_bound
    @testset "JuMP.upper_bound" begin
        @test_throws ArgumentError upper_bound(bad)
        @test upper_bound(dispatch_variable_ref(pref1)) == 1
        @test upper_bound(dispatch_variable_ref(pref2)) == Inf
        @test upper_bound(pref1) == 1
        @test upper_bound(pref2) == Inf
        @test_throws ErrorException upper_bound(pref3)
    end
    # JuMP.set_lower_bound
    @testset "JuMP.set_upper_bound" begin
        @test_throws ArgumentError set_upper_bound(bad, -1)
        @test_throws ErrorException set_upper_bound(dispatch_variable_ref(pref1), -1)
        @test_throws ErrorException set_upper_bound(dispatch_variable_ref(pref3), -1)
        @test_throws ErrorException set_upper_bound(pref1, -1)
        @test_throws ErrorException set_upper_bound(pref3, -1)
        @test isa(set_upper_bound(dispatch_variable_ref(pref1), 2), Nothing)
        @test upper_bound(pref1) == 2
        @test isa(set_upper_bound(pref1, 3), Nothing)
        @test upper_bound(pref1) == 3
    end
end

# Test methods for finite parameters
@testset "Finite Parameters" begin
    # initialize the model
    m = InfiniteModel()
    bad = TestVariableRef(m, TestIndex(-1))
    # test @finite_parameter
    @testset "@finite_parameter" begin
        m2 = Model()
        # test errors
        @test_macro_throws ErrorException @finite_parameter(m)
        @test_macro_throws ErrorException @finite_parameter(m, a, 2, 3)
        @test_macro_throws ErrorException @finite_parameter(m, (2, 3, 4), 2)
        @test_macro_throws ErrorException @finite_parameter(m2, 2)
        @test_macro_throws ErrorException @finite_parameter(m, "bob")
        @test_macro_throws ErrorException @finite_parameter(m2, test, 2)
        @test_macro_throws ErrorException @finite_parameter(m, test, 2, bob = 2)
        # test anonymous definition
        pref = GeneralVariableRef(m, 1, FiniteParameterIndex)
        @test @finite_parameter(m, 42) == pref
        @test InfiniteOpt._core_variable_object(pref).value == 42
        # test vector anonymous definition
        prefs = [GeneralVariableRef(m, 2, FiniteParameterIndex),
                 GeneralVariableRef(m, 3, FiniteParameterIndex)]
        @test @finite_parameter(m, [1:2], 42, base_name = "a") == prefs
        @test InfiniteOpt._core_variable_object(prefs[1]).value == 42
        @test name.(prefs) == ["a[1]", "a[2]"]
        # test named definition
        pref = GeneralVariableRef(m, 4, FiniteParameterIndex)
        @test @finite_parameter(m, b, 42) == pref
        @test InfiniteOpt._core_variable_object(pref).value == 42
        @test name(pref) == "b"
        # test named vector definition
        prefs = [GeneralVariableRef(m, 5, FiniteParameterIndex),
                 GeneralVariableRef(m, 6, FiniteParameterIndex)]
        prefs = convert(JuMPC.SparseAxisArray, prefs)
        @test @finite_parameter(m, c[i = 1:2], [3, 7][i],
                                container = SparseAxisArray) == prefs
        @test InfiniteOpt._core_variable_object(prefs[2]).value == 7
        @test name(prefs[2]) == "c[2]"
    end
    # initialize the model
    m = InfiniteModel()
    # test parameter_value
    @testset "parameter_value" begin
        pref = GeneralVariableRef(m, 1, FiniteParameterIndex)
        dpref = dispatch_variable_ref(pref)
        @test_throws ArgumentError parameter_value(bad)
        @test @finite_parameter(m, g, 1) == pref
        @test parameter_value(dpref) == 1
        @test parameter_value(pref) == 1
    end
    # test JuMP.set_value
    @testset "JuMP.set_value" begin
        pref = GeneralVariableRef(m, 1, FiniteParameterIndex)
        dpref = dispatch_variable_ref(pref)
        push!(InfiniteOpt._constraint_dependencies(dpref), ConstraintIndex(1))
        @test_throws ArgumentError set_value(bad, 42)
        @test isa(set_value(dpref, 42), Nothing)
        @test parameter_value(pref) == 42
        @test isa(set_value(pref, 41), Nothing)
        @test parameter_value(pref) == 41
    end
end

# Test support flll-in and geneartion functions
@testset "Support Fill-in and Generation" begin
    @testset "generate_and_add_supports! (AbstractInfiniteSet)" begin
        m = InfiniteModel()
        gvref1 = @independent_parameter(m, 0 <= a <= 1)
        pref1 = dispatch_variable_ref(gvref1)
        set1 = infinite_set(pref1)
        dist = Normal(0., 1.)
        gvref2 = @independent_parameter(m, c in dist)
        pref2 = dispatch_variable_ref(gvref2)
        set2 = infinite_set(pref2)
        @test generate_and_add_supports!(pref1, set1, num_supports = 4) isa Nothing
        @test generate_and_add_supports!(pref2, set2, num_supports = 4) isa Nothing
        @test length(supports(pref1)) == 4
        @test length(supports(pref2)) == 4
    end
    # fill_in_supports! (ParameterRef)
    @testset "fill_in_supports! (ParameterRef)" begin
        m = InfiniteModel()
        pref1 = @independent_parameter(m, 0 <= a <= 1)
        pref2 = @independent_parameter(m, 0 <= b[1:2] <= 1)
        dist = Normal(0., 1.)
        pref3 = @independent_parameter(m, c in dist, supports = [-0.5, 0.5])
        @test fill_in_supports!(pref1, num_supports = 11) isa Nothing
        @test fill_in_supports!.(pref2, num_supports = 11) isa Array{Nothing}
        @test fill_in_supports!(pref3, num_supports = 11) isa Nothing
        @test length(supports(pref1)) == 11
        @test length(supports(pref2[1])) == 11
        @test length(supports(pref2[2])) == 11
        @test length(supports(pref3)) == 11
        @test -0.5 in supports(pref3)
        @test 0.5 in supports(pref3)
        @test fill_in_supports!(pref1, num_supports = 20) isa Nothing
        @test length(supports(pref1)) == 20
    end
end


module EMLmultiply
	using DanaTypes
	using DotPlusInheritance
	using Reexport
	@reexport using ...types.EMLtypes
	import EMLtypes.length
	include("multiply/Multiply.jl")
end


mutable struct Model
    config
    worddict
    nn
end

function Model(config)
    traindata, testdata, worddict = readdata(config)
    nn = NN_LSTM(Float32, length(worddict))
    m = Model(config, worddict, nn)
    train!(m, traindata, testdata)
    m
end

function train!(model, traindata, testdata)
    config = model.config
    device = config["device"]
    nn = todevice(model.nn, device)
    traindata = DataLoader(traindata, device=device, batchsize=config["seqlength"])
    testdata = DataLoader(testdata, device=device, batchsize=1)

    for epoch = 1:config["nepochs"]
        println("Epoch:\t$epoch")
        opt.rate = config["learning_rate"]
        println("Learning rate: $(opt.rate)")

        loss = fit!(traindata, nn)
        loss /= length(traindata.data.data)
        println("Loss:\t$loss")
        #perplexity = 2^loss
        #println("Perplexity:\t$perplexity")

        println()
    end
end


module DGmethods

using MPI
using ..MPIStateArrays
using ..Mesh.Grids
using ..Mesh.Topologies
using StaticArrays
using ..SpaceMethods
using ..VariableTemplates
using DocStringExtensions
using KernelAbstractions
using KernelAbstractions.Extras: @unroll

export BalanceLaw, DGModel, init_ode_state

include("balancelaw.jl")
include("NumericalFluxes.jl")
include("DGmodel.jl")
include("DGmodel_kernels.jl")

end


using LightDarkPOMDPs
using POMDPToolbox
using POMCP
using POMCPOW
using Plots
using Parameters
using StaticArrays
using POMDPs
using ContinuousPOMDPTreeSearchExperiments
using ParticleFilters
using Reel

# using Gallium
# Gallium.breakpoint(Pkg.dir("ContinuousPOMDPTreeSearchExperiments","src","ContinuousPOMDPTreeSearchExperiments.jl"), 68)
# @conditional Gallium.breakpoint(Pkg.dir("POMCPOW","src","solver2.jl"), 112) (h_node.node==14)

pomdp = LightDark2DTarget(term_radius=0.1,
                    init_dist=SymmetricNormal2([2.0, 2.0], 3.0),
                    discount=0.95)

rng_seed = 4
rng = MersenneTwister(rng_seed)

## POMCPOW ##
immutable OneStepValue end
POMCP.estimate_value(o::OneStepValue, pomdp::POMDP, s, h, steps) = reward(pomdp, s)

rng3 = copy(rng)
action_gen = AdaptiveRadiusRandom(max_radius=6.0, to_zero_first=true, to_light_second=true, rng=rng3)
rollout_policy = SimpleFeedback(max_radius=10.0)
tree_queries = 500_000
updater = ObsAdaptiveParticleFilter(pomdp, LowVarianceResampler(100_000), 0.05, rng)
solver = POMCPOWSolver(next_action=action_gen,
                        tree_queries=tree_queries,
                        criterion=MaxUCB(5.0),
                        final_criterion=MaxTries(),
                        max_depth=40,
                        k_action=10.0,
                        alpha_action=1/8,
                        k_observation=4.0,
                        alpha_observation=1/8,
                        estimate_value=OneStepValue(),
                        node_belief_updater=updater,
                        rng=rng3
                       )
#policy = solve(solver, pomdp)
policy = POMCPPlanner2(solver, pomdp)

#=
ib = initial_state_distribution(pomdp)
a = action(policy, ib)
POMCP.blink(policy)
=#

#=
using ProfileView
ib = initial_state_distribution(pomdp)
a = action(policy, ib)
Profile.clear()
@profile a = action(policy, ib)
ProfileView.view()
=#

hr = HistoryRecorder(max_steps=40, rng=MersenneTwister(rng_seed), initial_state=Vec2(2.0, 3.0), show_progress=true)
hist = simulate(hr, pomdp, policy, updater)
println(discounted_reward(hist))

using JLD
filename = tempname()*".jld"
JLD.save(filename, "hist", hist, "solver", solver, "pomdp", pomdp)
println("saved to "*filename)

pyplot()
steps = length(hist)
film = roll(fps=1, duration=steps) do t, dt
    print(".")
    v = view(hist, 1:Int(t+1))
    plot(pomdp)
    plot!(v)
    b = belief_hist(v)[end]
    plot!(b)
end
filename = string(tempname(), "POMCPOW_$(tree_queries)_$rng_seed.gif")
write(filename, film)
println(filename)
run(`setsid gifview $filename`)


include("splitcell.jl")
include("lineage.jl");
include("segmentation3d.jl")
include("normalization3d.jl")
include("julia2ims.jl")
using Dates

data_dir = "/datahub/rawdata/tandeng/mRNA_imaging/mRNA_confocal_hamamatsu-60X-TIRF/20200318";
ret_dir = "/datahub/rawdata/tandeng/mRNA_imaging/mRNA_confocal_hamamatsu-60X-TIRF/20200318_result/output";

#for s in 3:19
#for s in 2:20
function playing(s)
    println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    println("$(Dates.now()) Loading $s")
	@time img = load(File(format"TIFF","$data_dir/HE7-1NLS-22uw-const_1_s$s.ome.tiff"));
	GC.gc()
	println("Load done")

    t_len = size(img)[3]÷20
	@time markers = split_cell_LoG(img, t_len);
    GC.gc()
	@time time_line, longlived_labels, livingtime, time_line_whole =
		find_time_line(markers);
	if length(longlived_labels) == 0
		println("No longlived cells found, skipping")
		return nothing
	end
	@time split_contacted_cell!(time_line, longlived_labels, livingtime, time_line_whole);
	tracks = walking(time_line, longlived_labels, livingtime);
	@time longlived_maps, watershed_maps = grant_domain(img, time_line, longlived_labels, livingtime, time_line_whole);
    #save("$ret_dir/d21s$(s)-watershed.jld", "maps", watershed_maps)
	for index in 1:length(longlived_labels)
		println("tracking cell $index")
		@time local cell = pick_cell(img, longlived_maps, longlived_labels[index], tracks[:,:,index],
								livingtime[index]);
		@time cell_nu, nucleus_size, nucleus_th = extract3dnucleus(cell);
		@time cell_nu_nor, nor_para = normalize(cell_nu);
        save("$ret_dir/d18s$(s)-$index.jld", "threshold", nucleus_th, "normal", nor_para,
             "livingtime", livingtime[index], "track", tracks[:,:,index], "size", nucleus_size)
        save2ims(reinterpret.(reshape(cell_nu_nor, (512,512,20,t_len))),
                 "$ret_dir/d18s$(s)-$index.ims")
	end
	GC.gc()
end

println("Processing $data_dir, output $ret_dir")
for i in parse(Int, ARGS[1]):parse(Int, ARGS[2])
    playing(i)
end


module MultiLearners

export MultiLearner, transform!, fit!
export multirun

using TSML.TSMLTypes
import TSML.TSMLTypes.fit! # to overload
import TSML.TSMLTypes.transform! # to overload
using TSML.Utils: mergedict
using TSML.System # import LIB_SKL_AVAILABLE

if LIB_SKL_AVAILABLE
    using TSML.SKLearners
end

mutable struct MultiLearner <: TSLearner
    model
    args
    data

    function MultiLearner(args=Dict())
        default_args=Dict(
            :output => :numeric,
            :learner => "RandomForestRegressor",
            :impl_args => Dict()
        )
        new(nothing,mergedict(default_args,args),nothing)
    end
end

function fit!(mlt::MultiLearner, x::T,y::Vector) where {T<:Union{Matrix,Vector}}
    @info "fit: to be implemented"
    @info "matrify x"
    @info "fit(matrified x, y)"
end

function transform!(mlt::MultiLearner,x::T) where {T<:Union{Matrix,Vector}}
    @info "transform: to be implemented"
end

function multirun()
    model = MultiLearner()
    println(model)
end

end


"""
ULMFiT - testing file
"""

# Test the language model on the validation set
function test_lm(lm, data_gen)
    model_layers = Chain(
        lm.embedding_layer,
        lm.lstm_layer1,
        lm.lstm_layer2,
        lm.lstm_layer3,
        x -> lm.embedding_layer(x, true),
        softmax
    )
    testmode!(model_layers)
    sum_l, l_vect = 0, []
    for iter=1:num_of_iters
        x, y = take!(data_gen)
        h = broadcast(w -> model_layers[1](indices([w], lm.vocab, "_unk_")), x)
        h = model_layers[2:end].(h)
        y = broadcast(x -> Flux.onehotbatch([x], lm.vocab, "_unk_"), y)
        Flux.reset!(model_layers)
        l = sum(crossentropy.(Tracker.data.(h), y))
        sum_l += l
        push!(l_vect, l)
    end
    return sum_l/num_of_iters, l_vect
end

# Sampling
function sampling(starting_text::String, lm::LanguageModel=LanguageModel())
    model_layers = Chain(
        lm.lstm_layer1,
        lm.lstm_layer2,
        lm.lstm_layer3,
    )
    testmode!(model_layers)
    tokens = tokenize(starting_text)
    word_indices = map(x -> indices([x], lm.vocab, "_unk_"), tokens)
    embeddings = lm.embedding_layer.(word_indices)
    h = (model_layers.(embeddings))[end]
    probabilities = softmax(lm.embedding_layer(h, true))
    prediction = lm.vocab[findall(isequal(maximum(probabilities)), probabilities)[1]]
    println("SAMPLING...")
    print(prediction, ' ')
    while true
        h = lm.embedding_layer(indices([prediction], lm.vocab, "_unk_"))
        h = model_layers(h)
        probabilities = softmax(lm.embedding_layer(h, true))
        prediction = lm.vocab[findall(isequal(maximum(probabilities)), probabilities)[1]]
        print(prediction, ' ')
        prediction == "_pad_" && break
    end
end

# Test Classifier
function test_classifier(classifier::TextClassifier, data_gen)
    testmode!(classifier)
    sum_l, l_vect = 0, []
    for iter=1:num_of_iters
        x = take!(data_gen)
        y = take!(data_gen)
        h = broadcast(x -> classifier.rnn_layers[1](indices([x], lm.vocab, "_unk_")), x)
        h = classifier.rnn_layers[2:end].(h)
        h = classifier.linear_layers(h)
        Flux.reset!(classifier)
        l = crossentropy(Tracker.data(h), y)
        sum_l += l
        push!(l_vect, l)
    end
    return sum_l/num_of_iters, l_vect
end


function RangeRateTroposphericDelayModifier(arg0::DiscreteTroposphericModel, arg1::jboolean)
    return RangeRateTroposphericDelayModifier((DiscreteTroposphericModel, jboolean), arg0, arg1)
end

function get_parameters_drivers(obj::RangeRateTroposphericDelayModifier)
    return jcall(obj, "getParametersDrivers", List, ())
end

function modify(obj::RangeRateTroposphericDelayModifier, arg0::EstimatedMeasurement)
    return jcall(obj, "modify", void, (EstimatedMeasurement,), arg0)
end

function range_rate_error_tropospheric_model(obj::RangeRateTroposphericDelayModifier, arg0::GroundStation, arg1::FieldSpacecraftState, arg2::Vector{RealFieldElement})
    return jcall(obj, "rangeRateErrorTroposphericModel", RealFieldElement, (GroundStation, FieldSpacecraftState, Vector{RealFieldElement}), arg0, arg1, arg2)
end

function range_rate_error_tropospheric_model(obj::RangeRateTroposphericDelayModifier, arg0::GroundStation, arg1::SpacecraftState)
    return jcall(obj, "rangeRateErrorTroposphericModel", jdouble, (GroundStation, SpacecraftState), arg0, arg1)
end


#TODO implement proper soil module
@system Soil begin
    T_soil => 10 ~ track(u"°C")
    WP_leaf: leaf_water_potential => 0 ~ track(u"MPa") # pressure - leaf water potential MPa...
    total_root_weight => 0 ~ track(u"g")
end

@system SoilStub begin
    soil ~ hold

    WP_leaf(soil.WP_leaf) ~ track(u"MPa")
end

# Base.show(io::IO, s::Soil) = print(io, "$(s.T_soil)\n$(s.WP_leaf)\n$(s.total_root_weight)")


using PyCall
using PyPlot

@pyimport matplotlib.animation as anim  # After using PyPlot, so matplotlib installed via conda.
const wave_propagation_filename = "wave_propagation.mp4"

function saveall(output_path=".")
    fig = figure()
    ims = []
    for i_time in 1:10
        im = PyPlot.imshow(rand(4, 8), cmap="viridis")
        if 1 == i_time
            PyPlot.colorbar()
        end
        PyPlot.title("Wave amplitude [dB]")
        PyPlot.xlabel("Depth [m]")
        PyPlot.ylabel("Azimuth [m]")
        push!(ims, PyCall.PyObject[im])
    end
    #= close() =#

    # If matplotlib complains, ensure that
    # a) ffmpeg is installed with libx264 support, and
    # b) matplotlib is built with ffmpeg support enabled
    ani = anim.ArtistAnimation(fig, ims, interval=30, blit=true, repeat=false)
    anim_filename = wave_propagation_filename
    mywriter = anim.FFMpegWriter()
    ani[:save](joinpath(output_path, anim_filename), mywriter)
end

saveall()


export sliceColorDim, sliceTimeDim, sliceSpatialDim, threeSlices,
       getColoredSlices, getColoredSlicesMovie

### sliceColorDim ###

sliceColorDim(A::AbstractArray{T,3}, proj::Integer) where T = A[proj,:,:]
sliceColorDim(A::AbstractArray{T,4}, proj::Integer) where T = A[proj,:,:,:]
sliceColorDim(A::AbstractArray{T,5}, proj::Integer) where T = A[proj,:,:,:,:]

### sliceTimeDim ###

sliceTimeDim(A::ImageMeta{T,D}, proj::String) where {T,D} =
  copyproperties(A, sliceTimeDim(A.data, proj))

function sliceTimeDim(A::AbstractArray{T,D}, proj::String) where {T,D}
  if  proj == "MIP"
    return dropdims(maximum(A, dims=D), dims=D)
  elseif proj == "TTP"
    error("not yet implemented!")
    #data_ = [maximum(d, 4) for d in m.data]
    #data_ = [  mip(d,4) for d in m.data]
  #elseif params[:frameProj] == 2 && ndims(m.data[1]) == 4
  #data_ = [timetopeak(d, alpha=params[:TTPThresh], alpha2=params[:TTPThresh]) for d in m.data]
  end
end

function sliceTimeDim(A::AbstractArray{T,5}, proj::Integer) where T
  return getindex(A,:,:,:,:,proj)
end

function sliceTimeDim(A::AbstractArray{T,4}, proj::Integer) where T
  return getindex(A,:,:,:,proj)
end

### sliceSpatialDim ###

function sliceSpatialDim(A::AbstractArray{T,3}, proj::Union{Tuple,Vector}, dim::Integer) where T
  if dim == 1
    return A[proj[1],:,:]
  elseif dim == 2
    return A[:,proj[2],:]
  else
    return A[:,:,proj[3]]
  end
end

function sliceSpatialDim(A::AbstractArray{T,4}, proj::Union{Tuple,Vector}, dim::Integer) where T
  if dim == 1
    return A[:,proj[1],:,:]
  elseif dim == 2
    return A[:,:,proj[2],:]
  else
    return A[:,:,:,proj[3]]
  end
end

function sliceSpatialDim(A::AbstractArray{T,D}, proj::String, dim::Integer) where {T,D}
  d = (D == 4) ? dim + 1 : dim
  if proj == "MIP"
    return AbstractArray{T,D-1}(dropdims(maximum(A, dims=d),dims=d))
  elseif proj == "SUM"
    return AbstractArray{T,D-1}(dropdims(sum(A, d),dims=d))
  else
    error("proj=$proj not available!")
  end
end

### threeSlices ###

function threeSlices(data::AbstractArray{T,3}, proj) where T
  zx = reverse(permutedims(sliceSpatialDim(data, proj, 2),(2,1)),dims=2)
  zy = permutedims(sliceSpatialDim(data, proj, 1), (2,1))
  xy = reverse(sliceSpatialDim(data, proj, 3), dims=1)
  return zx, zy, xy
end

function threeSlices(data::AbstractArray{T,4}, proj) where T
  zx = sliceSpatialDim(data, proj, 2)
  zy = sliceSpatialDim(data, proj, 1)
  xy = sliceSpatialDim(data, proj, 3)
  return _changeDims(zx, zy, xy)
end

function _changeDims(zx, zy, xy)
  zx_ = reverse(permutedims(zx,(1,3,2)),dims=3)
  zy_ = permutedims(zy, (1,3,2))
  xy_ = reverse(xy, dims=2)
  return zx_, zy_, xy_
end

### getColoredSlices ###

function getColoredSlices(data::AbstractArray{T,4}, dataBG, coloring,
                          minval, maxval, params) where {T}
  slices = (min(params[:sliceX], size(data,2)),
	    min(params[:sliceY], size(data,3)),
	    min(params[:sliceZ], size(data,4)))
  proj = params[:spatialMIP] ? "MIP" : slices

  return getColoredSlices(data, dataBG, coloring, minval, maxval, proj, params[:blendChannels],
                          params[:complexBlending], params[:activeChannel],
                          get(params,:coloringBG,ColoringParams(0,1,"gray")),
                          get(params,:hideFG, false), get(params,:hideBG, false),
                          get(params,:translucentBlending, false),
                          get(params,:spatialMIPBG, false) ? "MIP" : slices)
end

function getColoredSlices(data::AbstractArray{T,4}, dataBG, coloring,
                          minval, maxval, proj, blendChannels, complexBlending,
                          activeChannels, coloringBG, hideFG, hideBG,
                          translucentBlending, projBG) where {T}

  zx, zy, xy = threeSlices(data, proj)

  cdata_zx = colorize(zx,coloring,minval,maxval,blendChannels,activeChannels,complexBlending)
  cdata_zy = colorize(zy,coloring,minval,maxval,blendChannels,activeChannels,complexBlending)
  cdata_xy = colorize(xy,coloring,minval,maxval,blendChannels,activeChannels,complexBlending)

  if dataBG != nothing #!isempty(dataBG)
    zxBG, zyBG, xyBG = threeSlices(dataBG, projBG)  #no MIP for BG data!!!

    minval,maxval = extrema(dataBG)
    cdataBG_zx = colorize(zxBG,coloringBG,minval,maxval)
    cdataBG_zy = colorize(zyBG,coloringBG,minval,maxval)
    cdataBG_xy = colorize(xyBG,coloringBG,minval,maxval)

    if !hideFG && !hideBG
      if translucentBlending
        cdata_zx = blend(cdataBG_zx, cdata_zx)
        cdata_zy = blend(cdataBG_zy, cdata_zy)
        cdata_xy = blend(cdataBG_xy, cdata_xy)
      else
        #all_maps = existing_cmaps()
        colormap = cmap( coloring[1].cmap)

        cdata_zx = overlay(cdataBG_zx, cdata_zx, first(colormap))
        cdata_zy = overlay(cdataBG_zy, cdata_zy, first(colormap))
        cdata_xy = overlay(cdataBG_xy, cdata_xy, first(colormap))
      end
    elseif hideFG
      cdata_zx = cdataBG_zx
      cdata_zy = cdataBG_zy
      cdata_xy = cdataBG_xy
    end
  end

  return cdata_zx, cdata_zy, cdata_xy
end

export getInterpAndColoredSlices

function getInterpAndColoredSlices(data::AbstractArray{T,4}, dataBG,
                                   coloring, minval, maxval, params) where {T}
  if dataBG != nothing
    dataFG = interpolateToRefImage(dataBG, data, params)
    dataBG = interpolateToRefImage(dataBG, params)
  else
    dataFG = data
    dataBG = nothing
  end

  cdata_zx, cdata_zy, cdata_xy = getColoredSlices(dataFG, dataBG, coloring, minval, maxval, params)
  return cdata_zx, cdata_zy, cdata_xy
end

function getColoredSlicesMovie(data::ImageMeta{T,5}, dataBG,
                               coloring, params) where {T}

    xx, yy, zz = _getColoredSlicesMovie(data, dataBG, coloring, params)

    return [copyproperties(data, xx), copyproperties(data, yy), copyproperties(data, zz)]

end

function getColoredSlicesMovie(data::AbstractArray{T,5}, dataBG,
                               coloring, params) where {T}
   return _getColoredSlicesMovie(data, dataBG, coloring, params)
end

function _getColoredSlicesMovie(data::AbstractArray{T,5}, dataBG,
                               coloring, params) where {T}
    L = size(data, 5)

    maxval = [maximum(sliceColorDim(data,d)) for d=1:size(data,Axis{:color})]
    minval = [minimum(sliceColorDim(data,d)) for d=1:size(data,Axis{:color})]

    cdata_zx, cdata_zy, cdata_xy = getInterpAndColoredSlices(sliceTimeDim(data,1), dataBG, coloring, minval, maxval, params)

    cdata_xy_all = AxisArray(zeros(eltype(cdata_xy),size(cdata_xy,1),size(cdata_xy,2),L),
                             AxisArrays.axes(cdata_xy)..., AxisArrays.axes(data)[5])

    cdata_zx_all = AxisArray(zeros(eltype(cdata_zx),size(cdata_zx,1),size(cdata_zx,2),L),
                             AxisArrays.axes(cdata_zx)..., AxisArrays.axes(data)[5])

    cdata_zy_all = AxisArray(zeros(eltype(cdata_zy),size(cdata_zy,1),size(cdata_zy,2),L),
                             AxisArrays.axes(cdata_zy)..., AxisArrays.axes(data)[5])

    cdata_xy_all[:,:,1] .= cdata_xy
    cdata_zy_all[:,:,1] .= cdata_zy
    cdata_zx_all[:,:,1] .= cdata_zx

    for l=2:L
      cdata_zx, cdata_zy, cdata_xy = getInterpAndColoredSlices(sliceTimeDim(data,l), dataBG, coloring, minval, maxval, params)

      cdata_xy_all[:,:,l] .= cdata_xy
      cdata_zy_all[:,:,l] .= cdata_zy
      cdata_zx_all[:,:,l] .= cdata_zx
    end
    return [cdata_xy_all, cdata_zx_all, cdata_zy_all]
end


using JSON
Nproc=4

rmprocs(workers())
addprocs(Nproc)

println("workers: ",length(workers()))

@everywhere using Base.Test
@everywhere import Base.convert
@everywhere using MAT
@everywhere using ForwardDiff
@everywhere using Polynomials
@everywhere import ForwardDiff.DiffBase
@everywhere using Optim
@everywhere gc(true)

# cluster idibaps
@everywhere path="/home/emma/github/fit_behaviour_julia/"
# cluster
# @everywhere path="/home/hcli64/hcli64751/rats/fit_behaviour_julia/"
#
@everywhere include(path*"/functions/functions_julia.jl")
@everywhere include(path*"/functions/parameter_functions.jl")
@everywhere include(path*"/functions/utils.jl")

rat_num = 4
path_load=path*"scripts_rats/"
filename="/data/processed_data.json"

data = Dict()
open(path_load*filename, "r") do f
    global data
    dicttxt = readstring(f)  # file information to string
    data=JSON.parse(dicttxt)  # parse and transform data
end

stimulus = data[1][rat_num]             # Data frames obtained from a gaussian distribution of a determined coherence
# coherences = data[2][rat_num]           # Target coherences used for each trial
# rewards = data[3][rat_num]              # Correct choice side
decisions = data[4][rat_num]            # Actual decision made by the rat each trial
# performances = data[5][rat_num]         # 0 if the rat chose the correct choice, 1 if the rat chose the incorrect choice
# target_sigmas = data[6][rat_num]        # Target sigmas used for each trial

stim_10 = []
stim_20 = []
choices_10 = []
choices_20 = []

# Separar els estímuls de 10 frames dels de 20 frames
for session in 1:size(stimulus)[1]
    for trial in 1:size(stimulus[session])[1]
        stim_1 = stimulus[session][trial]
        if size(stim_1)[1] == 10
            stim_10 = [stim_10;stim_1]
            choices_10 = [choices_10;decisions[session][trial]]
        end
        if size(stim_1)[1] == 20
            stim_20 = [stim_20;stim_1]
            choices_20 = [choices_20;decisions[session][trial]]
        end
    end
end
n_stim_10 = convert(Int64, size(stim_10)[1]/10)
n_stim_20 = convert(Int64, size(stim_20)[1]/20)

# if n_stim_10 != 0
#     stim_10 = permutedims(reshape(stim_10,(10,n_stim_10)))
# end
# if n_stim_20 != 0
#     stim_20 = permutedims(reshape(stim_20,(20,n_stim_20)))
# end

results = Dict("stim_10"=>stim_10,"stim_20"=>stim_20,"choices_10"=>choices_10,"choices_20"=>choices_20,"n_stim_10"=>n_stim_10,"n_stim_20"=>n_stim_20)

filename_save = path_load*"data/processed_data_rat"*string(rat_num)*".json"
# pass data as a json string (how it shall be displayed in a file)
stringdata = JSON.json(results)

# write the file with the stringdata variable information
open(filename_save, "w") do f
        write(f, stringdata)
     end


# Adapted from Knet's src/data.jl (author: Deniz Yuret)
module Data

export
    DataLoader,
    VariableBatchSizeUniformSampleWithReplacement

using
    Random

using Base: @propagate_inbounds
using Random: GLOBAL_RNG
struct DataLoader{S,D,R<:AbstractRNG}
    data::D
    batchsize::Int
    nobs::Int
    partial::Bool
    imax::Int
    indices::Vector{Int}
    shuffle::Bool
    rng::R
    datasampler::S
end

function UniformSampleWithoutReplacement(d::DataLoader, i::Integer)
    if d.shuffle && i == 0
        shuffle!(d.rng, d.indices)
        @info "Using UniformSampleWithoutReplacement"
    end
    nexti = min(i + d.batchsize, d.nobs)
    ids = d.indices[i + 1:nexti]
    return (ids, nexti)
end

function UniformSampleWithReplacement(d::DataLoader, i::Integer)
    if d.shuffle && i == 0
        # only thing different from above... should probably combine
        rand!(d.rng, d.indices, [1:d.nobs;])
        @info "Using UniformSampleWithReplacement"
    end
    nexti = min(i + d.batchsize, d.nobs)
    ids = d.indices[i + 1:nexti]
    return (ids, nexti)
end

function VariableBatchSizeUniformSampleWithReplacement(d::DataLoader, i::Integer)
    if i == 0
        @info "Using VariableBatchSizeUniformSampleWithReplacement"
    end

    total_batches = d.nobs / d.batchsize
    sample_rate = float(d.batchsize) / float(d.nobs)

    ids = 0 # hack for scope, probably not Julian
    while true
        mask = rand(d.rng, Float32, size(d.indices)) .< sample_rate
        ids = d.indices[mask]
        length(ids) > 0 && break # ensure we have at least one observation per batch
    end
    # @show length(ids)
    nexti = i + 1

    if nexti >= total_batches
        nexti = d.imax # hacky
    end

    return (ids, nexti)
end

"""
    DataLoader(data; batchsize=1, shuffle=false, partial=true, rng=GLOBAL_RNG)

An object that iterates over mini-batches of `data`, each mini-batch containing
`batchsize` observations (except possibly the last one).

Takes as input a single data tensor, or a tuple (or a named tuple) of tensors.
The last dimension in each tensor is considered to be the observation dimension.

If `shuffle=true`, shuffles the observations each time iterations are re-started.
If `partial=false`, drops the last mini-batch if it is smaller than the batchsize.

The original data is preserved in the `data` field of the DataLoader.

Usage example:

    Xtrain = rand(10, 100)
    train_loader = DataLoader(Xtrain, batchsize=2)
    # iterate over 50 mini-batches of size 2
    for x in train_loader
        @assert size(x) == (10, 2)
        ...
    end

    train_loader.data   # original dataset

    # similar, but yielding tuples
    train_loader = DataLoader((Xtrain,), batchsize=2)
    for (x,) in train_loader
        @assert size(x) == (10, 2)
        ...
    end

    Xtrain = rand(10, 100)
    Ytrain = rand(100)
    train_loader = DataLoader((Xtrain, Ytrain), batchsize=2, shuffle=true)
    for epoch in 1:100
        for (x, y) in train_loader
            @assert size(x) == (10, 2)
            @assert size(y) == (2,)
            ...
        end
    end

    # train for 10 epochs
    using IterTools: ncycle
    Flux.train!(loss, ps, ncycle(train_loader, 10), opt)

    # can use NamedTuple to name tensors
    train_loader = DataLoader((images=Xtrain, labels=Ytrain), batchsize=2, shuffle=true)
    for datum in train_loader
        @assert size(datum.images) == (10, 2)
        @assert size(datum.labels) == (2,)
    end
"""
function DataLoader(data; batchsize=1, shuffle=false, partial=true, rng=GLOBAL_RNG,
                    datasampler=UniformSampleWithoutReplacement)
    batchsize > 0 || throw(ArgumentError("Need positive batchsize"))

    n = _nobs(data)
    if n < batchsize
        @warn "Number of observations less than batchsize, decreasing the batchsize to $n"
        batchsize = n
    end
    imax = partial ? n : n - batchsize + 1
    DataLoader(data, batchsize, n, partial, imax, [1:n;], shuffle, rng, datasampler)
end

# Return data in d.indices[i+1:i+batchsize]
@propagate_inbounds function Base.iterate(d::DataLoader, i=0)
    i >= d.imax && return nothing
    # if d.shuffle && i == 0
    #     shuffle!(d.rng, d.indices)
    # end
    # nexti = min(i + d.batchsize, d.nobs)
    # ids = d.indices[i + 1:nexti]
    ids, nexti = d.datasampler(d, i)
    batch = _getobs(d.data, ids)
    return (batch, nexti)
end

function Base.length(d::DataLoader)
    n = d.nobs / d.batchsize
    d.partial ? ceil(Int, n) : floor(Int, n)
end

_nobs(data::AbstractArray) = size(data)[end]

function _nobs(data::Union{Tuple,NamedTuple})
    length(data) > 0 || throw(ArgumentError("Need at least one data input"))
    n = _nobs(data[1])
    if !all(x -> _nobs(x) == n, Base.tail(data))
        throw(DimensionMismatch("All data should contain same number of observations"))
    end
    return n
end

_getobs(data::AbstractArray, i) = data[ntuple(i -> Colon(), Val(ndims(data) - 1))..., i]
_getobs(data::Union{Tuple,NamedTuple}, i) = map(Base.Fix2(_getobs, i), data)

Base.eltype(::DataLoader{D}) where D = D

end


const FILTER_CACHE = Dict{UUID, Tuple{UInt64, Any}}()
const SORTED_CACHE = Dict{UUID, UInt64}()

col_access(row, col) = row[col]

function generate_sorter(params, fixed_col_names)
    lts = []
    for sortspec in params
        let col = sortspec["colId"]
            ind = findfirst(==(col), fixed_col_names)
            if ind === nothing
                continue
            end
            if sortspec["sort"] == "asc"
                push!(lts, (r -> col_access(r, ind), (r1, r2) -> col_access(r1, ind) < col_access(r2, ind)))
            else
                push!(lts, (r -> col_access(r, ind), (r1, r2) -> !(col_access(r1, ind) < col_access(r2, ind))))
            end
        end
    end
    function (row1, row2)
        lt = false
        for (accessor, ltf) in lts
            if ltf(row1, row2)
                return true
            end
            if accessor(row1) != accessor(row2)
                return false
            end
        end
        return lt
    end
end

const filterMapping = Dict{String, Function}(
    "equals" => ==,
    "notEqual" => !=,
    "lessThan" => <,
    "lessThanOrEqual" => <=,
    "greaterThan" => >,
    "greaterThanOrEqual" => >=,
)

function generate_filter(params, col)
    filter_type = params["filterType"]
    if filter_type == "number"
        generate_number_filter(params, col)
    elseif filter_type == "text"
        generate_string_filter(params, col)
    elseif filter_type == "date"
        generate_date_filter(params, col)
    else
        (args...) -> true
    end
end

function generate_number_filter(params, col)
    row -> filterMapping[params["type"]](col_access(row, col), params["filter"])
end

function regex_escape(s::AbstractString)
    res = replace(s, r"([()[\]{}?*+\-|^\$\\.&~#\s=!<>|:])" => s"\\\1")
    replace(res, "\0" => "\\0")
end

function generate_string_filter(params, col)
    filtervalue = regex_escape(params["filter"])
    op = params["type"]
    if op == "equals"
        matcher = Regex("^" * filtervalue * "\$", "i")
        row -> occursin(matcher, string(col_access(row, col)))
    elseif op == "notEqual"
        matcher = Regex("^" * filtervalue * "\$", "i")
        row -> !occursin(matcher, string(col_access(row, col)))
    elseif op == "startsWith"
        matcher = Regex("^" * filtervalue, "i")
        row -> occursin(matcher, string(col_access(row, col)))
    elseif op == "endsWith"
        matcher = Regex(filtervalue * "\$", "i")
        row -> occursin(matcher, string(col_access(row, col)))
    elseif op == "contains"
        matcher = Regex(filtervalue, "i")
        row -> occursin(matcher, string(col_access(row, col)))
    elseif op == "notContains"
        matcher = Regex(filtervalue, "i")
        row -> !occursin(matcher, string(col_access(row, col)))
    end
end

function generate_date_filter(params, col)
    format = dateformat"y-m-d H:M:S"
    dateFrom = Date(params["dateFrom"], format)

    op = params["type"]

    if op == "inRange"
        dateTo = Date(params["dateTo"], format)

        row -> >(col_access(row, col), dateFrom) && <(col_access(row, col), dateTo)
    else
        row -> filterMapping[op](col_access(row, col), dateFrom)
    end
end

function generate_bool(op, cond1, cond2, col)
    let f1 = generate_filter(cond1, col), f2 = generate_filter(cond2, col)
        if op == "AND"
            (args...) -> f1(args...) && f2(args...)
        else
            (args...) -> f1(args...) || f2(args...)
        end
    end
end

function generate_filterer(filters::Dict, fixed_col_names)
    funcs = Function[]
    for (col, filter) in filters
        ind = findfirst(==(col), fixed_col_names)
        if ind === nothing
            continue
        end
        op = get(filter, "operator", "")
        if op in ("AND", "OR")
            push!(funcs, generate_bool(op, filter["condition1"], filter["condition2"], ind))
        else
            push!(funcs, generate_filter(filter, ind))
        end
    end
    return funcs
end


module OutlierDetectionNetworks
    using OutlierDetectionInterface
    using OutlierDetectionInterface:SCORE_UNSUPERVISED, SCORE_SUPERVISED
    const OD = OutlierDetectionInterface

    include("utils.jl")
    include("models/ae.jl")
    include("models/dsad.jl")
    include("models/esad.jl")
    include("templates/templates.jl")

    const UUID = "c7f57e37-4fcb-4a0b-a36c-c2204bc839a7"
    const MODELS = [:AEDetector,
                    :DSADDetector,
                    :ESADDetector]

    for model in MODELS
        @eval begin
            OD.@default_frontend $model
            OD.@default_metadata $model $UUID
            export $model
        end
    end
end


using Pkg

pkg"add https://github.com/faf0/AES.jl"
#pkg"rm AES"
pkg"add https://github.com/oxinabox/MD5.jl"
pkg"add JSON"


function add_noise!(layout::RQCLayout{VT, PT, SCGate}, v, d) where {VT, PT}
    if gates(layout, v)[d] !== FSim
        gates(layout, v)[d] = Noise
        return true
    end
    return false
end

function update_noise!(layout::RQCLayout{VT, PT, SCGate}; approx = false) where {VT, PT}
    to_update = true
    while to_update
        to_update = false
        for v in vertices(layout)
            gates_v = gates(layout, v)
            for i in 1:length(gates_v)
                gates_v[i] === Noise || continue
                for j in (i-1, i+1)
                    1 <= j <= length(gates_v) || continue
                    if gates_v[j] === FSim
                        u = partner(layout, v, depth_to_cycle(j))
                        gates_u = gates(layout, u)
                        if Noise in (approx ? (gates_u[i], gates_v[2j-i], gates_u[2j-i]) : (gates_u[i], gates_v[2j-i]))
                            gates_v[j] = Noise
                            gates_u[j] = Noise
                            # u == 53 && println("FSim @ $(depth_to_cycle(j)) from $v")
                            # v == 53 && println("FSim @ $(depth_to_cycle(j))")
                            to_update = true
                        end
                    elseif gates_v[j] in (Id, Single)
                        gates_v[j] = Noise
                        # v == 53 && println("Single @ $(depth_to_cycle(j))")
                        to_update = true
                    end
                end
            end
        end
    end
    return layout
end

function update_id!(layout::RQCLayout{VT, PT, SCGate}) where {VT, PT}
    for v in vertices(layout)
        gates_v = gates(layout, v)
        for i = 1:(length(gates_v)-1)
            if gates_v[i] === Noise && gates_v[i+1] === Noise
                i > 1 && (gates_v[i] = Id)
            end
        end
    end
    for v in vertices(layout)
        gates_v = gates(layout, v)
        if gates_v[1] === Noise && gates_v[2] === Noise
            gates_v[2] = Id
        end
    end
    return layout
end

simplify!(layout::RQCLayout{VT, PT, SCGate}; approx = false) where {VT, PT} = update_noise!(layout; approx = approx)
function simplify!(layout::RQCLayout{VT, PT, SCGate}, cuts; approx = false) where {VT, PT}
    for c in cuts
        add_noise!(layout, c[1], c[2])
    end
    return simplify!(layout; approx = approx)
end

function add_noise_greedy!(layout::RQCLayout, vs = collect(vertices(layout)),
    flux_min = 15, out_min = 25)
    update_noise!(layout)
    to_update = true
    while to_update
        to_update = false
        for d in 1:depth(layout), v in vs
            if gates(layout, v)[d] in (Single, Id)
                lo_new = copy(layout)
                gates(lo_new, v)[d] = Noise
                update_noise!(lo_new)
                if flux(lo_new, vs) >= flux_min &&
                        sum(gates(lo_new, v)[end] !== Noise for v in vs) >= out_min
                    add_noise!(layout, v, d)
                    update_noise!(layout)
                    to_update = true
                end
            end
        end
    end
    return layout
end


module TestProgress
include("preamble.jl")

using Test: collect_test_logs
using Logging: LogLevel

const ProgressLevel = LogLevel(-1)

function test_only_one_progress(f)
    logs, = collect_test_logs(f, min_level = ProgressLevel)
    ps = [get(l.kwargs, :progress, nothing) for l in logs]
    @test any(isa.(ps, Real))
    @test "done" in ps
    @test length(unique([l.id for l in logs])) == 1
end

@testset "foldl" begin
    test_only_one_progress() do
        @test foldl(+, withprogress(1:100; interval=0)) == 5050
    end
end

@testset "sum" begin
    test_only_one_progress() do
        @test sum(withprogress(1:100; interval=0)) == 5050
    end
end

end  # module


# Reading of each struct type has 2 methods
# - s has same type as sink
#   - read is performed at the element level and doesn't require any additional permutations
# - s has different type from sink
#   - read in as raw eltype and permutedims to fill in sink. This requires appropriately axisnames

########
# Read #
########
Base.read!(s::AxisStream, sink::ImageMeta) = read!(s, getfield(sink, :data))
Base.read!(s::AxisStream{S,<:BitTypes}, sink::Array{<:BitTypes}) where {S,N} = read!(getfield(s,:data), sink)
Base.read!(s::AxisStream{S,T,N}, sink::AxisArray{T,N}) where {S,T,N} = read!(getfield(s, :data), getfield(s,:data))
Base.read(s::AxisStream{S,T,N,I,Ax}) where {S,T,N,I,Ax} = AxisArray(read(s), axisparams(s))
# raw eltype → struct view can use Axis name permutation to semiautomate this
Base.read!(s::AxisStream{S,<:BitTypes,N}, sink::AxisArray{T,N}) where {S,T,N} = read!(s, getfield(sink, :data))

# Triangle
function Base.read!(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{Triangle}) where {S,T,N,I,Ax}
    perm = permutation((:triangledim, setdiff(axisnames(s), :triangledim)...,), axisnames(s))
    return fill!(sink, triangleview(permutedims(read(getfield(s,:data)), perm)))
end

# Point
function Base.read!(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{Point}) where {S,N,I,Ax}
    perm = permutation((:pointdim, setdiff(AxisArrays.axisnames(s), :pointdim)...,), AxisArrays.axisnames(s))
    return fill!(sink, pointview(permutedims(read(s), perm)))
end

# Quat
function Base.read!(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{Quat}) where {S,N,I,Ax}
    perm = permutation((:quatdim, setdiff(axisnames(s), :quatdim)...,), axisnames(s))
    return fill!(sink, quatview(permutedims(read(getfield(s,:data)), perm)))
end

# SMatrix
function Base.read!(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{SMatrix}) where {S,N,I,Ax}
    perm = permutation((:matdim1, :matdim2, setdiff(axisnames(s), (:matdim1,:matdim2))...,), axisnames(s))
    return fill!(sink, matview(permutedims(read(getfield(s,:data)), perm)))
end

# SVector
function Base.read!(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{SVector}) where {S,N,I,Ax}
    perm = permutation((:vecdim, setdiff(axisnames(s), :vecdim)...,), axisnames(s))
    return fill!(sink, vecview(permutedims(read(getfield(s, :data)), perm)))
end

# ColorTypes
function Base.read!(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{T,N}) where {S,T<:Union{Color3,ColorAlpha},N,I,Ax}
    perm = permutation((:colordim, setdiff(axisnames(s), :colordim)...,), axisnames(s))
    return fill!(sink, colorview(T, PermutedDimsArray(read!(getfield(s, :data)), perm)))
end

# Distributions
function Base.read!(s::AxisStream{S,<:BitTypes}, sink::Array{T}) where {S,T<:Distribution}
    perm = permutation((:statdim, setdiff(axisnames(s), :statdim)...,), axisnames(s))
    return fill!(sink, statview(T, permutedims(read(s), perm)))
end

#########
# Write #
#########
Base.write(s::AxisStream{S,T,N,I,Ax}, sink::ImageMeta) where {S,T,N,I,D,Ax} = write(s, getfield(sink, :data))
Base.write(s::AxisStream{S,T,N,I,Ax}, sink::AxisArray) where {S,T,N,I,D,Ax} = write(s, getfield(sink, :data))

# Triangle
function Base.write(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{Triangle}) where {S,T,N,I,Ax}
    perm = permutation(axisnames(s), (:triangledim, setdiff(axisnames(s), :triangledim)...,))
    write(getfield(s,:data), PermutedDimsArray(mappedview(extracttriangle, sink), perm))
end

# Point
function Base.write(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{Point}) where {S,N,I,Ax}
    perm = permutation(axisnames(s), (:pointdim, setdiff(axisnames(s), :pointdim)...,))
    write(getfield(s,:data), PermutedDimsArray(mappedview(extractpoint, A), perm))
end

# Quat
function Base.write(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{Quat}) where {S,N,I,Ax}
    perm = permutation(axisnames(s), (:quatdim, setdiff(axisnames(s), :quatdim)...,))
    write(getfield(s,:data), PermutedDimsArray(mappedview(extractquat, A), perm))
end

# SMatrix
function Base.write(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{Quat}) where {S,N,I,Ax}
    perm = permutation(axisnames(s), (:matdim1, :matdim2, setdiff(axisnames(s), :matdim1, :matdim2)...,))
    write(getfield(s,:data), PermutedDimsArray(extractsmat(sink), perm))
end

# SVector
function Base.write(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{SVector}) where {S,N,I,Ax}
    perm = permutation(axisnames(s), (:vecdim, setdiff(axisnames(s), :vecdim)...,))
    write(getfield(s,:data), PermutedDimsArray(extractsvec(sink), perm))
end

# ColorTypes
function Base.write(s::AxisStream{S,<:BitTypes,N,I,Ax}, sink::Array{<:Union{Color3,ColorAlpha}}) where {S,N,I,Ax}
    perm = permutation(axisnames(s), (:colordim, setdiff(axisnames(s), :colordim)...,))
    write(getfield(s,:data), PermutedDimsArray(channelview(sink), perm))
end

# Distributions
function Base.write(s::AxisStream{S,<:BitTypes}, sink::Array{T}) where {S,T<:Distribution}
    perm = permutation(axisnames(s), (:statdim, setdiff(axisnames(s), :statdim)...,))
    write(getfield(s,:data), PermutedDimsArray(extractstat(sink), perm))
end


function run_Ipopt_para_ac(itema, network_data, load_distrn, load_keys, gen_keys, time_lim)

    network_data_new = deepcopy(network_data)
    #relax the inf term
    for i in 1:length(gen_keys)
        if network_data_new["gen"][gen_keys[i]]["qmax"] == Inf
            network_data_new["gen"][gen_keys[i]]["qmax"] = 10^20
        end

        if network_data_new["gen"][gen_keys[i]]["qmax"] == -Inf
            network_data_new["gen"][gen_keys[i]]["qmax"] = -10^20
        end

        if network_data_new["gen"][gen_keys[i]]["qmin"] == Inf
            network_data_new["gen"][gen_keys[i]]["qmin"] = 10^20
        end

        if network_data_new["gen"][gen_keys[i]]["qmin"] == -Inf
            network_data_new["gen"][gen_keys[i]]["qmin"] = -10^20
        end
    end

    if haskey(network_data_new, "dcline")
        dcline_keys = [i for i in keys(network_data["dcline"])]

        for i in 1:length(dcline_keys)
            if network_data_new["dcline"][dcline_keys[i]]["qmaxf"] == Inf
                network_data_new["dcline"][dcline_keys[i]]["qmaxf"] = 10^20
            end

            if network_data_new["dcline"][dcline_keys[i]]["qmaxf"] == -Inf
                network_data_new["dcline"][dcline_keys[i]]["qmaxf"] = -10^20
            end

            if network_data_new["dcline"][dcline_keys[i]]["qmint"] == Inf
                network_data_new["dcline"][dcline_keys[i]]["qmint"] = 10^20
            end

            if network_data_new["dcline"][dcline_keys[i]]["qmint"] == -Inf
                network_data_new["dcline"][dcline_keys[i]]["qmint"] = -10^20
            end

            if network_data_new["dcline"][dcline_keys[i]]["qminf"] == Inf
                network_data_new["dcline"][dcline_keys[i]]["qminf"] = 10^20
            end

            if network_data_new["dcline"][dcline_keys[i]]["qminf"] == -Inf
                network_data_new["dcline"][dcline_keys[i]]["qminf"] = -10^20
            end

            if network_data_new["dcline"][dcline_keys[i]]["qmaxt"] == Inf
                network_data_new["dcline"][dcline_keys[i]]["qmaxt"] = 10^20
            end

            if network_data_new["dcline"][dcline_keys[i]]["qmaxt"] == -Inf
                network_data_new["dcline"][dcline_keys[i]]["qmaxt"] = -10^20
            end
        end
    end

    for i in 1:length(load_keys)
        network_data_new["load"][load_keys[i]]["pd"] += rand(load_distrn[load_keys[i]])
    end

    out_file_name = "./data/perturb_output/" * itema[1:end-2] * "perturb_output" * ".csv"

    # run the test with Ipopt
    r_ipopt = run_ac_opf(network_data_new,
        optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0, "max_wall_time" => time_lim, "file_print_level" => 3,
            "output_file" => out_file_name))

    obj_val = r_ipopt["objective"]
    Optimal = r_ipopt["termination_status"]
    solve_time = r_ipopt["solve_time"]
    primal_feasibility = r_ipopt["primal_status"]
    dual_feasibility = r_ipopt["dual_status"]
    time_limit = time_lim

    # initialize variables
    esti_obj_val = 0
    dual_infeasibility = 0
    constraint_violation = 0
    overall_nlp = 0
    complementarity = 0
    var_bound_violation = 0
    esti_Optimal = 0

    open(out_file_name, "r") do file_object
        for line in eachline(file_object)

            if occursin("Objective...............", line)
                esti_obj_val = line[55:end]
            end

            if occursin("Dual infeasibility....", line)
                dual_infeasibility = line[55:end]
            end

            if occursin("Constraint violation....", line)
                constraint_violation = line[55:end]
            end

            if occursin("Overall NLP error....", line)
                overall_nlp = line[55:end]
            end

            if occursin("Complementarity..", line)
                complementarity = line[55:end]
            end

            if occursin("Variable bound violation", line)
                var_bound_violation = line[55:end]
            end

            if occursin("EXIT:", line)
                esti_Optimal = line[7:end]
            end

        end

    end

    return obj_val, Optimal, solve_time, primal_feasibility, dual_feasibility, time_limit, network_data_new["load"], r_ipopt["solution"]["bus"], r_ipopt["solution"]["gen"], r_ipopt["solution"]["branch"], esti_obj_val, dual_infeasibility, constraint_violation, overall_nlp, complementarity, var_bound_violation,esti_Optimal
end


function event_occurred(obj::FieldEventDetector, arg0::FieldSpacecraftState, arg1::jboolean)
    return jcall(obj, "eventOccurred", Action, (FieldSpacecraftState, jboolean), arg0, arg1)
end

function g(obj::FieldEventDetector, arg0::FieldSpacecraftState)
    return jcall(obj, "g", RealFieldElement, (FieldSpacecraftState,), arg0)
end

function get_max_check_interval(obj::FieldEventDetector)
    return jcall(obj, "getMaxCheckInterval", RealFieldElement, ())
end

function get_max_iteration_count(obj::FieldEventDetector)
    return jcall(obj, "getMaxIterationCount", jint, ())
end

function get_threshold(obj::FieldEventDetector)
    return jcall(obj, "getThreshold", RealFieldElement, ())
end

function init(obj::FieldEventDetector, arg0::FieldSpacecraftState, arg1::FieldAbsoluteDate)
    return jcall(obj, "init", void, (FieldSpacecraftState, FieldAbsoluteDate), arg0, arg1)
end

function reset_state(obj::FieldEventDetector, arg0::FieldSpacecraftState)
    return jcall(obj, "resetState", FieldSpacecraftState, (FieldSpacecraftState,), arg0)
end