@testset "rbs.jl" begin
    @testset "expandRbs!()" begin
        ### Checking status ###
        # :NodeLimitStop

        search=SeaPearl.staticRBSearch{SeaPearl.InfeasibleNodeCriteria}(10, 10)
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)
        model.limit.numberOfNodes = 1
        toCall = Stack{Function}()
        @test SeaPearl.expandRbs!(toCall, model, 1, search, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :NodeLimitStop
        @test isempty(toCall)

        # :SolutionLimitStop
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)
        model.limit.numberOfSolutions = 0

        toCall = Stack{Function}()
        @test SeaPearl.expandRbs!(toCall, model, 1, search, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :SolutionLimitStop
        @test isempty(toCall)

        #:TimeLimitStop
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)
        model.limit.searchingTime = 0

        toCall = Stack{Function}()
        SeaPearl.tic()
        @test SeaPearl.expandRbs!(toCall, model, 1, search, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic())  == :TimeLimitStop
        @test isempty(toCall)

        # :Infeasible
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(3, 3, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        toCall = Stack{Function}()
        @test SeaPearl.expandRbs!(toCall, model, 1, search, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :Infeasible
        @test isempty(toCall)

        # :Feasible
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(2, 2, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        toCall = Stack{Function}()
        @test SeaPearl.expandRbs!(toCall, model, 1, search, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :FoundSolution
        @test isempty(toCall)

        ### Checking stack ###
        search=SeaPearl.staticRBSearch{SeaPearl.InfeasibleNodeCriteria}(10, 10)

        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 3, "x", trailer)
        y = SeaPearl.IntVar(2, 3, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        toCall = Stack{Function}()
        @test SeaPearl.expandRbs!(toCall, model, 10, search, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :Feasible
        @test length(toCall) == 6

        @test pop!(toCall)(model) == :SavingState
        @test length(model.trailer.prior) == 1 # saveState!()

        @test pop!(toCall)(model) == :FoundSolution
        @test length(model.statistics.solutions) == 1 # Found a solution

        @test pop!(toCall)(model) == :BackTracking
        @test length(model.trailer.prior) == 0 # restoreState!()

        @test pop!(toCall)(model) == :SavingState
        @test length(model.trailer.prior) == 1 # saveState!()

        @test pop!(toCall)(model) == :FoundSolution
        @test length(model.statistics.solutions) == 2 # Found another solution

        @test pop!(toCall)(model) == :BackTracking
        @test length(model.trailer.prior) == 0 # restoreState!()
    end

    @testset "initroot(::staticRBSearch)" begin
        # :Feasible
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(2, 2, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        toCall = Stack{Function}()
        @test SeaPearl.initroot!(toCall, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10, 5), model, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :FoundSolution
        @test length(toCall) == 4
        @test pop!(toCall).nodeLimitList[2] == 10
        @test pop!(toCall).nodeLimitList[3] == 10
        @test pop!(toCall).nodeLimitList[4] == 10
        @test pop!(toCall).nodeLimitList[5] == 10

    end

    @testset "initroot(::geometricRBSearch)" begin
        # :Feasible
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(2, 2, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        toCall = Stack{Function}()
        @test SeaPearl.initroot!(toCall, SeaPearl.geometricRBSearch{SeaPearl.VisitedNodeCriteria}(10, 5, 1.1), model, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic())== :FoundSolution
        @test length(toCall) == 4
        @test pop!(toCall).nodeLimitList[2] == 11
        @test pop!(toCall).nodeLimitList[3] == 13
        @test pop!(toCall).nodeLimitList[4] == 14
        @test pop!(toCall).nodeLimitList[5] == 15

        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(2, 2, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        toCall = Stack{Function}()
        @test SeaPearl.initroot!(toCall, SeaPearl.geometricRBSearch{SeaPearl.VisitedNodeCriteria}(10, 1, 1.1), model, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic())== :FoundSolution
        @test isempty(toCall)
    end

    @testset "initroot(::lubyRBSearch)" begin   # The Luby sequence is a sequence of the following form: 1,1,2,1,1,2,4,1,1,2,1,1,2,4,8, . .
        # :Feasible
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(2, 2, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        toCall = Stack{Function}()
        @test SeaPearl.initroot!(toCall, SeaPearl.lubyRBSearch{SeaPearl.VisitedNodeCriteria}(10,5), model, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic())== :FoundSolution
        @test length(toCall) == 4
        @test pop!(toCall).nodeLimitList[2] == 10
        @test pop!(toCall).nodeLimitList[3] == 20
        @test pop!(toCall).nodeLimitList[4] == 10
        @test pop!(toCall).nodeLimitList[5] == 10

        @test SeaPearl.generateLimitList(SeaPearl.lubyRBSearch{SeaPearl.VisitedNodeCriteria}(10,31))==[10,10,20,10,10,20,40,10,10,20,10,10,20,40,80,10,10,20,10,10,20,40,10,10,20,10,10,20,40,80,160]
    end

    @testset "Stopping Criteria" begin
        @testset "InfeasibleNodeCriteria" begin

            criteria = SeaPearl.staticRBSearch{SeaPearl.InfeasibleNodeCriteria}(1, 0)

            trailer = SeaPearl.Trailer()
            model = SeaPearl.CPModel(trailer)

            x = SeaPearl.IntVar(2, 2, "x", trailer)
            y = SeaPearl.IntVar(3, 3, "y", trailer)
            SeaPearl.addVariable!(model, x)
            SeaPearl.addVariable!(model, y)
            SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

            toCall = Stack{Function}()
            @test SeaPearl.expandRbs!(toCall, model, 1, criteria, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :Infeasible
            @test criteria(model,1) == false #one infeasible state has been reached, so the search stops.
        end
        @testset "VisitedNodeCriteria" begin

            criteria = SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(1, 0)

            trailer = SeaPearl.Trailer()
            model = SeaPearl.CPModel(trailer)

            x = SeaPearl.IntVar(2, 3, "x", trailer)
            y = SeaPearl.IntVar(2, 3, "y", trailer)
            SeaPearl.addVariable!(model, x)
            SeaPearl.addVariable!(model, y)
            SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

            toCall = Stack{Function}()
            @test SeaPearl.expandRbs!(toCall, model, 1, criteria, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :Feasible
            @test criteria(model,1) == true    #only one node has been visited
            pop!(toCall)(model) # :SavingState
            pop!(toCall)(model) # expandRbs!(...)
            @test criteria(model,1) == false
        end

        @testset "SolutionFoundCriteria" begin

            criteria = SeaPearl.staticRBSearch{SeaPearl.SolutionFoundCriteria}(2, 0)

            trailer = SeaPearl.Trailer()
            model = SeaPearl.CPModel(trailer)

            x = SeaPearl.IntVar(2, 3, "x", trailer)
            y = SeaPearl.IntVar(2, 3, "y", trailer)
            SeaPearl.addVariable!(model, x)
            SeaPearl.addVariable!(model, y)
            SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

            toCall = Stack{Function}()
            @test SeaPearl.expandRbs!(toCall, model, 2, criteria, SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :Feasible
            @test criteria(model,2) == true
            pop!(toCall)(model) # :SavingState
            @test pop!(toCall)(model) == :FoundSolution
            @test criteria(model,2) == true  #One solution found
            pop!(toCall)(model) # :Backtrack
            pop!(toCall)(model) # :SavingState
            @test pop!(toCall)(model) == :FoundSolution
            @test criteria(model,2) == false #Two solution found
        end
    end

    @testset "search!(::RBSearch)" begin
        ### Checking status ###
        # :LimitStop
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)
        model.limit.numberOfNodes = 1
        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection()) == :NodeLimitStop

        # :SolutionLimitStop
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)
        model.limit.numberOfSolutions = 0

        x = SeaPearl.IntVar(2, 3, "x", trailer)
        y = SeaPearl.IntVar(2, 3, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection()) == :SolutionLimitStop

        # :Infeasible
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(3, 3, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection()) == :Infeasible

        # :Optimal
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 2, "x", trailer)
        y = SeaPearl.IntVar(2, 2, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        push!(model.constraints, SeaPearl.Equal(x, y, trailer))
        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection()) == :NonOptimal
        @test length(model.statistics.solutions) == 1

        ### Checking more complex solutions ###
        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 3, "x", trailer)
        y = SeaPearl.IntVar(2, 3, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection()) == :NonOptimal
        @test length(model.statistics.solutions) == 2
        @test model.statistics.solutions[1] == Dict("x" => 3,"y" => 3)
        @test model.statistics.solutions[2] == Dict("x" => 2,"y" => 2)

    end

    @testset "search!() with a BasicHeuristic" begin

        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 3, "x", trailer)
        y = SeaPearl.IntVar(2, 3, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))

        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic()) == :NonOptimal
        @test model.statistics.solutions[1] == Dict("x" => 3,"y" => 3)
        @test model.statistics.solutions[2] == Dict("x" => 2,"y" => 2)

        SeaPearl.empty!(model)

        x = SeaPearl.IntVar(2, 3, "x", model.trailer)
        y = SeaPearl.IntVar(2, 3, "y", model.trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, model.trailer))

        my_heuristic(x::SeaPearl.IntVar; cpmodel=nothing) = minimum(x.domain)
        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic(my_heuristic)) == :NonOptimal
        @test model.statistics.solutions[1] == Dict("x" => 2,"y" => 2)
        @test model.statistics.solutions[2] == Dict("x" => 3,"y" => 3)

    end

    @testset "search!() with a BasicHeuristic - out_solver being true" begin

        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x = SeaPearl.IntVar(2, 3, "x", trailer)
        y = SeaPearl.IntVar(2, 3, "y", trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, trailer))
        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic(); out_solver=true) == :FoundSolution
        @test length(model.statistics.solutions) == 1
        @test model.statistics.solutions[1] == Dict("x" => 3,"y" => 3)

        SeaPearl.empty!(model)

        x = SeaPearl.IntVar(2, 3, "x", model.trailer)
        y = SeaPearl.IntVar(2, 3, "y", model.trailer)
        SeaPearl.addVariable!(model, x)
        SeaPearl.addVariable!(model, y)
        SeaPearl.addConstraint!(model, SeaPearl.Equal(x, y, model.trailer))

        my_heuristic(x::SeaPearl.IntVar; cpmodel=nothing) = minimum(x.domain)
        @test SeaPearl.search!(model, SeaPearl.staticRBSearch{SeaPearl.VisitedNodeCriteria}(10,10), SeaPearl.MinDomainVariableSelection(), SeaPearl.BasicHeuristic(my_heuristic); out_solver=true) == :FoundSolution
        @test length(model.statistics.solutions) == 1
        @test model.statistics.solutions[1] == Dict("x" => 2,"y" => 2)

    end

    @testset "search!() with PPO/RBS" begin

    using Random
    numInFeatures = 3
    approximator_GNN = GeometricFlux.GraphConv(64 => 64, Flux.leakyrelu)
    target_approximator_GNN = GeometricFlux.GraphConv(64 => 64, Flux.leakyrelu)
    gnnlayers = 10
    seed = 123
    rng = MersenneTwister(seed)

    actor_approximator = SeaPearl.CPNN(
        graphChain = Flux.Chain(
            GeometricFlux.GraphConv(numInFeatures => 64, Flux.leakyrelu),
            [approximator_GNN for i = 1:gnnlayers]...
        ),
        nodeChain = Flux.Chain(
            Flux.Dense(64, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
            Flux.Dense(32, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
            Flux.Dense(32, 16, Flux.leakyrelu; initW = glorot_uniform(rng)),
        ),
        outputChain = Flux.Dense(16, 4; initW = glorot_uniform(rng)),
    )
    critic_approximator = SeaPearl.CPNN(
        graphChain = Flux.Chain(
            GeometricFlux.GraphConv(numInFeatures => 64, Flux.leakyrelu),
            [target_approximator_GNN for i = 1:gnnlayers]...
        ),
        nodeChain = Flux.Chain(
            Flux.Dense(64, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
            Flux.Dense(32, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
            Flux.Dense(32, 16, Flux.leakyrelu; initW = glorot_uniform(rng)),
        ),
        outputChain = Flux.Dense(16, 1; initW = glorot_uniform(rng)),
    )

    UPDATE_FREQ = 1

    # Agent definition
    agent = RL.Agent(
        policy = RL.QBasedPolicy(
            learner = RL.A2CLearner(
                approximator =RL.ActorCritic(
                    actor = actor_approximator,
                    critic = critic_approximator,
                    optimizer = ADAM(1e-3),
                ) |> cpu,
                γ = 0.99f0,
                actor_loss_weight = 1.0f0,
                critic_loss_weight = 0.5f0,
                entropy_loss_weight = 0.001f0,
                update_freq = UPDATE_FREQ,

            ),
            explorer = GumbelSoftmaxExplorer(),
            ),
        trajectory = RL.CircularArraySARTTrajectory(
            capacity = 8,
            state = SeaPearl.DefaultTrajectoryState => (),
            action = Vector{Int} => (1,),
            reward = Vector{Float32} => (1,),
            terminal = Vector{Bool} => (1,),
        )
    )

        # define the value selection
        valueSelection = SeaPearl.LearnedHeuristic(agent)

        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x1 = SeaPearl.IntVar(1, 2, "x1", trailer)
        x2 = SeaPearl.IntVar(1, 2, "x2", trailer)
        x3 = SeaPearl.IntVar(2, 3, "x3", trailer)
        x4 = SeaPearl.IntVar(1, 4, "x4", trailer)
        SeaPearl.addVariable!(model, x1)
        SeaPearl.addVariable!(model, x2)
        SeaPearl.addVariable!(model, x3)
        SeaPearl.addVariable!(model, x4)

        SeaPearl.addConstraint!(model, SeaPearl.NotEqual(x1, x2, trailer))
        SeaPearl.addConstraint!(model, SeaPearl.NotEqual(x2, x3, trailer))
        SeaPearl.addConstraint!(model, SeaPearl.NotEqual(x3, x4, trailer))

        # define the variable selection
        variableSelection = SeaPearl.MinDomainVariableSelection()

        # launch the search
        SeaPearl.search!(model, SeaPearl.geometricRBSearch{SeaPearl.InfeasibleNodeCriteria}(3,10,1.1), variableSelection, valueSelection)

        possible_solutions = [
            Dict("x1" => 1, "x2" => 2, "x3" => 3, "x4" => 1),
            Dict("x1" => 1, "x2" => 2, "x3" => 3, "x4" => 2),
            Dict("x1" => 1, "x2" => 2, "x3" => 3, "x4" => 4),
            Dict("x1" => 2, "x2" => 1, "x3" => 3, "x4" => 1),
            Dict("x1" => 2, "x2" => 1, "x3" => 3, "x4" => 2),
            Dict("x1" => 2, "x2" => 1, "x3" => 3, "x4" => 4),
            Dict("x1" => 2, "x2" => 1, "x3" => 2, "x4" => 1),
            Dict("x1" => 2, "x2" => 1, "x3" => 2, "x4" => 3),
            Dict("x1" => 2, "x2" => 1, "x3" => 2, "x4" => 4)
        ]

        for solution in model.statistics.solutions
            @test solution in possible_solutions
        end

    end

    @testset "search!() with a PPO/RBS" begin

        using Random
        numInFeatures = 3
        approximator_GNN = GeometricFlux.GraphConv(64 => 64, Flux.leakyrelu)
        target_approximator_GNN = GeometricFlux.GraphConv(64 => 64, Flux.leakyrelu)
        gnnlayers = 10
        seed = 123
        rng = MersenneTwister(seed)

        actor_approximator = SeaPearl.CPNN(
            graphChain = Flux.Chain(
                GeometricFlux.GraphConv(numInFeatures => 64, Flux.leakyrelu),
                [approximator_GNN for i = 1:gnnlayers]...
            ),
            nodeChain = Flux.Chain(
                Flux.Dense(64, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
                Flux.Dense(32, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
                Flux.Dense(32, 16, Flux.leakyrelu; initW = glorot_uniform(rng)),
            ),
            outputChain = Flux.Dense(16, 4; initW = glorot_uniform(rng)),
        )
        critic_approximator = SeaPearl.CPNN(
            graphChain = Flux.Chain(
                GeometricFlux.GraphConv(numInFeatures => 64, Flux.leakyrelu),
                [target_approximator_GNN for i = 1:gnnlayers]...
            ),
            nodeChain = Flux.Chain(
                Flux.Dense(64, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
                Flux.Dense(32, 32, Flux.leakyrelu; initW = glorot_uniform(rng)),
                Flux.Dense(32, 16, Flux.leakyrelu; initW = glorot_uniform(rng)),
            ),
            outputChain = Flux.Dense(16, 1; initW = glorot_uniform(rng)),
        )

        UPDATE_FREQ = 1

        # Agent definition   //     PPO
        agent = RL.Agent(
            policy = RL.QBasedPolicy(
                learner = RL.A2CLearner(
                    approximator =RL.ActorCritic(
                        actor = actor_approximator,
                        critic = critic_approximator,
                        optimizer = ADAM(1e-3),
                    ) |> cpu,
                    γ = 0.99f0,
                    actor_loss_weight = 1.0f0,
                    critic_loss_weight = 0.5f0,
                    entropy_loss_weight = 0.001f0,
                    update_freq = UPDATE_FREQ,

                ),
                explorer = GumbelSoftmaxExplorer(),
                ),
            trajectory = RL.CircularArraySARTTrajectory(
                capacity = 8,
                state = SeaPearl.DefaultTrajectoryState => (),
                action = Vector{Int} => (1,),
                reward = Vector{Float32} => (1,),
                terminal = Vector{Bool} => (1,),
            )
        )

        # define the value selection
        valueSelection = SeaPearl.LearnedHeuristic(agent)

        trailer = SeaPearl.Trailer()
        model = SeaPearl.CPModel(trailer)

        x1 = SeaPearl.IntVar(1, 2, "x1", trailer)
        x2 = SeaPearl.IntVar(1, 2, "x2", trailer)
        x3 = SeaPearl.IntVar(2, 3, "x3", trailer)
        x4 = SeaPearl.IntVar(1, 4, "x4", trailer)
        SeaPearl.addVariable!(model, x1)
        SeaPearl.addVariable!(model, x2)
        SeaPearl.addVariable!(model, x3)
        SeaPearl.addVariable!(model, x4)

        SeaPearl.addConstraint!(model, SeaPearl.NotEqual(x1, x2, trailer))
        SeaPearl.addConstraint!(model, SeaPearl.NotEqual(x2, x3, trailer))
        SeaPearl.addConstraint!(model, SeaPearl.NotEqual(x3, x4, trailer))

        # define the variable selection
        variableSelection = SeaPearl.MinDomainVariableSelection()

        # launch the search
        SeaPearl.search!(model, SeaPearl.geometricRBSearch{SeaPearl.InfeasibleNodeCriteria}(3,10,1.1), variableSelection, valueSelection; out_solver=true) #In simple example, we never get an infeasible state

        possible_solutions = [
            Dict("x1" => 1, "x2" => 2, "x3" => 3, "x4" => 1),
            Dict("x1" => 1, "x2" => 2, "x3" => 3, "x4" => 2),
            Dict("x1" => 1, "x2" => 2, "x3" => 3, "x4" => 4),
            Dict("x1" => 2, "x2" => 1, "x3" => 3, "x4" => 1),
            Dict("x1" => 2, "x2" => 1, "x3" => 3, "x4" => 2),
            Dict("x1" => 2, "x2" => 1, "x3" => 3, "x4" => 4),
            Dict("x1" => 2, "x2" => 1, "x3" => 2, "x4" => 1),
            Dict("x1" => 2, "x2" => 1, "x3" => 2, "x4" => 3),
            Dict("x1" => 2, "x2" => 1, "x3" => 2, "x4" => 4)
        ]

        for solution in model.statistics.solutions
            @test solution in possible_solutions
        end

    end

end


module config

# This is the main configuration file for the whole of the kokako-detect project.
# Any constants and parameters that need to be changed must be changed here only.
# Likewise the addition of any such parameters should also be added here.
# Author: Tim Evans
# Date: 30/07/14

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# paths and constants pertaining to extracting features
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

export wavdir, traindir, speclength, path_rel, test_size, specwidth, labelleddir,datafile,
ntrees, ratio, subsampleratio, modeldir, specdir,boxwidth,boxheight,thresh,kernwidth,kernsigma,
minarea

#the following paths are all relative to the project directory and will be concatenated
#To use absolute paths sets path_rel = false
path_rel = true
wavdir = "data/wavs/"
traindir = "data/training/"
specdir = "data/spectrograms/"
labelleddir = "data/labelled/"
datafile = "data.csv"

#------------------------------------------------------------------------------
#                           filter parameters
#------------------------------------------------------------------------------
fc = 1250
npoles = 4
speclength = 10 #seconds
specwidth = 930 #setting the width of spectrograms produced

#------------------------------------------------------------------------------
#                       feature extraction paramaters
#------------------------------------------------------------------------------
boxwidth = 3
boxheight = 10
# This determines how much we will subsample the noise and labelled noise in our
# dataset. E.g. if we have 30 observations of bird call pixels, and subsampleratio = 0.5
# then we will add 15 labelled noise, and 15 unlabelled noise to the dataset
subsampleratio = 1

#------------------------------------------------------------------------------
#                          Random Forest parameters
#------------------------------------------------------------------------------
modeldir = "data/model/"
ntrees = 10;
ratio = 0.2;

#------------------------------------------------------------------------------
#                prediction parameters (post-processing)
#------------------------------------------------------------------------------
thresh = 0.6
kernwidth = 5 # Gaussian kernel parameters
kernsigma = 2
minarea = 16 # minimum sized syllable to consider as a bird

end


# Chance Sampled Monte Carlo Counterfactual Regret Minimization
# Johanson et al 2012 Algorithm 1
# http://modelai.gettysburg.edu/2013/cfr/cfr.pdf page 12 is also useful

# set up distributed processing

# add packages
using Pkg
Pkg.activate(pwd())
using Distributed
@everywhere using Random, StatsBase

# include other files
include("helper.jl")

# Define variables
N = 1:2 # number of players
I = 1:10 # information sets
A = [[1,2,3], [1,2], [1,2,3,4], [1,3,5], [1,2,6], [3,4,5], [4,6], [6], [4,5,6], [6]] # actions
actor = [1,2,1,2,1,2,1,1,1,1] # decision maker at each info set
ni = size(I,1) # number of info sets
na = sum.(A) # number of actions at each info set
regret = [zeros(na[i]) for i in 1:ni] # player 1 regret
strategy = [zeros(na[i]) for i in 1:ni] # cumulative strategies
σ1 = [fill(1 / na[i], na[i]) for i in 1:ni] # player 1 strategy
σ2 = [fill(1 / na[i], na[i]) for i in 1:ni] # player 2 strategy
σ = (σ1, σ2)

# sample tree
leaf1 = Node(1, [])
leaf2 = Node(3, [])
leaf3 = Node(4, [])
n = Node(2, [leaf2, leaf3])
head = Node(0, [leaf1, n])
nh = nnodes(head)
H, Z = gametree()

# constants
H, Z, C, P # all history nodes, terminal nodes, chance nodes, public nodes
S = [S[i] for i in N] # chance nodes only observable to player i
O = [O[i] for i in N] # chance nodes only observable to opponent of player i
# note: C = S1 ∪ O1 ∪ P and also C = S2 ∪ O2 ∪ P
Πi = [π1, π2] # contribution of player i to prob(h)
Πo = [πo1, πo2] # contribution of all other players than i to prob(h)
I1 = Vector{Vector{Node}}(undef, ni)
I2 = Vector{Vector{Node}}(undef, ni)
Iset = [I1, I2] # info sets
AI # dict from info set to action
Uz = [Uz1, Uz2] # utility of each leaf node for each player

function gametree
    # build h nodes into tree
    # record Z while doing this
    H, Z
end

function chance(h::Node, a::Int)
    1 / length(children(h)) # need to make this actually match public chance
end

function leafutility(h::Node, i::Int)
    Uz[i][idnode(h)]
end

function sampleoutcome(h::Node, πi::Float64, πo::Float64)
    if h ∈ C
        nextnodes = children(h)
        chance_weights = [chance(h, a) for a in actions]
        ha = sample(nextnodes, chance_weights)
        return updatetree!(ha, i, πi, πo)
    end
end

function infoset(h::Node)
    idinfo(h), Iset[idactor(h)][idinfo(h)]
end

function matchregret(I::Vector{Node})
    ind = findfirst(isequal(I), Iset)
    na = length(AI[ind]})
    σ = Vector{Float64}(undef, na)
    for a in AI[ind]
        denom = sum(b * max(r[ind][b], 0) for b in AI[ind])
        if denom > 0
            σ[a] = max(r[ind][a], 0) / denom
        else
            σ[a] = 1 / length(AI[ind])
        end
    end
    σ
end

function updateutility!(i::Int, ind::Int, h::Node, σ::Vector{Float64},
        u::Float64, πi::Float64, πo::Float64)
    na = length(AI[ind])
    m = Vector{Float64}(undef, na)
    for a in AI[ind]
        ind_ha = findfirst(x -> x == a, nextactions(h))
        ha = children(h)[ind_ha]
        if idactor(h) == i
            πip = σ[a] * πi
            up = updatetree(ha, i, πip, πo)
            m[a] = up
            u = u + σ[a] * up
        else
            πop = σ[a] * πo
            up = updatetree(ha, i, πi, πop)
            u = u + up
        end
    end
    m
end

function updateregret!(i::Int, ind::Int, σ::Vector{Float64},
        u::Float64, m::Vector{Float64}, πi::Float64,
        r::Vector{Vector{Float64}}, s::Vector{Vector{Float64}})
    if idactor(h) == i
        for a in AI[ind]
            r[ind][a] = r[ind][a] + m[a] - u
            s[ind][a] = s[ind][a] + πi * σ[a]
        end
    end
end

function updatetree(h::Node, i::Int, πi::Float64, πo::Float64)
    # πo is short for \vec{π}_{-i}
    h ∈ Z && return leafutility(h, i)
    sampleoutcome(h, πi, πo)
    indI, I = infoset(h)
    u = 0
    σ = matchregret(I)
    m = updateutility!(i, indI, h, σ, u, πi, πo)
    updateregret!(i, indI, σ, m, πi, r, s)
    u
end

function cfr(T::Int, ni::Int)
    r = [zeros(na[i]) for i in 1:ni] # regret
    s = [zeros(na[i]) for i in 1:ni] # cumulative strategies
    for t in 1:T
        for i in N
            u = updatetree!([], i, 1, 1, r, s) # utility
        end
    end
    u, r, s
end


input = """100100110110
101110110110
010100010100
011001110000
000000000111
000010110001
001111000001
100010000001
010100110011
010000010110
010000000011
010101001000
011011101100
011100110111
011011000000
001010010010
011011111010
011110110110
000000100010
111111010101
011100010011
001110110100
110110010100
100000001011
100100010011
011011010001
111000101110
101100001111
110101010101
011010001001
101111011000
011111111011
110000100101
011101101101
011010011001
111100100001
100000010000
111010001010
111110010001
110110001101
000001000001
000101011000
111101100100
110011001001
100010100011
011011100010
011000101101
110101101011
111110101000
010111110100
010010101011
100001001000
101011101011
110111000001
011100011110
011001101101
010100001111
001111011000
100111111010
110110000100
000010101100
111010111011
110110110110
110111011000
100001011001
110010011111
100000000101
110000101100
110100010110
101001101100
111000100011
011001001101
000111100111
001000111110
110100100010
000110011100
010011101111
010101000111
101100100001
100110000010
001110100101
010100100100
001110010110
000010111010
101011101001
101111101010
101011100110
011001111010
100110101010
101111101110
100000100011
000101001011
000110110000
110111101111
111110010110
100010010111
011100000101
101010011010
010111110010
000011111101
000001101000
000111001001
101000001011
011001101000
010110101110
000111101000
101100100011
011010010011
110101111110
101011110001
111000011101
111110011100
100110110001
110111110011
000000011010
000100001110
011100001111
010110010111
011000100110
011000111000
010001000111
100010101110
011001111011
010111100001
011101101110
101100010110
100010101011
110101000010
111010001101
111001100111
101001110111
011000110010
011001111100
010001000110
011011100101
101111010001
100110001011
001011000010
010100011111
101111100110
011100000010
011001000010
010011011111
001101000010
110110010110
001000101001
101100011000
000111000110
000001001101
000100100111
110111011100
010100101001
010101110000
110000001110
010011010000
011101110000
001111100000
010011000011
010011010011
101110010000
101100000110
110011101101
010011111101
101011010110
001011011100
111111011001
011011111000
011101110011
010011001101
100011001000
011010111011
111101000110
100110001101
101001101011
100000111100
111101001011
010111010111
101110000000
010010111111
100010110010
101111110100
111101110111
111111110010
110110001100
101010101111
000110101000
100110110100
100101011010
001001011000
111010001011
000000110100
111000010010
101010011101
010110000010
011111110111
101011111101
011010100111
100011011101
110110011110
101111100111
001100001111
011111101011
100101000100
100101010010
011110111010
010110001001
010101101010
001111000101
010001001100
001010000010
010000001101
110000100110
111100001101
101010001001
110011111111
010111011001
000111101111
111110000011
011111000011
011110011011
100011010100
000011000110
011100101000
010101001111
010110010110
111111000111
101011001001
011100010111
000000001100
011001001000
110101101111
101101011001
000111011000
000010001111
111110011011
010111011111
011101110100
001011100001
001001011100
110100111011
100101011111
000111001101
011110100101
111100011010
111000110100
010001010101
100001000100
000000101110
011011001110
111010100111
100110110000
110110110001
001110001001
110010111111
000111001011
011001011111
000010001011
100010101001
100101100101
010110011001
101101011110
101011100101
101110110101
111001100001
001000101010
101011010011
110011101100
101100001101
101111101001
111100000010
100111000000
010010000100
001001001001
000110111110
011010001110
100011110000
110111101000
110000010101
011011101011
101000010001
110110110010
100000000001
011011010101
000111111101
100001111011
100100000110
001111100010
110010000011
101101111001
110011010110
010001011011
010010011010
001110011110
100111010010
110011011010
111100010011
111011011101
100001010110
101011001111
001001000000
101011100010
010000110011
100111110000
010010101110
110000110011
110100010111
001011110010
101011000111
000101110011
010001111011
000010011011
011000001011
000000100100
001101111001
011000000101
100100001101
101001000011
111100100111
111000111111
011000110011
101011011110
101110000100
010101000001
110111111100
100000011011
101001011010
111011000110
110111101011
011000101100
100001111111
111011100101
110111110101
101011111010
010001101100
111000111001
110001000011
100000110100
010110100010
100011011111
001111111001
101101011011
011101011000
100000111000
101000000110
010110011101
110010101101
011110000010
100011011010
110010110101
110111100011
110100110000
101010011100
001010010110
100110011110
101100100010
110101111001
111110000000
011000111111
100100100000
111111101111
001011110101
101110000001
101111011110
011010111001
011111010111
000111000101
000100010001
111100100101
100110000100
001000001111
001001100100
001000000110
110010100100
111110110101
010010111001
110111000110
011101000000
100001101100
111101000111
000101111111
001111010100
101001100100
111111110001
010010010001
111011101010
001110101000
111101100001
100001000001
111001111010
100101000011
000001110011
000111100001
110010011110
111111101011
000101001001
000101111101
011101011001
100100000111
110000001011
001111100110
110010001101
011101011011
011100111111
000001010000
011001010101
001111001010
010111000000
001101101101
011111000111
000111011101
100100111100
011101011111
111000000111
100111101101
001001111001
010001100101
101111100001
000010101101
010001011111
000111101010
011101111101
111111110101
011111001101
110101001100
011110001110
001001010000
010001010001
011000010010
100010000100
100100100001
011101101111
111000101111
001101110110
110011110000
000111011111
011100110010
001101101110
101010100010
111110110010
111101000100
001010101000
001100101001
111010101000
010110000100
110111001000
111100000110
001110001011
011111111100
110011100000
001010011100
110011001000
001001110101
001010110110
110010100101
100000111111
110010000100
110110100100
001011101101
000110000010
001100000111
010011100110
001010101011
111001101000
110111010010
110111001100
010000111100
010111100110
001111111111
011010111010
100111011100
011011011010
001000001110
111010100101
100001011000
011001110011
100111011000
111111101000
010111011010
111011111000
001010101110
000010010111
110011000010
100000111010
110000001111
011110111101
101011011001
011110011111
100001111010
111001010110
100001010000
011000100000
111001011000
000100110011
010110111101
010111000100
100111011101
100000011010
010110111001
010101001110
110001001001
111100010001
111010110011
100100010100
000100100000
100100110010
111011100000
111010110010
110110100110
001001010001
010110100111
111001101100
000111111001
011100000110
011011010010
001010111000
001100000010
100001110001
011101110111
010101010100
111100111001
010000010101
011110011110
000011110100
000111100100
100100011000
101000011011
111010000100
000001000111
111111011111
000111010101
000111100101
000000001101
000011100001
101000100111
100111010001
001110101010
101101110001
101000111001
001101100011
101110000111
100111110010
010001111111
100111000010
100111101111
111011110110
100111000100
000011111110
001010000101
111011111110
010010101111
010011001110
110100000001
110101110001
000010001101
000110001010
101111110010
111111001000
001001100001
110001110100
110111111011
000010000101
111100111000
010000000010
001110111001
111000110111
000010000001
111111010010
010010001010
001110010111
000111111110
100101000001
000001110111
101000010000
100011011001
010000101101
011101000110
110011001011
110100011010
100111000101
011000000100
110010101110
001000101110
100010111111
100011111011
001101010001
111000100100
100101100100
001000100001
000011000101
001000110001
110110011011
100110010111
110000011101
000010000010
011111001111
000100111000
100001011011
110010000000
000100010100
011000101001
100111011011
110000101101
111000110011
101100010111
101001100110
110011111011
100110100110
100111111111
100100101010
001000011111
000110101101
110111100001
100111000110
100111110110
111111100000
100010001011
100000000010
110001001000
101101001110
111101110011
010111001111
011111010100
110111000101
010010001110
010111100010
111110010011
001100011011
100000110111
010111110110
001100011101
010010010111
110011001101
000111101110
011000001111
010100000000
110001101001
101101010111
110111010100
101010010111
001110111110
101001000111
011010010110
010000101100
110000111111
011101010000
010110001101
010101000101
101101001000
100010110001
101000110101
111101010101
010011111100
100010100110
011001111001
000110001001
101100000101
000101011010
100100011110
100000110011
100011000101
110001011010
111100000111
001100110111
011010110000
011000000111
111101101011
011101110101
010100011110
010101100011
000100010000
010111001011
101110100001
110110110101
000010100111
001011000110
110110101100
010001100010
011110110111
110100001100
000110001100
001101001100
101010111011
000001101100
011110101101
110100000000
011011100111
000010101001
100111110101
000101000101
010011010111
111101010100
110110101101
110011011100
110011011001
000111010111
110100011110
000110011010
110110100000
111110101101
001100111000
101111000110
011011000010
000000001001
010100111110
100010000110
001110111101
010001011100
101001111101
110101110000
001111101111
100101111101
110100111101
000100001000
111011110101
001011001000
011101010111
101000000101
011111111001
111001011011
010001110001
010001110011
001100111001
100101110010
100010100010
110011111001
111011101011
001101110011
000011011010
101000010111
000011010001
101000010010
000011101111
011100001100
110110011001
011101000001
010011011001
111010010011
001011000111
010111100101
101001111011
101111011011
011011001100
010100110000
000111100011
010111111011
100101101010
010001100000
111110100011
100101011101
011011011011
111101011001
001000011101
101111110011
100100100110
010111100111
110000001101
000001010011
100001010100
101101000100
110100101101
110001100011
110000111010
101111011010
011011100000
100101100110
010110010001
101000011000
011001110100
001110001100
100100110100
001110101011
000110000100
101000110001
101101110010
011100001110
111011010011
011001110001
110101100000
110000010011
010111101111
101111101111
010110100000
111111011011
100110000110
011000111100
110010111110
001000001101
011101011010
001010110101
100110001100
101000000011
110001010011
100001011111
000001101001
100001101101
011100100000
011011000110
001110111011
100111100101
001110000000
110101001101
101100101100
001110000001
110000011010
101100110010
011010100010
111101111010
110001111100
011110101000
110011011000
110100000111
100000000111
101100101110
011001000011
011011100110
101001000001
001100100101
011011110011
110110001011
000101011110
010011110101
110101001010
111110101010
110111010001
010111001101
001101011000
011001011011
011101000010
101010111110
010010000101
100100010010
110010000010
000000001110
100110011100
000110100111
000000011011
111011010000
100001111000
000010010011
110010000101
011001101111
010010000110
101000110100
010111010110
011010110010
001101110001
001101000100
001001101011
010111110101
100100110101
000101011011
110110000111
000100110001
111111110011
111010110000
000000110001
010011101001
111110011101
101011100100
001010100101
010111111100
010101100100
011010000000
010111111111
111001100011
111001011110
010011111010
001010101100
110001100101
011000011110
001111010010
010110001110
011101011110
101100011010
000000001000
000000000000
100000011000
101000001111
101101010010
001010110010
101111001001
111000000001
010011001010
110000000101
101101101000
110011110111
111101010110
111011011011
100011101100
101100011011
000011010110
111100111101
110000100100
001111110111
011110011101
010001010110
101000001110
101010101110
010110000011
100001100000
000001000011
110011100010
000101000110
010010110110
100111100011
000110100100
110100101110
001101100101
110000100000
110001100110
110010000001
010010011011
101111001010
101010010110
001001001100
011001110010
111010100100
000001000100
010101101001
101001100011
010010100100
010111001010
101000000100
011001010100
101110000110
011010000111
101001110001
111000011110
010011000111
111000100111
010101010011
010010101010
011110011001
110010011101
110100001001
011111001001
011110100011
100100001111
000010100101
110010010111
111111000101
000001010100
001000011110
011101001110
111001100101
111110011110
000110111100
110001001110
011001010111
101100011101
010000001001
100100011101
101001101000
100011110101
100110101101
000001111001
011110010001
001001011001
110111100111
100000010111
011110010010
000011110000
111011110111
101110110010
000111010100
010010101001
000011010010
101010001011
111100101100
111101101111
101001011111
101110001010
010101011110
101001001101
100010111100
011110111001
110100111110
001101000011
000111100010
100111110011
010111011011
101010111100
100111111110
001111001011
000101110001
001000001010
010001101101
101101001101
111100010000
011110000110
100011101110
111100001110
101000100010
101010100101
110011010111
110010101010
110110111111
010010110011
000111110010
111100110011"""


module SegwayRobot3

    include("src3/segway.jl")
    #export Segway_mutable, Segway_unmutable

    include("src3/dynamics_type.jl")
    include("src3/dynamics_vec.jl")
    #export segway_rk4, segway_rk4!, dxdt_segway

    const params_segway = [-1.23, -1.20, -3.70, -7.51, -14.7, -10.2, -11.9, -3.43]
    const s_init  = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    const u_range = [1.0, 1.0]

    include("src3/dynmodel.jl")

    cost_func = segway_cost # set standard Environment cost-function

    function rk4_func(xt::AbstractVector, ut::AbstractVector, dt::Number)

        #run the simulation
        xt1 = dynmodel(xt, ut, dt, params_segway)

        return xt1
    end

    export rk4_func, cost_func, s_init, u_range
end # module SegwayRobot3


#=
Test resynthesizer in 4D using commonly available test data.
=#

# requires pkg OMETIFF
# using Pkg; Pkg.add("OMETIFF")
# OMETIFF supports 4D in .tiff format?

# The tested item
include("../../src/resynthesizer.jl")

using ImageMetadata

# Grab the open source test data
using TestImages
# "multi" is short name
fullImage = testimage("multi");

println("fullImage is:")
println(summary(fullImage))
# 167×439×3×7

#=
See JuliaImages.
The image has named axes.
And metadata??
We only want to convert the data.
Extract: data(fullImage), not fullImage.data??
=#
imageWithAxes = data(fullImage)
println("imageWithAxes is:")
println(summary(imageWithAxes))

imageView = imageWithAxes.data
println("image is:")
println(summary(imageView))

#=
imageView is still reshape(reinterpret(...)) which throws type errors.
Its a view.
So copy it.
=#
image = copy(imageView)
println("typeof copy")
println(typeof(image))

# Create a mask
mask = falses(size(image))
# ??? The third dimension is 3 channels.  Arbitrarily take them all.
mask[3:4, 2:3, 1:3, 5:6] .= true

println("mask is:")
println(summary(mask))

function test(data, mask)
    result = resynthesize(data, mask)

    # Assert result is 3D array

    #=
    Chunk it back into the Image for display.
    =#
    #fullImage.data = result
    # imshow(result)

    # return type so not massive print object
    typeof(result)
end

#imshow requires    using ImageView, Images


function make_S_N_NG86_table(code::GeneticCode)
    f = i -> S_N_NG86(i, code)
    return S_N_NG86_LOOKUP(f)
end

function make_DS_DN_NG86_table(code::GeneticCode)
    f = (i, j) -> DS_DN_NG86(i, j, code)
    return DS_DN_NG86_LOOKUP(f)
end

isinteractive() && info("Compiling lookup tables for NG86...")
const S_N_NG86_LOOKUPS = (function ()
    lookups = Dict{Int, S_N_NG86_LOOKUP}()
    for (i, code) in ncbi_trans_table.tables
        lookups[i] = make_S_N_NG86_table(code)
    end
    return lookups
end)()

const DS_DN_NG86_LOOKUPS = (function ()
    lookups = Dict{Int, DS_DN_NG86_LOOKUP}()
    for (i, code) in ncbi_trans_table.tables
        lookups[i] = make_DS_DN_NG86_table(code)
    end
    return lookups
end)()


"""
# Optim.jl
Welcome to Optim.jl!

Optim.jl is a package used to solve continuous optimization problems. It is
written in Julia for Julians to help take advantage of arbitrary number types,
fast computation, and excellent automatic differentiation tools.

## REPL help
`?` followed by an algorithm name (`?BFGS`), constructors (`?Optim.Options`)
prints help to the terminal.

## Documentation
Besides the help provided at the REPL, it is possible to find help and general
documentation online at http://julianlsolvers.github.io/Optim.jl/stable/ .
"""
module Optim
using Compat
using NLSolversBase          # for shared infrastructure in JuliaNLSolvers

using PositiveFactorizations # for globalization strategy in Newton
import PositiveFactorizations: cholesky!, cholesky

using LineSearches           # for globalization strategy in Quasi-Newton algs

import NaNMath               # for functions that ignore NaNs (no poisoning)

import Parameters: @with_kw, # for types where constructors are simply defined
                   @unpack   # by their default values, and simple unpacking
                             # of fields

using Printf                 # For printing, maybe look into other options

using FillArrays             # For handling scalar bounds in Fminbox

#using Compat                 # for compatibility across multiple julia versions

# for extensions of functions defined in Base.
import Base: length, push!, show, getindex, setindex!, maximum, minimum

# objective and constraints types and functions relevant to them.
import NLSolversBase: NonDifferentiable, OnceDifferentiable, TwiceDifferentiable,
                      nconstraints, nconstraints_x, NotInplaceObjective, InplaceObjective

# var for NelderMead
import StatsBase: var

import LinearAlgebra
import LinearAlgebra: Diagonal, diag, Hermitian, Symmetric,
                      rmul!, mul!,
                      norm, normalize!,
                      diagind,
                      eigen, BLAS,
                      cholesky, Cholesky, # factorizations
                      I,
                      svd,
                      opnorm, # for safeguards in newton trust regions
                      issuccess

import SparseArrays: AbstractSparseMatrix

# exported functions and types
export optimize, maximize, # main function

       # Re-export objective types from NLSolversBase
       NonDifferentiable,
       OnceDifferentiable,
       TwiceDifferentiable,

       # Re-export constraint types from NLSolversBase
       TwiceDifferentiableConstraints,

       # I don't think these should be here [pkofod]
       OptimizationState,
       OptimizationTrace,

       # Optimization algorithms
       ## Zeroth order methods (heuristics)
       NelderMead,
       ParticleSwarm,
       SimulatedAnnealing,

       ## First order
       ### Quasi-Newton
       GradientDescent,
       BFGS,
       LBFGS,

       ### Conjugate gradient
       ConjugateGradient,

       ### Acceleration methods
       AcceleratedGradientDescent,
       MomentumGradientDescent,

       ### Nonlinear GMRES
       NGMRES,
       OACCEL,

       ## Second order
       ### (Quasi-)Newton
       Newton,

       ### Trust region
       NewtonTrustRegion,

       # Constrained
       ## Box constraints, x_i in [lb_i, ub_i]
       ### Specifically Univariate, R -> R
       GoldenSection,
       Brent,

       ### Multivariate, R^N -> R
       Fminbox,
       SAMIN,

       ## Manifold constraints
       Manifold,
       Flat,
       Sphere,
       Stiefel,

       ## Non-linear constraints
       IPNewton

include("types.jl") # types used throughout
include("Manifolds.jl") # code to handle manifold constraints
include("multivariate/precon.jl") # preconditioning functionality

# utilities
include("utilities/generic.jl") # generic utilities
include("utilities/maxdiff.jl") # find largest difference
include("utilities/update.jl")  # trace code

# Unconstrained optimization
## Grid Search
include("multivariate/solvers/zeroth_order/grid_search.jl")

## Zeroth order (Heuristic) Optimization Methods
include("multivariate/solvers/zeroth_order/nelder_mead.jl")
include("multivariate/solvers/zeroth_order/simulated_annealing.jl")
include("multivariate/solvers/zeroth_order/particle_swarm.jl")

## Quasi-Newton
include("multivariate/solvers/first_order/gradient_descent.jl")
include("multivariate/solvers/first_order/bfgs.jl")
include("multivariate/solvers/first_order/l_bfgs.jl")

## Acceleration methods
include("multivariate/solvers/first_order/accelerated_gradient_descent.jl")
include("multivariate/solvers/first_order/momentum_gradient_descent.jl")

## Conjugate gradient
include("multivariate/solvers/first_order/cg.jl")

## Newton
### Line search
include("multivariate/solvers/second_order/newton.jl")
include("multivariate/solvers/second_order/krylov_trust_region.jl")
### Trust region
include("multivariate/solvers/second_order/newton_trust_region.jl")

## Nonlinear GMRES
include("multivariate/solvers/first_order/ngmres.jl")

# Constrained optimization
## Box constraints
include("multivariate/solvers/constrained/fminbox.jl")
include("multivariate/solvers/constrained/samin.jl")

# Univariate methods
include("univariate/solvers/golden_section.jl")
include("univariate/solvers/brent.jl")
include("univariate/types.jl")
include("univariate/printing.jl")

# Line search generic code
include("utilities/perform_linesearch.jl")

# Backward compatibility
include("deprecate.jl")

# convenient user facing optimize methods
include("univariate/optimize/interface.jl")
include("multivariate/optimize/interface.jl")

# actual optimize methods
include("univariate/optimize/optimize.jl")
include("multivariate/optimize/optimize.jl")

# Convergence
include("utilities/assess_convergence.jl")
include("multivariate/solvers/zeroth_order/zeroth_utils.jl")

# Traces
include("utilities/trace.jl")

# API
include("api.jl")

## Interior point includes
include("multivariate/solvers/constrained/ipnewton/types.jl")
# Tracing
include("multivariate/solvers/constrained/ipnewton/utilities/update.jl")
# Constrained optimization
include("multivariate/solvers/constrained/ipnewton/iplinesearch.jl")
include("multivariate/solvers/constrained/ipnewton/interior.jl")
include("multivariate/solvers/constrained/ipnewton/ipnewton.jl")
# Convergence
include("multivariate/solvers/constrained/ipnewton/utilities/assess_convergence.jl")
# Traces
include("multivariate/solvers/constrained/ipnewton/utilities/trace.jl")

# Maximization convenience wrapper
include("maximize.jl")

end


module Coexist

using DataFrames
using CSVFiles
using LinearAlgebra
using Dates
import StatsFuns: logistic, gammapdf
using PyCall

const DATA_DIR = joinpath(dirname(@__FILE__), "..", "data")

include("diseaseProg.jl")
include("utils.jl")
include("generated_dydt.jl")
include("pyCoexist.jl")

end # module


@inline function step!(integrator::ODEIntegrator)
  if integrator.opts.advance_to_tstop
    @inbounds while integrator.tdir * integrator.t < top(integrator.opts.tstops)
      loopheader!(integrator)
      check_error!(integrator) != :Success && return
      perform_step!(integrator,integrator.cache)
      loopfooter!(integrator)
    end
  else
    @inbounds loopheader!(integrator)
    check_error!(integrator) != :Success && return
    @inbounds perform_step!(integrator,integrator.cache)
    @inbounds loopfooter!(integrator)
    @inbounds while !integrator.accept_step
      loopheader!(integrator)
      perform_step!(integrator,integrator.cache)
      loopfooter!(integrator)
    end
  end
  @inbounds handle_tstop!(integrator)
end


using VDPTag2
using Test
using POMDPSimulators
using MCTS
using POMDPs
using ParticleFilters
using ProgressMeter
using LinearAlgebra
using Random
using POMDPModelTools

Random.seed!(1)
pomdp = VDPTagPOMDP()
gen = NextMLFirst(mdp(pomdp), MersenneTwister(31))
s = TagState([1.0, 1.0], [-1.0, -1.0])

struct MyNode end
MCTS.n_children(::MyNode) = rand(1:10)

@inferred next_action(gen, pomdp, s, MyNode())
@inferred next_action(gen, pomdp, initial_state_distribution(pomdp), MyNode())

for a in linspace(0.0, 2*pi, 100)
    s = TagState(Vec2(0,0), Vec2(1,1))
    barriers = CardinalBarriers(0.2, 1.8)
    agent_speed = 1.0
    step_size = 0.5
    delta = agent_speed*step_size*Vec2(cos(a), sin(a))
    agent = VDPTag2.barrier_stop(barriers, s.agent, delta)
    @test agent == s.agent+delta
end

pomdp = VDPTagPOMDP()
for sao in stepthrough(pomdp, RandomPolicy(pomdp), "sao", max_steps=10)
    @show sao
end

dpomdp = AODiscreteVDPTagPOMDP(pomdp, 30, 0.5)
for sao in stepthrough(dpomdp, RandomPolicy(dpomdp), "sao", max_steps=10)
    @show sao
end

pomdp = VDPTagPOMDP(mdp=VDPTagMDP(barriers=CardinalBarriers(0.2, 1.8)))
filter = SIRParticleFilter(pomdp, 1000)
for sao in stepthrough(pomdp, ToNextML(pomdp), filter, "sao", max_steps=10)
    @show sao
end

# test to make sure it can't pass through any walls
pomdp = VDPTagPOMDP(mdp=VDPTagMDP(barriers=CardinalBarriers(0.0, 100.0)))
for quadrant in [Vec2(1,1), Vec2(-1,1), Vec2(1,-1), Vec2(-1,-1)]
    @showprogress for i in 1:100
        is = initial_state(pomdp, Base.GLOBAL_RNG)
        is = TagState(quadrant, is.target)
        for (s, sp) in stepthrough(pomdp, ToNextML(pomdp), filter, "s,sp", max_steps=100, initial_state=is)
            @test all(s.agent.*quadrant .>= 0.0)
            if s == sp
                println("did not move (this should not happen a bunch of times)")
            end
        end
        for (s, sp) in stepthrough(pomdp, RandomPolicy(pomdp), "s,sp", max_steps=100, initial_state=is)
            @test all(s.agent.*quadrant .>= 0.0)
            if s == sp
                println("did not move (this should not happen a bunch of times)")
            end
        end
    end
end

# make sure it does pass through walls sometimes without any barriers
pomdp = VDPTagPOMDP()
N = 100
for quadrant in [Vec2(1,1), Vec2(-1,1), Vec2(1,-1), Vec2(-1,-1)]
    in_other = falses(N)
    @showprogress for i in 1:N
        is = initial_state(pomdp, Base.GLOBAL_RNG)
        is = TagState(quadrant, is.target)
        hr = HistoryRecorder(max_steps=100)
        hist = simulate(hr, pomdp, ToNextML(pomdp), filter)
        in_other[i] = any(any(s.agent.*quadrant .< 0.0) for s in state_hist(hist))
    end
    @show sum(in_other)/length(in_other)
    println("Should be near one?")
    @test any(in_other)
end
for quadrant in [Vec2(1,1), Vec2(-1,1), Vec2(1,-1), Vec2(-1,-1)]
    in_other = falses(N)
    @showprogress for i in 1:N
        is = initial_state(pomdp, Base.GLOBAL_RNG)
        is = TagState(quadrant, is.target)
        hr = HistoryRecorder(max_steps=2)
        hist = simulate(hr, pomdp, RandomPolicy(pomdp))
        in_other[i] = any(any(s.agent.*quadrant .< 0.0) for s in state_hist(hist))
    end
    @show sum(in_other)/length(in_other)
    println("Should be near 3/4?")
    @test any(in_other)
end


"""
    AbstractFitFunction{T, ND, NP}

Abstract type for an `ND`-dimensional fit of eltype `T` with `NP` parameters.
"""
abstract type AbstractFitFunction{T, ND, NP} end

"""
    mutable struct FitFunction{T, ND, NP} <: AbstractFitFunction{T, ND, NP}

T: Precision type

ND: Dimensionality

NP: NP parameters

Fields:
- `model::Function`: Function of the fit model.
- `fitranges::NTuple{N, Vector{T}}`: Range on which the fit is performed.
- `parameter_names::Vector{Symbol}`: Parameters names.
- `fitted_parameters::Vector{T}`: Fitted parameters.
- `initial_parameters::Vector{T}`: Initial parameters.

"""
mutable struct FitFunction{T, ND, NP} <: AbstractFitFunction{T, ND, NP}
    model::Function
    fitranges::NTuple{ND, AbstractVector{T}}
    parameter_names::AbstractVector{Symbol}
    fitted_parameters::AbstractVector{T}
    initial_parameters::AbstractVector{T}
    parameter_bounds::AbstractVector{<:Interval}
    bin_widths::Vector{T}
    bin_centers::Vector{T}
    residuals::Vector{T}
    Χ²::T
    backend_result::Any

    function FitFunction{T}(model::Function, ndims::Int, nparams::Int) where {T <: AbstractFloat}
        fitranges::NTuple{ndims, Vector{T}} = NTuple{ndims, Vector{T}}( [-Inf, Inf] for idim in 1:ndims)
        parameter_names::Vector{Symbol} = [Symbol("par$(ipar)") for ipar in 1:nparams]
        fitted_parameters::Vector{T} = [ T(NaN) for ipar in 1:nparams]
        initial_parameters::Vector{T} = [ T(NaN) for ipar in 1:nparams]
        parameter_bounds = [ T(nextfloat(typemin(T))/2)..T(prevfloat(typemax(T))/2) for ipar in 1:nparams]
        return new{T, ndims, nparams}(model, fitranges, parameter_names, fitted_parameters, initial_parameters, parameter_bounds, T[], T[], T[], T(NaN), missing)
    end
end

function Base.getproperty(f::FitFunction, s::Symbol)
    if s === :Chi2 || s === :chi2
        return f.Χ²
    else
        return getfield(f, s)
    end
end

get_ndims(ff::AbstractFitFunction{T, ND}) where {T <: AbstractFloat, ND} = ND
get_nparams(ff::AbstractFitFunction{T, ND, NP}) where {T <: AbstractFloat, ND, NP} = NP
get_pricision_type(ff::AbstractFitFunction{T}) where {T <: AbstractFloat} = T

function get_fit_backend_result(f::AbstractFitFunction)
    return get_fit_backend_result(f.backend_result)
end
function _set_fit_backend_result!(f::AbstractFitFunction, r)
    f.backend_result = r
    nothing
end

function set_parameter_bounds!(ff::FitFunction{T}, par_bounds::Vector{<:Interval})::Nothing where {T <: AbstractFloat}
    nparams::Int = get_nparams(ff)
    @assert length(par_bounds) == nparams "Wrong number of parameter bounds."
    for i in 1:nparams
        ff.parameter_bounds[i] = par_bounds[i]
    end
end
function set_parameter_bounds!(ff::FitFunction{T}, par_bounds::NamedTuple)::Nothing where {T <: AbstractFloat}
    nparams::Int = get_nparams(ff)
    @assert length(par_bounds) == nparams "Wrong number of parameter bounds."
    for i in 1:nparams
        ff.parameter_bounds[i] = par_bounds[i]
    end
end

function set_fitranges!(ff::AbstractFitFunction{T, N}, fitranges::NTuple{N, NTuple{2, <:Real}})::Nothing where {T <: AbstractFloat, N}
     for idim in 1:N
        ff.fitranges[idim][:] = T.([ fitranges[idim][1], fitranges[idim][2] ])
    end
    nothing
end
function set_fitranges!(ff::AbstractFitFunction{T, N}, fitranges::NTuple{N, AbstractVector{<:Real}})::Nothing where {T <: AbstractFloat, N}
    for idim in 1:N
        @assert length(fitranges[idim]) == 2 "All vectors in `fitranges` must have length 2. But in dimension $(idim) it is $(length(fitranges[idim])) ($(fitranges[idim]))."
        ff.fitranges[idim][:] = T.([ first(fitranges[idim]), last(fitranges[idim]) ])
    end
    nothing
end
function set_parameter_names!(ff::FitFunction{T}, parameter_names::Vector{String})::Nothing where {T <: AbstractFloat}
    ff.parameter_names[:] = [Symbol(name) for name in parameter_names]
    nothing
end
function set_initial_parameters!(ff::FitFunction{T}, initial_parameters::NamedTuple)::Nothing where {T <: AbstractFloat}
    ff.parameter_names[:] = collect(keys(initial_parameters))
    ff.initial_parameters[:] = collect(T, initial_parameters)
    nothing
end
function set_initial_parameters!(ff::FitFunction{T}, initial_parameters::AbstractVector{<:Real})::Nothing where {T <: AbstractFloat}
    ff.initial_parameters[:] = collect(T, initial_parameters)
    nothing
end
function _set_fitted_parameters!(ff::FitFunction{T}, fitted_parameters::AbstractVector{<:Real})::Nothing where {T <: AbstractFloat}
    ff.fitted_parameters[:] = T.(fitted_parameters)
    nothing
end
function _set_fitted_parameters!(ff::FitFunction{T}, fitted_parameters::NamedTuple)::Nothing where {T <: AbstractFloat}
    ff.fitted_parameters[:] = collect(T, fitted_parameters)
    nothing
end

function get_fitted_parameters(ff::FitFunction{T, ND, NP}) where {T <: AbstractFloat, ND, NP}
    return NamedTuple{Tuple(ff.parameter_names), NTuple{NP, T}}( ff.fitted_parameters )
end
function get_initial_parameters(ff::FitFunction{T, ND, NP}) where {T <: AbstractFloat, ND, NP}
    return NamedTuple{Tuple(ff.parameter_names), NTuple{NP, T}}( ff.initial_parameters )
end

function println(io::IO, f::FitFunction)
    println(io, "Model: ", f.model)
    println(io, "  fit ranges: ", f.fitranges)
    println(io, "  Parameters: value (initial value)")
    for i in eachindex(f.parameter_names)
        println("    ", f.parameter_names[i], ": ", f.fitted_parameters[i], " (", f.initial_parameters[i],")" )
    end
end
function print(io::IO, f::FitFunction)
    println(io, f)
end
function show(io::IO, f::FitFunction)
    println(io, f)
end
function show(io::IO, ::MIME"text/plain", f::FitFunction)
    println(io, f)
end
function show(io::IO, ::MIME"text/html", f::FitFunction)
    println(io, f)
end

function _set_bin_widths!(f::FitFunction, bin_widths::Vector{T})::Nothing where T <: Real
    f.bin_widths = bin_widths
    nothing
end

function _set_bin_centers!(f::FitFunction, bin_centers::Vector{T})::Nothing where T <: Real
    f.bin_centers = bin_centers
    nothing
end

function _get_Χ²(residuals::Array{T,1}, inv_σi²::Array{T,1}, dof::Int)::T where T <: Real
    x::T = 0.0
    residuals_sq = residuals .* residuals
    for i in 1:length(residuals_sq)
        val = residuals_sq[i] * inv_σi²[i]
        (!isnan(val) && !isinf(val)) ? x += val : x+= residuals_sq[i]
    end
    x / dof
end

function _set_Χ²!(f::FitFunction, weights::Vector{T}; idcs::Vector{Int}=collect(eachindex(weights)))::Nothing where T <: Real
    f.Χ² = _get_Χ²(f.residuals[idcs], weights[idcs], length(weights[idcs]) - length(f.fitted_parameters) ) # weights are 1/(σ_i)^2
    nothing
end

function _set_Χ²!(f::FitFunction; idcs::Vector{Int} = collect(eachindex(f.bin_centers)))::Nothing where T <: Real
    f.Χ² = _get_Χ²(f.residuals[idcs], inv.(f.model(f.bin_centers, f.fitted_parameters) .* (isempty(f.bin_widths) ? 1.0 : f.bin_widths)), length(f.bin_centers[idcs]) - length(f.fitted_parameters) ) # weights are 1/(σ_i)^2
    nothing
end

function _get_residuals(f::FitFunction, xdata::Vector{T}, ydata::Vector{T})::Vector{T} where T <: Real
    return  (f.model(xdata, f.fitted_parameters) .* (isempty(f.bin_widths) ? 1.0 : f.bin_widths) .- ydata)
end

function _set_residuals!(f::FitFunction, xdata::Vector{T}, ydata::Vector{T})::Nothing where T <: Real
    f.residuals = _get_residuals(f, xdata, ydata)
    nothing
end

get_standard_deviations(f::FitFunction) = _get_standard_deviations(f, f.backend_result)

_get_standard_deviations(f::FitFunction, fr::Missing) = error("No fit performed yet.")

@recipe function f(ff::FitFunction{T}, h::Union{Missing, Histogram} = missing; npoints = 501, use_initial_parameters = false) where {T}
    x = collect(range(ff.fitranges[1][1], stop=ff.fitranges[1][2], length=npoints))
    par = use_initial_parameters ? ff.initial_parameters : ff.fitted_parameters
    y = if ismissing(h) && isempty(ff.bin_widths)
        ff.model(x, collect(par))
    elseif ismissing(h)
        ff.model(x, collect(par)) .*  map(xe -> ff.bin_widths[findmin(abs.(ff.bin_centers .- xe))[2]], x)
    else
        bin_width_weights::Vector{T} = map(xe -> StatsBase.binvolume(h, StatsBase.binindex(h, xe)), x)
        ff.model(x, collect(par)) .* bin_width_weights
    end
    linecolor --> (use_initial_parameters ? :green : :red)
    label --> (use_initial_parameters ? "Fit model with initial parameters" : "Fit model with fitted parameters")
    x,y
end


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

using AsterReader

"""
Nodes are different order in Code Aster compared to ABAQUS. This is yet
incomplete mapping between the permutations. Most of this is still a
great mystery.

# References
- http://onelab.info/pipermail/gmsh/2008/003850.html
- http://caelinux.org/wiki/index.php/Proj:UNVConvert

"""
const med_connectivity = Dict{Symbol, Vector{Int}}(
    :Tet4   => [4,3,1,2],
    :Tet10  => [4,3,1,2,10,7,8,9,6,5],
    :Pyr5   => [1,4,3,2,5],
    :Wedge6 => [4,5,6,1,2,3],
    :Hex8   => [4,8,7,3,1,5,6,2],
    :Hex20  => [4,8,7,3,1,5,6,2,20,15,19,11,12,16,14,10,17,13,18,9],
    :Hex27  => [4,8,7,3,1,5,6,2,20,15,19,11,12,16,14,10,17,13,18,9,24,25,26,23,21,22,27])

"""
Map element names used in in Code Aster to element names used in in JuliaFEM
"""
const med_element_names = Dict{Symbol, Symbol}(
    :PO1 => :Poi1,
    :SE2 => :Seg2,
    :SE3 => :Seg3,
    :SE4 => :Seg4,
    :TR3 => :Tri3,
    :TR6 => :Tri6,
    :TR7 => :Tri7,
    :QU4 => :Quad4,
    :QU8 => :Quad8,
    :QU9 => :Quad9,
    :TE4 => :Tet4,
    :T10 => :Tet10,
    :PE6 => :Wedge6,
    :P15 => :Wedge15,
    :P18 => :Wedge18,
    :HE8 => :Hex8,
    :H20 => :Hex20,
    :H27 => :Hex27,
    :PY5 => :Pyr5,
    :P13 => :Pyr13)

"""
    aster_read_mesh(filename, mesh_name=nothing; reorder_element_connectivity=true)

Read code aster mesh from file and return `Mesh` structure.

If mesh file contains several meshes, a name of mesh must be given. By default,
elements are reordered so that they match to the conventions used in JuliaFEM.
"""
function aster_read_mesh(filename::String, mesh_name=nothing; reorder_element_connectivity=true)
    m = AsterReader.aster_read_mesh(filename, mesh_name)
    mesh = Mesh(m)
    for (elid, eltype) in mesh.element_types
        mesh.element_types[elid] = med_element_names[eltype]
    end
    if reorder_element_connectivity
        reorder_element_connectivity!(mesh, med_connectivity)
    end
    nnodes = length(mesh.nodes)
    nelements = length(mesh.elements)
    @info("Mesh parsed from Code Aster file $filename.")
    @info("Mesh contains $nnodes nodes and $nelements elements.")
    for (elset_name, elset_elids) in mesh.element_sets
        content = Dict{Symbol, Int}()
        for elid in elset_elids
            eltype = mesh.element_types[elid]
            content[eltype] = get(content, eltype, 0) + 1
        end
        s = join(("$v x $k" for (k, v) in content), ", ")
        nels = length(elset_elids)
        @info("Element set $elset_name contains $nels elements ($s).")
    end
    return mesh
end


#= Custom calibration configuration file. =#

using Distributions
using StatsBase
using LinearAlgebra
using CalibrateEDMF
using CalibrateEDMF.ModelTypes
using CalibrateEDMF.LESUtils
using CalibrateEDMF.TurbulenceConvectionUtils
# Import prior constraints
using EnsembleKalmanProcesses.ParameterDistributions
using JLD2

# Cases defined as structs for quick access to default configs
struct GridSearchCases end

function get_config()
    config = Dict()
    # Define regularization of inverse problem
    config["regularization"] = get_regularization_config()
    # Define reference used in the inverse problem
    config["reference"] = get_reference_config(GridSearchCases())
    # Define the SCM static configuration
    config["scm"] = get_scm_config()
    # grid search configuration
    config["grid_search"] = get_grid_search_config()
    return config
end

function get_regularization_config()
    config = Dict()
    # Regularization of observations: mean and covariance
    config["perform_PCA"] = true # Performs PCA on data
    config["variance_loss"] = 1.0e-2 # Variance truncation level in PCA
    config["normalize"] = true  # whether to normalize data by pooled variance
    config["tikhonov_mode"] = "relative" # Tikhonov regularization
    config["tikhonov_noise"] = 1.0e-4 # Tikhonov regularization
    config["dim_scaling"] = true # Dimensional scaling of the loss

    # Parameter regularization: L2 regularization with respect to prior mean.
    #  - Set to `nothing` to use prior covariance as regularizer,
    #  - Set to a float for isotropic parameter regularization.
    #  - Pass a dictionary of lists similar to config["prior_mean"] for
    #       anisotropic regularization. The dictionary must be complete.
    #       If you want to avoid regularizing a certain parameter, set the entry
    #       to [0].
    # To turn off regularization, set config["process"]["augmented"] to false.
    config["l2_reg"] = nothing
    return config
end

function get_reference_config(::GridSearchCases)
    config = Dict()
    config["case_name"] = ["TRMM_LBA", "Rico", "TRMM_LBA"]
    n_cases = length(config["case_name"])
    # Flag to indicate source of data (LES or SCM) for reference data and covariance
    config["y_reference_type"] = SCM()
    # config["Σ_reference_type"] = LES()
    # Fields to learn from during training
    config["y_names"] = repeat([["ql_mean", "qt_mean", "total_flux_qt"]], n_cases)
    # LES data can be stored as an Artifact and downloaded lazily
    # config["y_dir"] = [...]  # only needed for loss map computation
    # provide list of dirs if different from `y_dir`
    # config["Σ_dir"] = [...]
    config["t_start"] = repeat([100.0], n_cases)
    config["t_end"] = repeat([200.0], n_cases)
    config["scm_parent_dir"] = repeat(["scm_init"], n_cases)
    config["scm_suffix"] = repeat(["0"], n_cases)
    return config
end

function get_scm_config()
    config = Dict()
    # List of tuples like [("time_stepping", "dt_min", 1.0)], or nothing
    config["namelist_args"] = nothing
    config["namelist_args"] = [
        ("time_stepping", "t_max", 200.0),
        ("turbulence", "EDMF_PrognosticTKE", "entrainment", "moisture_deficit"),
        ("turbulence", "EDMF_PrognosticTKE", "stochastic_entrainment", "prognostic_noisy_relaxation_process"),
        ("time_stepping", "adapt_dt", false),
    ]
    return config
end

function get_grid_search_config()
    config = Dict()
    # Grid search is performed over each pair of parameters, across all specified values
    config["parameters"] = Dict(
        "general_stochastic_ent_params_{1}" => [0.3, 0.4],
        "general_stochastic_ent_params_{2}" => [0.2, 0.3],
        "entrainment_factor" => [0.1],
    )
    # Number of simulations to run with identical configuration (except random seed)
    config["ensemble_size"] = 1
    # grid search output data stored in `<output_root_dir>/output/YYmmdd_abc`
    # config["output_root_dir"] = pwd()  # provided explicitly in `grid_search_test.jl`
    # Perform grid search and loss map calculation for either the `reference` or `validation` set.
    config["sim_type"] = "reference"
    return config
end


function pp62(data::Dict)

  # Setup basic dimensions of arrays

  # Parse & check FEdict data

  if :struc_el in keys(data)
    struc_el = data[:struc_el]
    @assert typeof(struc_el) <: StructuralElement
  else
    println("No fin_el type specified.")
    return
  end

  ndim = struc_el.ndim
  nst = struc_el.nst

  if struc_el.axisymmetric
    nst = 4
  end

  fin_el = struc_el.fin_el
  @assert typeof(fin_el) <: FiniteElement

  if typeof(fin_el) == Line
    (nels, nn) = mesh_size(fin_el, struc_el.nxe)
  elseif typeof(fin_el) == Triangle || typeof(fin_el) == Quadrilateral
    (nels, nn) = mesh_size(fin_el, struc_el.nxe, struc_el.nye)
  elseif typeof(fin_el) == Hexahedron
    (nels, nn) = mesh_size(fin_el, struc_el.nxe, struc_el.nye, struc_el.nze)
  else
    println("$(typeof(fin_el)) is not a known finite element.")
    return
  end

  nodof = fin_el.nodof           # Degrees of freedom per node
  ndof = fin_el.nod * nodof      # Degrees of freedom per fin_el

  # Update penalty if specified in FEdict

  penalty = 1e20
  if :penalty in keys(data)
    penalty = data[:penalty]
  end

  tol = 1.0e-10
  if :tol in keys(data)
    tol = copy(data[:tol])
  end

  limit = 100
  if :limit in keys(data)
    limit = copy(data[:limit])
  end

  if :cg_tol in keys(data)
    cg_tol = data[:cg_tol]
  else
    cg_tol = 1.0e-5
  end

  if :cg_limit in keys(data)
    cg_limit = data[:cg_limit]
  else
    cg_limit = Int(1e3)
  end

  # Allocate all arrays

  # Start with arrays to be initialized from FEdict

  if :properties in keys(data)
    prop = zeros(size(data[:properties], 1), size(data[:properties], 2))
    for i in 1:size(data[:properties], 1)
      prop[i, :] = data[:properties][i, :]
    end
  else
    println("No :properties key found in FEdict")
  end

  nf = ones(Int, nodof, nn)
  if :support in keys(data)
    for i in 1:size(data[:support], 1)
      nf[:, data[:support][i][1]] = data[:support][i][2]
    end
  end

  x_coords = zeros(nn)
  if :x_coords in keys(data)
    x_coords = data[:x_coords]
  end

  y_coords = zeros(nn)
  if :y_coords in keys(data)
    y_coords = data[:y_coords]
  end

  z_coords = zeros(nn)
  if :z_coords in keys(data)
    z_coords = data[:z_coords]
  end

  g_coord = zeros(ndim,nn)
  if :g_coord in keys(data)
    g_coord = data[:g_coord]'
  end

  g_num = zeros(Int, fin_el.nod, nels)
  if :g_num in keys(data)
    g_num = reshape(data[:g_num]', fin_el.nod, nels)
  end

  etype = ones(Int, nels)
  if :etype in keys(data)
    etype = data[:etype]
  end

  # All other arrays

  points = zeros(struc_el.nip, ndim)
  g = zeros(Int, ndof)
  fun = zeros(fin_el.nod)
  coord = zeros(fin_el.nod, ndim)
  gamma = zeros(nels)
  jac = zeros(ndim, ndim)
  der = zeros(ndim, fin_el.nod)
  deriv = zeros(ndim, fin_el.nod)
  bee = zeros(nst,ndof)
  km = zeros(ndof, ndof)
  mm = zeros(ndof, ndof)
  gm = zeros(ndof, ndof)
  kg = zeros(ndof, ndof)
  eld = zeros(ndof)
  weights = zeros(struc_el.nip)
  g_g = zeros(Int, ndof, nels)
  num = zeros(Int, fin_el.nod)
  actions = zeros(ndof, nels)
  displacements = zeros(size(nf, 1), ndim)
  gc = ones(ndim)
  dee = zeros(nst,nst)
  sigma = zeros(nst)
  axial = zeros(nels)

  eps = zeros(nst)
  stress = zeros(nst)
  bload =zeros(ndof)
  eload =zeros(ndof)
  erate = zeros(nst)
  evp = zeros(nst)
  devp = zeros(nst)
  m1 = zeros(nst, nst)
  m2 = zeros(nst, nst)
  m3 = zeros(nst, nst)
  flow = zeros(nst, nst)

  formnf!(nodof, nn, nf)
  neq = maximum(nf)

  @assert :qincs in keys(data)
  qincs = data[:qincs]

  loads = zeros(Float64, neq+1)
  bdylds = zeros(Float64, neq+1)
  oldis = zeros(Float64, neq+1)
  totd = zeros(Float64, neq+1)
  p = zeros(nst, struc_el.nip, nels)
  x = zeros(neq+1)
  u = zeros(neq+1)
  xnew = zeros(neq+1)
  diag_precon = zeros(neq+1)
  d = zeros(neq+1)

  # Find global array sizes
  for iel in 1:nels
    geom_rect!(fin_el, iel, x_coords, y_coords, coord, num, struc_el.direction)
    num_to_g!(num, nf, g)
    g_num[:, iel] = num
    g_coord[:, num] = coord'
    g_g[:, iel] = g
  end

  tensor = zeros(nst, struc_el.nip, nels)
  storkm = zeros(ndof, ndof, nels)
  dt = 1.0e15
  ddt = 0.0
  sigm = 0.0
  dsbar = 0.0
  lode_theta = 0.0

  sample!(fin_el, points, weights)

  # element stiffnes integration, storage and conditioner

  for iel in 1:nels
    ddt = 4.0*(1.0+prop[etype[iel], 3])/(3.0*prop[etype[iel], 2])
    if ddt < dt
      dt = ddt
    end
    deemat!(dee, prop[etype[iel], 2], prop[etype[iel], 3])
    num = g_num[:, iel]
    coord = g_coord[:, num]'              # Transpose
    g = g_g[:, iel]
    km = zeros(ndof, ndof)
    for i in 1:struc_el.nip
      shape_der!(der, points, i)
      jac = der*coord
      detm = det(jac)
      jac = inv(jac)
      deriv = jac*der
      beemat!(bee, deriv)
      km += (bee')*dee*bee*detm*weights[i]
    end
    storkm[:,:,iel] = km
    for k in 1:ndof
      diag_precon[g[k]+1] = diag_precon[g[k]+1] + km[k,k]
    end
  end

  diag_precon = 1 ./ diag_precon

  # Read load weightings

  loaded_nodes = 0
  node = Int[]
  val = Array{Float64, 2}
  if :loaded_nodes in keys(data)
    loaded_nodes = size(data[:loaded_nodes], 1)
    node = zeros(Int, loaded_nodes)
    val = zeros(loaded_nodes, size(data[:loaded_nodes][1][2], 2))
  end

  if :loaded_nodes in keys(data)
    for i in 1:loaded_nodes
      node[i] = data[:loaded_nodes][i][1]
      val[i,:] = data[:loaded_nodes][i][2]
    end
  end

  nf1 = deepcopy(nf) + 1

  # Load increment loop

  converged = false
  ptot = 0.0
  iters = 0
  cg_tot = 0
  diag_precon[1] = 0.0
  iy = 0

  for iy in 1:size(qincs, 1)
    ptot = ptot + qincs[iy]
    iters = 0
    bdylds = zeros(Float64, neq+1)
    evpt = zeros(nst, struc_el.nip, nels)
    cg_tot = 0
    cg_converged = false

    # Plastic iteration loop

    while true
      iters += 1
      loads = zeros(Float64, neq+1)
      for i in 1:loaded_nodes
        loads[nf1[:,node[i]]] = val[i,:] * qincs[iy]
      end
      loads += bdylds
      d = diag_precon .* loads
      p = d
      x = zeros(neq+1)
      cg_iters = 0

      # Pcg equation solution
      tmpconvcrit = 0.0

      while true
        cg_iters += 1
        u = zeros(neq+1)

        for iel in 1:nels
          g = g_g[:, iel]
          km = storkm[:, :, iel]
          u[g+1] += km * p[g+1]
        end
        up = dot(loads, d)
        alpha = up ./ dot(p, u)
        xnew = x + p .* alpha
        loads -= u .* alpha
        d = diag_precon .* loads
        beta = dot(loads, d) ./ up
        p = d + p * beta
        #tmpconvcrit =  maximum(abs.(xnew-x))/maximum(abs.(xnew))
        cg_converged = checon(xnew, x, cg_tol)
        #iy < 2 && println([iters cg_iters cg_tot+cg_iters tmpconvcrit])
        if cg_converged || (cg_iters == cg_limit)
          break
        end
      end
      cg_tot += cg_iters
      loads = xnew
      loads[1] = 0.0

      # Check plastic convergence

      converged = checon(loads, oldis, tol)
      if iters == 1
        converged = false
      end
      if converged || (iters == limit)
        bdylds = zeros(Float64, neq+1)
      end

      # Go round the Gauss point

      for iel in 1:nels
        deemat!(dee, prop[etype[iel], 2], prop[etype[iel], 3])
        num = g_num[:, iel]
        coord = g_coord[:, num]'
        g = g_g[:, iel]
        eld = loads[g+1]
        bload = zeros(ndof)
        for i in 1:struc_el.nip
          shape_der!(der, points, i)
          jac = der*coord
          detm = det(jac)
          jac = inv(jac)
          deriv = jac*der
          beemat!(bee, deriv)
          eps = bee*eld
          eps -= evpt[:, i, iel]
          sigma = dee*eps
          stress = sigma + tensor[:, i, iel]
          (sigm, dsbar, lode_theta) = invar(stress, sigm, dsbar, lode_theta)

          # Check whether yield is violated

          f = dsbar - sqrt(3.0)*prop[etype[iel], 1]
          if converged || iters == limit
            devp = deepcopy(stress)
          else
            if f >= 0.0
              dq1 = 0.0
              dq2 = 3.0/2.0/dsbar
              dq3 = 0.0
              (m1, m2, m3) = formm!(stress, m1, m2, m3)
              flow = f*(m1*dq1 + m2*dq2 + m3*dq3)
              erate = flow*stress
              evp = erate*dt
              evpt[:,i,iel] += evp
              devp = dee*evp
            end
          end
          if f >= 0.0 || converged || iters == limit
            eload = devp' * bee
            bload += eload' .* detm .* weights[i]
          end

          # Update the Gauss point stresses

          if converged || iters == limit
            tensor[:,i,iel] = stress
          end
        end

        # Compute the total bodyloads vector

        bdylds[g+1] += bload
        bdylds[1] = 0.0
      end
      if converged || iters == limit
        break
      end
    end
    totd += loads
    totdstr = @sprintf("%+.4e", totd[nf1[2, node[1]]])

    if iy < 10
      println(" $(iy)       $(ptot)    $(totdstr)    $(iters)       $(round.(cg_tot/iters, digits=2))")
    else
      println("$(iy)       $(ptot)    $(totdstr)    $(iters)       $(round.(cg_tot/iters, digits=2))")
    end

  end
  (ptot, totd[nf1[2, node[1]]], iters, round.(cg_tot/iters, 2))
end

function p62a(data)
  lst = [deepcopy(data) for i in 1:length(data[:qincs])];
  for i in 2:length(data[:qincs])
    lst[i][:qincs] = [sum(data[:qincs][1:(i-1)]), data[:qincs][i]]
  end
  np = nprocs()  # determine the number of processes available
  n = length(lst)
  results = Vector{Any}(n)
  local ix = 2
  # function to produce the next work item from the queue.
  # in this case it's just an index.
  nextidx() = (idx=ix; ix+=1; idx)
  @sync begin
      for p=1:np
          if p != myid() || np == 1
              @async begin
                  while true
                      idx = nextidx()
                      if idx > n
                          break
                      end
                      results[idx] = remotecall_fetch(pp62, p, lst[idx])
                  end
              end
          end
      end
  end
  results[2:end]
end


# helps with static types that can't be easily inferred as same parametrically
same_type(::X, ::Y) where {X,Y} =  same_type(X, Y)
same_type(::Type{X}, ::Type{Y}) where {X<:OneToSRange,Y<:OneToSRange} = true
same_type(::Type{X}, ::Type{Y}) where {X<:UnitSRange,Y<:UnitSRange} = true
same_type(::Type{X}, ::Type{Y}) where {X<:StepSRange,Y<:StepSRange} = true
same_type(::Type{X}, ::Type{Y}) where {X<:LinSRange,Y<:LinSRange} = true
same_type(::Type{X}, ::Type{Y}) where {X<:StepSRangeLen,Y<:StepSRangeLen} = true
same_type(::Type{X}, ::Type{X}) where {X} = true
same_type(::Type{X}, ::Type{Y}) where {X,Y} = false

promote_values_rule(::X, ::Y) where {X,Y} = promote_values_rule(X, Y)
function promote_values_rule(::Type{X}, ::Type{Y}) where {X<:AbstractIndex,Y<:AbstractIndex}
    xv = values_type(X)
    yv = values_type(Y)
    return same_type(xv, yv) ? xv : _promote_rule(xv, yv)
end

promote_keys_rule(::X, ::Y) where {X,Y} = promote_keys_rule(X, Y)
function promote_keys_rule(::Type{X}, ::Type{Y}) where {X<:AbstractIndex,Y<:AbstractIndex}
    xv = keys_type(X)
    yv = keys_type(Y)
    return same_type(xv, yv) ? xv : _promote_rule(xv, yv)
end

function _promote_rule(::Type{X}, ::Type{Y}) where {X,Y}
    out = promote_rule(X, Y)
    return out <: Union{} ? promote_rule(Y, X) : out
end

function Base.promote_rule(::Type{X}, ::Type{Y}) where {X<:AbstractIndex,Y<:AbstractIndex}
    return similar_type(X, promote_keys_rule(X, Y), promote_values_rule(X, Y))
end

function Base.promote_rule(::Type{X}, ::Type{Y}) where {X<:AbstractIndex,Y<:AbstractUnitRange}
    return promote_rule(X, similar_type(X, index_keys_type(Y),Y))
end

function Base.promote_rule(::Type{X}, ::Type{Y}) where {X<:UnitRange,Y<:AbstractIndex}
    return promote_rule(Y, X)
end

function Base.promote_rule(::Type{X}, ::Type{Y}) where {X<:AbstractIndex,Y<:AbstractVector}
    return promote_rule(values_type(X), Y)
end
function Base.promote_rule(::Type{X}, ::Type{Y}) where {X<:AbstractVector,Y<:AbstractIndex}
    return promote_rule(Y, X)
end

#function promote_axes(::Type{X}, ::Type{Y}) where {X<:AbstractIndex,Y<:AbstractIndex}
#end

# TODO
#function Base.promote_rule(::Type{}, ::Type{}) where {X<:IndicesArray,Y<:AbstractArray}
#end
#=
function Base.promote_rule(::Type{X}, ::Type{Y}) where {X<:IndicesArray,Y<:IndicesArray}
    return similar_type(X,
                 promote_rule(eltype(X), eltype(Y)),
                 promote_rule(parent_type(X), parent_type(X)),
                 promote_axes(axes_type(X), axes_type(Y))
                )
end

function Base.promote_rule(::Type{X}, ::Type{Y}) where {X<:IndicesArray,Y<:AbstractArray}
end
=#


##------------------------------##
##实现从下位机读取麦克风阵列数据
##1、调用MATLAB函数读取
##2、c++封装的SO读取
##3、自己生成随机数
##------------------------------##
##name = "线路输入 (LIS_MIIS_8X8_192K)"
export  MicrophoneParameter,
        init_matlab,
        getdata

struct MicrophoneParameter{T1<:String, T2<:Int}
    name::T1 #麦克风阵列名称
    fs::T2  #采样频率
    sample::T2  #一帧采样数
    numchannels::T2    #通道数
end

function init_matlab(parameter::MicrophoneParameter)
    s = MATLAB.MSession(0)
    put_variable(s, :name, parameter.name)
    eval_string(s, "H = audioDeviceReader")
    eval_string(s, "H.Device = name ")
    eval_string(s, "H.NumChannels = $(parameter.numchannels)") #通道数要跟实际设备中搜集到的数据一致
    eval_string(s, "H.SampleRate = $(parameter.fs)")
    eval_string(s, "H.SamplesPerFrame = $(parameter.sample)")
    eval_string(s, "H.BitDepth = '16-bit integer' ")
    eval_string(s, "H.OutputDataType = 'double' ")
    return s
end

function (hardware::MicrophoneParameter)(select::Symbol)
    if select == :MATLAB
        return init_matlab(hardware)
    elseif select == :C++
        error("还没实现C++方法")
    elseif select == :RandomData
        error("还没实现随机数生成方法")
    else
        error("这儿没有实现其他方法")
    end
end

function getdata(s::MSession;spin = false, transform_matrix = [2, 1, 3, 4],
    ishilbert = false)
    eval_string(s, "data = H()")
    data = jarray(get_mvariable(s, :data))
    if spin
        data = view(data, :, transform_matrix)
    end
    if ishilbert
        data = hilbert(data)
    end
    return view(data, :, :)
end


isinteractive() && include("common.jl")
include("sample-correctness-utilities.jl")

#####
##### sample correctness tests
#####
##### Sample from well-characterized distributions using LogDensityTestSuite, check
##### convergence and mixing, and compare.

@testset "NUTS tests with random normal" begin
    for _ in 1:10
        K = rand(3:10)
        μ = randn(K)
        D = rand_D(K)
        C = rand_C(K)
        ℓ = multivariate_normal(μ, D * C)
        title = "multivariate normal μ = $(μ) D = $(D) C = $(C)"
        NUTS_tests(RNG, ℓ, title, 1000)
    end
end

@testset "NUTS tests with specific normal distributions" begin
    ℓ = multivariate_normal([0.0], fill(5e8, 1, 1))
    NUTS_tests(RNG, ℓ, "univariate huge variance", 1000)

    ℓ = multivariate_normal([1.0], fill(5e8, 1, 1))
    NUTS_tests(RNG, ℓ, "univariate huge variance, offset", 1000)

    ℓ = multivariate_normal([1.0], fill(5e-8, 1, 1))
    NUTS_tests(RNG, ℓ, "univariate tiny variance, offset", 1000)

    ℓ = multivariate_normal([1.0, 2.0, 3.0], Diagonal([1.0, 2.0, 3.0]))
    NUTS_tests(RNG, ℓ, "mildly scaled diagonal", 1000)

    # these tests are kept because they did produce errors for some code that turned out to
    # be buggy in the early development version; this does not meant that they are
    # particularly powerful or sensitive ones
    ℓ = multivariate_normal([-0.37833073009094703, -0.3973395239297558],
                            cholesky([0.08108928067723374 -0.19742780267879112;
                                      -0.19742780267879112 1.2886298811010262]).L)
    NUTS_tests(RNG, ℓ, "kept 2 dim", 1000)

    ℓ = multivariate_normal(
        [-1.0960316317778482, -0.2779143641884689, -0.4566289703243874],
        cholesky([2.2367476976202463 1.4710084974801891 2.41285525745893;
                  1.4710084974801891 1.1684361535929932 0.9632367554302268;
                  2.41285525745893 0.9632367554302268 4.5595606374865785]).L)
    NUTS_tests(RNG, ℓ, "kept 3 dim", 1000)

    ℓ = multivariate_normal(
        [-1.42646, 0.94423, 0.852379, -1.12906, 0.0868619, 0.948781, -0.875067, 1.07243],
        cholesky([14.8357 2.42526 -2.97011 2.08363 -1.67358 4.02846 5.57947 7.28634;
                   2.42526 10.8874 -1.08992 1.99358 1.85011 -2.29754 -0.0540131 1.79718;
                   -2.97011 -1.08992 3.05794 0.0321187 1.8052 -1.5309 1.78163 -0.0821483;
                   2.08363 1.99358 0.0321187 2.38112 -0.252784 0.666474 1.73862 2.55874;
                   -1.67358 1.85011 1.8052 -0.252784 12.3109 -2.3913 -2.99741 -1.95031;
                   4.02846 -2.29754 -1.5309 0.666474 -2.3913 4.89957 3.6118 5.22626;
                   5.57947 -0.0540131 1.78163 1.73862 -2.99741 3.6118 10.215 9.60671;
                   7.28634 1.79718 -0.0821483 2.55874 -1.95031 5.22626 9.60671 11.5554]).L)
    NUTS_tests(RNG, ℓ, "kept 8 dim", 1000)
end

@testset "NUTS tests with mixtures" begin
    ℓ1 = multivariate_normal(zeros(3), 1.0)
    D2 = Diagonal(fill(0.4, 3))
    C2 = [1.0 -0.48058358598852935 0.39971148270854306;
          0.0 0.876948924897229 -0.5361348433365906;
          0.0 0.0 0.7434985947205197]
    ℓ2 = multivariate_normal(ones(3), D2 * C2)
    ℓ = mix(0.2, ℓ1, ℓ2)
    NUTS_tests(RNG, ℓ, "mixture of two normals", 1000; τ_alert = 0.15)
end

@testset "NUTS tests with heavier tails and skewness" begin
    K = 5

    ℓ = elongate(1.2, StandardMultivariateNormal(K))
    NUTS_tests(RNG, ℓ, "elongate(1.2, 𝑁)", 1000; p_alert = 1e-5, EBFMI_alert = 0.2)

    # this has very nasty tails to we relax requirements a bit
    ℓ = elongate(1.1, shift(ones(K), StandardMultivariateNormal(K)))
    NUTS_tests(RNG, ℓ, "skew elongate(1.1, 𝑁)", 10000; τ_alert = 0.1, EBFMI_alert = 0.2)
end


module SpatialIndexing

export SpatialIndex, SpatialIndexException, SpatialElem,
    RTree, SimpleSpatialIndex,
    contained_in, intersects_with

include("regions.jl")
include("pool.jl")

include("abstract.jl")

## Spatial Indices

#   SimpleSpatialIndex
include("simple/simpleindex.jl")

#   R-tree
include("rtree/node.jl")
include("rtree/rtree.jl")
include("rtree/check.jl")
include("rtree/query.jl")
include("rtree/split.jl")
include("rtree/insert.jl")
include("rtree/adjust.jl")
include("rtree/delete.jl")
include("rtree/bulk.jl")

end # module


using JSON
using Profile
using Random
using Distributed
using StatProfilerHTML
include("src/Flips.jl")
using .Flips

function main()
    graph_data = JSON.parsefile("horizontal.json")
    graph = IndexedGraph(graph_data, "population");
    plan = Plan(graph, "district")
    t = MersenneTwister(0);
    n = 8
    unique_plans = 1
    for i in 1:10000
        println("--- ", i, " ---")
        flip, _, reasons = recom_until_step(graph, plan, 355, 365, t)
        update!(plan, graph, flip)
        unique_plans += 1
        println(reasons)
        if unique_plans % 100 == 0
            println(plan.assignment)
        end
    end
end

main()


module Display_Test

using Compat
using Compat.Test
using IntervalArithmetic
using EAGOBranchBound

S = BnBSolver()
S.Verbosity = "Full"

println("test #1:")
@test_nowarn EAGOBranchBound.print_node!(S,1,3.0,[Interval(1.0,2.0)])
println("test #2:")
@test_nowarn EAGOBranchBound.print_results!(S,2.0,[1.5],false,true)
println("test #3:")
@test_nowarn EAGOBranchBound.print_results!(S,2.0,[1.5],false,false)

end


CBB
hasprecon(::CBB) = NoPrecon()


names = [
    "test_sortedindices.jl",
    "test_polynomials.jl",

    "test_bases.jl",
    "test_states.jl",

    "test_operators.jl",
    "test_operators_dense.jl",
    "test_sparsematrix.jl",
    "test_operators_sparse.jl",
    "test_operators_lazytensor.jl",
    "test_operators_lazysum.jl",
    "test_operators_lazyproduct.jl",

    "test_fock.jl",
    "test_spin.jl",
    "test_particle.jl",
    "test_manybody.jl",
    "test_nlevel.jl",
    "test_subspace.jl",
    "test_state_definitions.jl",

    "test_phasespace.jl",
    "test_transformations.jl",

    "test_metrics.jl",
    "test_embed.jl",
    "test_spectralanalysis.jl",

    "test_timeevolution_schroedinger.jl",
    "test_timeevolution_master.jl",
    "test_timeevolution_mcwf.jl",

    "test_timeevolution_twolevel.jl",
    "test_timeevolution_pumpedcavity.jl",

    "test_superoperators.jl",
    "test_steadystate.jl",
    "test_timecorrelations.jl",

    "test_semiclassical.jl",

    "test_stochastic_definitions.jl",
    "test_stochastic_schroedinger.jl",
    "test_stochastic_master.jl",
    "test_stochastic_semiclassical.jl",

    "test_printing.jl"
]

detected_tests = filter(
    name->startswith(name, "test_") && endswith(name, ".jl"),
    readdir("."))

unused_tests = setdiff(detected_tests, names)
if length(unused_tests) != 0
    error("The following tests are not used:\n", join(unused_tests, "\n"))
end

unavailable_tests = setdiff(names, detected_tests)
if length(unavailable_tests) != 0
    error("The following tests could not be found:\n", join(unavailable_tests, "\n"))
end

for name=names
    if startswith(name, "test_") && endswith(name, ".jl")
        include(name)
    end
end


abstract type SMExpr end
abstract type Substitutable end

Numeric = Union{Integer, AbstractFloat, Nothing}

struct Num <: SMExpr
    literal::String
    num::Numeric
end

struct Var <: Substitutable
    id::String
end

struct FunExp <: Substitutable
end