for (syevd, elty, relty) in    ((:ssyevd, :Float32, :Float32),
                            (:dsyevd, :Float64, :Float64)),
                            interface in (:Matrix, :CuMatrix)
    @eval begin
        function magma_syevd!(jobz::AbstractChar, uplo::AbstractChar, A::$interface{$elty})
            A = Matrix{$elty}(A)

            n = checksquare(A)
            lda     = max(1, stride(A, 2))
            W = similar(A, $relty, n)
            work = Vector{$elty}(undef, 1)
            lwork = -1
            iwork = Vector{Int32}(undef, 1)
            liwork = -1
            info    = Ref{Int}()

            jobz_magma      = char_to_magmaInt(jobz)
            uplo_magma     = char_to_magmaIntUplo(uplo)

            for i in 1:2
                func = eval(@magmafunc($syevd))
                func(
                    jobz_magma,
                    uplo_magma,
                    n,
                    A,
                    lda,
                    W,
                    work,
                    lwork,
                    iwork,
                    liwork,
                    info
                )

                if i == 1
                    lwork = ceil(Int, nextfloat(real(work[1])))
                    resize!(work, lwork)
                    liwork = Int(iwork[1])
                    resize!(iwork, liwork)
                end
            end

            jobz == 'V' ? (W, A) : W
        end

        function magma_syevd_m!(ngpu::Int, jobz::AbstractChar, uplo::AbstractChar, A::$interface{$elty})
            A = Matrix{$elty}(A)

            n = checksquare(A)
            lda     = max(1, stride(A, 2))
            W = similar(A, $relty, n)
            work = Vector{$elty}(undef, 1)
            lwork = -1
            iwork = Vector{Int32}(undef, 1)
            liwork = -1
            info    = Ref{Int}()

            jobz_magma      = char_to_magmaInt(jobz)
            uplo_magma     = char_to_magmaIntUplo(uplo)

            for i in 1:2
                func = eval(@magmafunc_m($syevd))
                func(
                    ngpu,
                    jobz_magma,
                    uplo_magma,
                    n,
                    A,
                    lda,
                    W,
                    work,
                    lwork,
                    iwork,
                    liwork,
                    info
                )

                if i == 1
                    lwork = ceil(Int, nextfloat(real(work[1])))
                    resize!(work, lwork)
                    liwork = Int(iwork[1])
                    resize!(iwork, liwork)
                end
            end

            jobz == 'V' ? (W, A) : W
        end
    end
end

for (syevd, elty, relty) in    ((:cheevd, :ComplexF32, :Float32),
                            (:zheevd, :ComplexF64, :Float64)),
                            interface in (:Matrix, :CuMatrix)
    @eval begin
        function magma_syevd!(jobz::AbstractChar, uplo::AbstractChar, A::$interface{$elty})
            A = Matrix{$elty}(A)

            n = checksquare(A)
            lda     = max(1, stride(A, 2))
            W = similar(A, $relty, n)
            work = Vector{$elty}(undef, 1)
            lwork = -1
            rwork = Vector{$relty}(undef, 1)
            lrwork = -1
            iwork = Vector{Int32}(undef, 1)
            liwork = -1
            info    = Ref{Int}()

            jobz_magma      = char_to_magmaInt(jobz)
            uplo_magma     = char_to_magmaIntUplo(uplo)

            for i in 1:2
                func = eval(@magmafunc($syevd))
                func(
                    jobz_magma,
                    uplo_magma,
                    n,
                    A,
                    lda,
                    W,
                    work,
                    lwork,
                    rwork,
                    lrwork,
                    iwork,
                    liwork,
                    info
                )

                if i == 1
                    lwork = ceil(Int, nextfloat(real(work[1])))
                    resize!(work, lwork)
                    lrwork = ceil(Int, nextfloat(real(rwork[1])))
                    resize!(rwork, lrwork)
                    liwork = Int(iwork[1])
                    resize!(iwork, liwork)
                end
            end

            jobz == 'V' ? (W, A) : W
        end

        function magma_syevd_m!(ngpu::Int, jobz::AbstractChar, uplo::AbstractChar, A::$interface{$elty})
            A = Matrix{$elty}(A)

            n = checksquare(A)
            lda     = max(1, stride(A, 2))
            W = similar(A, $relty, n)
            work = Vector{$elty}(undef, 1)
            lwork = -1
            rwork = Vector{$relty}(undef, 1)
            lrwork = -1
            iwork = Vector{Int32}(undef, 1)
            liwork = -1
            info    = Ref{Int}()

            jobz_magma      = char_to_magmaInt(jobz)
            uplo_magma     = char_to_magmaIntUplo(uplo)

            for i in 1:2
                func = eval(@magmafunc_m($syevd))
                func(
                    ngpu,
                    jobz_magma,
                    uplo_magma,
                    n,
                    A,
                    lda,
                    W,
                    work,
                    lwork,
                    rwork,
                    lrwork,
                    iwork,
                    liwork,
                    info
                )

                if i == 1
                    lwork = ceil(Int, nextfloat(real(work[1])))
                    resize!(work, lwork)
                    lrwork = ceil(Int, nextfloat(real(rwork[1])))
                    resize!(rwork, lrwork)
                    liwork = Int(iwork[1])
                    resize!(iwork, liwork)
                end
            end

            jobz == 'V' ? (W, A) : W
        end
    end
end


"""
```
simulate_switching(m, scen::SwitchingScenario; verbose = :low)
```

Simulate switching in and out of a default scenario for the
SwitchingScenario scen. See `SwitchingScenario` for more
info. Returns a dictionary of results and the proportion
of times a switch actually occured.
"""
function simulate_switching(m::AbstractModel, scen::SwitchingScenario;
                            verbose::Symbol = :low)

    if VERBOSITY[verbose] >= VERBOSITY[:low]
        info("Simulating switching for " * string(scen.key) * "...")
        println("Start time: " * string(now()))
        println("Outputs will be saved in " * rawpath(m, "scenarios"))
        tic()
    end

    # Revert model alt policy to historical rule
    m <= Setting(:alternative_policy, AltPolicy(:historical, solve, eqcond), false, "apol",
                 "Alternative policy")

    # Get output file names
    original_output_files = get_scenario_output_files(m, scen.original,
                                                      [:forecastobs, :forecastpseudo])
    default_output_files = get_scenario_output_files(m, scen.default,
                                                     [:forecastobs, :forecastpseudo])

    results = Dict{Symbol, Array{Float64}}()
    switching_results = [0.0, 0.0]

    for (i, output_var) in enumerate([:forecastobs, :forecastpseudo])
        # Read in original and default draws
        original_draws = load(original_output_files[output_var], "arr")
        default_draws  = load(default_output_files[output_var], "arr")

        n_draws, n_vars, n_periods = size(original_draws)
        n_default_draws = size(default_draws, 1)

        @assert n_vars == size(default_draws, 2)
        @assert length(scen.probs_enter) == n_periods <= size(default_draws, 3)

        # Simulate switching
        results[output_var] = zeros(n_draws, n_vars, n_periods)
        proportion_switched = 0.0
        for j = 1:n_draws
            k = rand(1:n_default_draws)
            results[output_var][j, :, :], switched = switch(original_draws[j, :, :], default_draws[k, :, :],
                                                            scen.probs_enter, scen.probs_exit)
            proportion_switched += Float64(switched)
        end
        switching_results[i] = proportion_switched / n_draws
    end

    results[:proportion_switched] = [mean(switching_results)]

    # Write outputs
    output_files = get_scenario_output_files(m, scen, [:forecastobs, :forecastpseudo])
    write_scenario_forecasts(m, output_files, results, verbose = verbose)

    # Print
    if VERBOSITY[verbose] >= VERBOSITY[:low]
        switching_time = toq()
        switching_time_min = switching_time/60
        println("\nTime elapsed: " * string(switching_time_min) * " minutes")
        println("Switching complete: " * string(now()))
    end

    return results, switching_results
end

"""
```
switch(original, default, probs_enter, probs_exit)
```

Simulate entry and exit from the original vector into the default one
according to the specified probabilities.
"""
function switch(original::Matrix{Float64}, default::Matrix{Float64},
                probs_enter::Vector{Float64}, probs_exit::Vector{Float64})

    n_vars, n_periods = size(original)
    output = zeros(n_vars, n_periods)

    # Find period of switch from default to original
    period_in = choose_switch_period(probs_enter, 1)

    # Record whether original is actually switched into
    switched = period_in < n_periods

    # Find period of switch from original to default
    # Start in period_in + 1 s.t. at least 1 period is spent in the original scenario
    period_out = choose_switch_period(probs_exit, period_in + 1)

    output[:, 1:(period_in-1)]          = default[:, 1:(period_in-1)]
    output[:, period_in:(period_out-1)] = original[:, 1:(period_out-period_in)]
    output[:, period_out:n_periods]     = default[:, period_out:n_periods]

    return output, switched
end

function choose_switch_period(probs::Vector{Float64}, first_period::Int)
    n_periods = length(probs)

    for t = first_period:n_periods
        if rand() < probs[t]
            return t
        end
    end

    return n_periods + 1
end


const DATA_DUMMY = GLfloat[0,0,0]

const DATA_CUBE = GLfloat[
# Bottom
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,

# Top
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,

# Front
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,

# Back
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,

# Left
-1.0, -1.0, 1.0,
-1.0, 1.0, -1.0,
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0,

# Right
1.0, -1.0, 1.0,
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, 1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
]

const DATA_CUBE_VERTEX = [
1f0,  1,  1,  # 0
-1,  1,  1,   # 1
-1, -1,  1,   # 2
1, -1,  1,    # 3
1, -1, -1,    # 4
-1, -1, -1,   # 5
-1,  1, -1,   # 6
1,  1, -1,    # 7
]

const DATA_CUBE_INDEX = GLuint[
0, 1, 2, 2, 3, 0, # Front face
7, 4, 5, 5, 6, 7, # Back face
6, 5, 2, 2, 1, 6, # Left face
7, 0, 3, 3, 4, 7, # Right face
7, 6, 1, 1, 0, 7, # Top face
3, 2, 5, 5, 4, 3  # Bottom face
]

const DATA_PLANE_VERTEX  = [
1f0,  0,  1,  # 0
1, 0, -1,     # 1
-1,  0,  1,   # 2
-1, 0, -1     # 3
]

const DATA_PLANE_INDEX = GLuint[
0, 1, 2, 3 #GL_TRIANGLE_STRIP
#0, 1, 2, 2, 1, 3 #GL_TRIANGLES
]


struct Replicate_Report
    ids::Vector{Chain_ID} #replicates
    state_vecs::Dict{Chain_ID,Matrix{Float64}} #vectors of autotransition probabilities evolving by iterate, concatanated to matrices, indexed by Chain_ID
    emission_arrays::Dict{Chain_ID,Array{Float64}} #iterate*symbol*state_vecs
    sorted_id1_states::Vector{Integer}
    sort_dicts::Dict{Chain_ID,Dict{Integer,Integer}}#emission channels sorted on the basis of euclidean closeness to ids[1]
    sorted_symbols::Dict{Integer,Vector{Integer}}

    Replicate_Report(ids,state_vecs,emission_arrays, sorted_id1_states,sort_dicts,sorted_symbols) = assert_repreport(ids) && new(ids,state_vecs,emission_arrays,sorted_id1_states,sort_dicts,sorted_symbols)
end

function assert_repreport(ids::Vector{Chain_ID})
    Ks=Vector{Int64}()
    orders=Vector{Int64}()
    replicates=Vector{Int64}()
    obsids=Vector{String}()

    for id in ids
        push!(Ks,id.K)
        push!(orders,id.order)
        push!(obsids,id.obs_id)
        push!(replicates, id.replicate)
    end

    length(unique(Ks)) > 1 && throw(ArgumentError("Replicate set includes chains with different K state numbers! All chains must have HMMs with the same number of states."))
    length(unique(orders)) > 1 && throw(ArgumentError("Replicate set includes chains with different order coding numbers! All chains must have HMMs with the same order coding."))
    !allunique(replicates) && throw(ArgumentError("Replicate set includes chains with the same replicate #. Replicates should be unique!"))

    return true
end

function report_replicates(repset::Vector{Chain_ID},chains::Dict{Chain_ID,Vector{EM_step}})
    assert_repreport(repset) #check that the repset will pass its constructor before doing the work
    emission_arrays=Dict{Chain_ID,AbstractArray{AbstractFloat}}()
    state_arrays=Dict{Chain_ID,AbstractArray{AbstractFloat}}()

    for id in repset
        emission_array=zeros(length(chains[id]),4^(id.order+1),id.K)
        state_array=zeros(length(chains[id]),id.K)
        for (it, step) in enumerate(chains[id])
            for k in 1:id.K
                emission_array[it,:,k]=step.hmm.B[k].p
                state_array[it,k]=step.hmm.A[k,k]
            end
        end
        emission_arrays[id]=emission_array
        state_arrays[id]=state_array
    end

    println(repset)
    println(emission_arrays)
    println(state_arrays)
    sort_dicts,sorted_id1_states,sorted_symbols=sort_emitters_by_distance!(repset,emission_arrays)

    return Replicate_Report(repset,state_arrays,emission_arrays,sorted_id1_states, sort_dicts,sorted_symbols)
end

function sort_emitters_by_distance!(repset,emission_arrays)
    id1=repset[1]
    length(repset)==1 && (return Dict{Chain_ID,Dict{Integer,Integer}}(),[1:id1.K...],[1:4^id1.order+1])

    final_emissions = zeros(length(repset),size(emission_arrays[id1])[2:3]...)
    for (n,id) in enumerate(repset)
        final_emissions[n,:,:] = emission_arrays[id][end,:,:]
    end

    distances=zeros(length(repset)-1,id1.K,id1.K)
    for rep in 1:length(repset)-1
        for k in 1:id1.K, j in 1:id1.K
            distances[rep,k,j]=euclidean(final_emissions[1,:,k],final_emissions[1+rep,:,j])
        end
    end

    sortdicts=Dict{Chain_ID,Dict{Integer,Integer}}()
    sorted_id1_states=Vector{Integer}()
    sorted_symbols=Dict{Integer,Vector{Integer}}()
    for rep in 1:length(repset)-1
        sortdicts[repset[rep+1]]=Dict{Integer,Integer}()
    end

    for i in 1:id1.K
        id1_mindist_K=findmin([sum([minimum(distances[rep,k,:]) for rep in 1:length(repset)-1]) for k in 1:id1.K])[2]#find the state from the first replicate that has minimum cumulative distance to the closest state in the other replicates, excluding already-chosen states by spiking their values
        push!(sorted_id1_states,id1_mindist_K)
        for rep in 1:length(repset)-1
            rep_k=findmin(distances[rep,id1_mindist_K,:])[2]
            sortdicts[repset[rep+1]][id1_mindist_K]=rep_k
            distances[rep,:,rep_k].=1.0 #mask
            distances[1,id1_mindist_K,:].=1.0 #mask
        end

        symbol_distances=sum(hcat([euclidean.(final_emissions[1,:,id1_mindist_K],final_emissions[n+1,:,sortdicts[id][id1_mindist_K]]) for (n,id) in enumerate(repset[2:end])]...),dims=2)
        sorted_symbols[id1_mindist_K]=sortperm(symbol_distances[:,1])
    end

    return sortdicts,sorted_id1_states,sorted_symbols
end

function Base.show(io::IO, report::Replicate_Report; top_states::Integer=report.ids[1].K, top_symbols::Integer=2)
    printstyled(io, "REPLICATE CONVERGENCE REPORT\n", color=:green)
    println(" ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶ ̶̶ ̶ ̶ ̶")
    for i in 1:top_states
        id=report.ids[1]
        id1_state=report.sorted_id1_states[i]
        itmax=maximum([size(report.state_vecs[id],1) for id in report.ids])

        autoplt=lineplot(report.state_vecs[id][:,id1_state],
                        title="Autotransition prob. convergence",
                        name="$(report.ids[1]) K$id1_state",
                        xlim=(0,ceil(itmax, sigdigits=2)),
                        ylim=(0,1),
                        xlabel="iterate",
                        ylabel="p")

        symplot=lineplot(report.emission_arrays[id][:,report.sorted_symbols[id1_state][1],id1_state],
                        report.emission_arrays[id][:,report.sorted_symbols[id1_state][2],id1_state],
                        title="Symbol prob. convergence",
                        name="$(report.ids[1]) K$id1_state",
                        xlim=(0,1),
                        ylim=(0,1),
                        xlabel="Symbol 1 p",
                        ylabel="S2 p")

        for (n,id) in enumerate(report.ids)
            if n > 1
                lineplot!(autoplt, report.state_vecs[id][:,report.sort_dicts[id][id1_state]],
                            name="$(report.ids[n]) K$(report.sort_dicts[id][id1_state])")
                lineplot!(symplot,
                            report.emission_arrays[id][:,report.sorted_symbols[id1_state][1],report.sort_dicts[id][id1_state]],
                            report.emission_arrays[id][:,report.sorted_symbols[id1_state][2],report.sort_dicts[id][id1_state]],
                            name="$(report.ids[n]) K$(report.sort_dicts[id][id1_state])")
            end
        end

        show(autoplt)
        println("\n")
        show(symplot)
        println("\n")
    end
end


using Parameters:@with_kw

"""
Stores information of a self-play game.
"""
mutable struct GameHistory
    observation_history::Array{Float32,4}
    action_history::Vector{Int}
    reward_history::Vector{Float32}
    to_play_history::Vector{Int}
    child_visits::Matrix{Float32}
    root_values::Vector{Float32}
    reanalysed_predicted_root_values
    priorities
    game_priority
end

@with_kw struct Config
    seed::Int = 1337
    observation_shape::Tuple{Int,Int,Int} # Dimensions of the game observation, must be 3D (WHC). For a 1D array, please reshape it to (1, 1, length of array)
    action_space
	players
	stacked_observations::Int # Number of previous observations and previous actions to add to the current observation
    muzero_player::Int = 1 # Turn Muzero begins to play (1: MuZero plays first, 2: MuZero plays second)
    opponent::String = "expert" # Hard coded agent that MuZero faces to assess its progress in multiplayer games. It doesn't influence training. None, "random" or "expert" if implemented in the Game class
	intermediate_rewards::Bool = false

    num_workers::Int # Number of simultaneous threads/workers self-playing to feed the replay buffer
    selfplay_on_gpu::Bool = false
    max_moves::Int # Game dependent
    temperature_threshold::Union{Int,Nothing} = nothing # Number of moves before dropping the temperature given by visit_softmax_temperature_fn to 0 (ie selecting the best action). If None, visit_softmax_temperature_fn is used every time

    dirichlet_α::Float32 = 0.25
    exploration_ϵ::Float32 = 0.25
    pb_c_base::Int = 19652
    pb_c_init::Float32 = 1.25
    discount::Float32 = 0.997 # Chronological discount of the reward
    num_iters::Int # Number of future moves self-simulated

    replay_buffer_size::Int = 10000 # Number of self-play games to keep in the replay buffer
    num_unroll_steps::Int # Number of game moves to keep for every batch element
    td_steps::Int # Number of steps in the future to take into account for calculating the target value
    PER::Bool # Prioritized Replay (See paper appendix Training), select in priority the elements in the replay buffer which are unexpected for the network
    PER_alpha::Int = 1 # How much prioritization is used, 0 corresponding to the uniform case, paper suggests 1

    results_path::String = mkpath("./results")
    networks_path::String = mkpath("./networks")
    training_steps::Int # Total number of training steps (ie weights update according to a batch)
    batch_size::Int # Number of parts of games to train on at each training step
    checkpoint_interval::Int = 10 # Number of training steps before using the model for self-playing
    value_loss_weight::Float32 = 0.25 # Scale the value loss to avoid overfitting of the value function, paper recommends 0.25 (See paper appendix Reanalyze)
end

function update_remote_counter!(remote_counter::RemoteChannel, update_count::Int)
	old_value=take!(remote_counter)
	put!(remote_counter, old_value + update_count)
	return nothing
end

@with_kw struct FeedForwardHP
	width_hidden::Int
	depth_representation::Int
	depth_prediction::Int
	depth_dynamics::Int
	depth_policy::Int
	depth_value::Int
	depth_reward::Int
	depth_state_head::Int
	use_batch_norm::Bool = false
	batch_norm_momentum::Float32 = 0.6f0
	hidden_state_size::Int #should be equal to prod(observation_shape)
	reward_activation
end

@with_kw mutable struct ResNetHP
	num_blocks::Int # Number of blocks in the ResNet
	depth_representation::Int
	num_filters::Int
	conv_kernel_size::Tuple{Int,Int}
	num_second_head_filters::Int = 2
	num_first_head_filters::Int = 1
	batch_norm_momentum::Float32 = 0.6f0
	downsample::Bool = false
	hidden_state_size::Int
	representation_output_size
	depth_policy::Int
	depth_value::Int
end


# Compile speed tests
# Tests time to get from nothing to certain tasks
using JuMP  # Ensure compiled before running the tests

# Test 1
# Size 100, aff_str into model_str
tic()
run(`$(ENV["_"]) -e """
using JuMP
m = Model()
N = 100
@variable(m, x[1:N])
@constraint(m, sum{i*x[i],i=1:N} >= N)
@time JuMP.aff_str(JuMP.REPLMode, m.linconstr[end].terms)
@time JuMP.model_str(JuMP.REPLMode, m)"""
`)
test1_time = toq()

# Test 2
# Size 100, model_str
tic()
run(`$(ENV["_"]) -e """
using JuMP
m = Model()
N = 100
@variable(m, x[1:N])
@constraint(m, sum{i*x[i],i=1:N} >= N)
@time JuMP.model_str(JuMP.REPLMode, m)"""
`)
test2_time = toq()

# Test 3
# Size 10,000, print model
tic()
run(`$(ENV["_"]) -e """
using JuMP
m = Model()
N = 10000
@variable(m, x[1:N])
@constraint(m, sum{i*x[i],i=1:N} >= N)
@time sprint(print, m)"""
`)
test3_time = toq()

# Test 4
# Size 10, 4 variables, print model
tic()
run(`$(ENV["_"]) -e """
using JuMP
m = Model()
N = 10
@variable(m, x1[1:N])
@variable(m, x2[1:N,f=1:N])
@variable(m, x3[1:N,f=1:2:N])
@variable(m, x4[[:a,:b,:c]])
@variable(m, x5[[:a,:b,:c],[:d,"e",4]])
@constraint(m,
    sum{i*x1[i],i=1:N} +
    sum{i*f*x2[i,f],i=1:N,f=1:N} +
    sum{i*f*x3[i,f],i=1:N,f=1:2:N} +
    sum(x4) >= N)
@time sprint(print, m)"""
`)
test4_time = toq()

println("Julia ", VERSION)
println("Current branch: $(chomp(readall(`git rev-parse --abbrev-ref HEAD`)))")
println("Current commit: $(chomp(readall(`git rev-parse HEAD`)))")
@printf("Test 1: %7.2f s\n", test1_time)
@printf("Test 2: %7.2f s\n", test2_time)
@printf("Test 3: %7.2f s\n", test3_time)
@printf("Test 4: %7.2f s\n", test4_time)


#
# This file is part of module
#   ModiaMedia (ModiaMedia/src/ModiaMedia.jl)
#

### Data structures that are additionally available for media <: PartialMixtureMedium -----------------------

"""
    R = gasConstant(state)

Return gas constant of MixtureMedium from
`state::MixtureThermodynamicState` in [J/mol.K]
"""
gasConstant(medium::MixtureMedium, state::MixtureThermodynamicState) = undefinedFunction("gasConstant", state)
gasConstant(                       state::MixtureThermodynamicState) = gasConstant(state.medium,state)

# moleToMassFractions: use inner constructor with fluid information.
# massToMoleFractions: use inner constructor with fluid information from abstract medium, since we have that type.


# using FNVHash

include("..\\generalized-parser\\source\\LogProbs.jl")
include("..\\generalized-parser\\source\\ProductSemiRings.jl")
include("..\\chinese-restaurants\\ChineseRestaurants.jl")
# include("JazzGrammars3.jl")
include("JazzGrammars5.jl")

# include("..\\generalized-parser\\source\\ChartParser2.jl")
# include("..\\generalized-parser\\source\\ChartParser5.jl")
include("..\\generalized-parser\\source\\ChartParser6.jl")

include("read_goldstandard.jl")
include("..\\tree-annotation-tool\\treebank_functions.jl")

###################
### Simple Test ###
###################

grammar = make_jazz_grammar()
# grammar = make_plain_PCFG_jazz_grammar()
@time forest = run_chartparser(map(Terminal, ["0maj","1maj","7maj","0maj"]), grammar)
# @time forest = run_chartparser(map(Terminal, ["0maj","0maj","0maj"]), grammar)
println(score(forest))
println(best_tree(forest))

####################
### Catalan Test ###
####################

# chords(n::Int) = [Terminal("0maj") for i in 1:n]
# number_parses(n::Int) = score(run_chartparser(chords(n), grammar))
# catalan_number(n::Int) = div(factorial(BigInt(2*n)), factorial(BigInt(n+1)) * factorial(BigInt(n)))
#
# for n in 1:20
#     println(n, "\t", number_parses(n), "\t", catalan_number(n-1))
# end

######################
### Profiling Test ###
######################

# corpus_dir = "C:\\Users\\Daniel\\Documents\\Bitbucket\\musical-grammars\\data\\Jazz-selection\\"
# testfile = "'Round Midnight_id_05848_midicons.txt"
# testfile2 = "After You've Gone_id_06924_allanah.txt"
#
# function translate_corpus_notation(chord::AbstractString)
#     m = match(r"([A-G]b*#*)(.*)(_)", chord)
#     string(pitch_class(m[1]), m[2])
# end
#
# function read_chords(file::AbstractString)
#     chords = map(x->x[2], map(split, map(strip, readlines(file)[2:end-1])))
#     map(translate_corpus_notation, chords)
# end

# chords = read_chords(corpus_dir*testfile2)
# @time forest = run_chartparser(chords, grammar)
# println(score(forest))
#
# using ProfileView
# Profile.clear()
# @profile run_chartparser(chords, grammar)
# ProfileView.view()

# workspace()


import Base

"""Base class for a single optimization objective.

All objectives must have a field `initial_state` and a field `generator`, at
minimum.
"""
abstract type AbstractControlObjective end

"""Standard optimization objective.

```julia
Objective(;
    initial_state=<initial_state>,
    generator=<genenerator>,
    target_state=<target_state>
)
```

describes an optimization objective where the time evoluation of the given
`initial_state` under the given `generator` aims towards `target_state`. The
`generator` here is e.g. a time-dependent Hamiltonian or Liouvillian.

The most common control problems in quantum control, e.g. state-to-state
transitions or quantum gate implementations can be expressed by simultaneously
fulfilling multiple objectives of this type.

Note that the objective can only be instantiated via keyword arguments.
"""
struct Objective{ST, GT} <: AbstractControlObjective
    initial_state :: ST
    generator :: GT
    target_state :: ST
    function Objective(;initial_state::ST, generator::GT, target_state::ST) where {ST, GT}
        new{ST, GT}(initial_state, generator, target_state)
    end
end

"""Standard optimization objective with a weight.

```julia
WeightedObjective(;
    initial_state=<initial_state>,
    generator=<genenerator>,
    target_state=<target_state>,
    weight=<weight>
)
```

initializes a control objective like [`Objective`](@ref), but with an
additional `weight` parameter (a float generally between 0 and 1) that weights
the objective relative to other objectives that are part of the same control
problem.
"""
struct WeightedObjective{ST, GT} <: AbstractControlObjective
    initial_state :: ST
    generator :: GT
    target_state :: ST
    weight :: Float64
    function WeightedObjective(;initial_state::ST, generator::GT, target_state::ST, weight::Float64) where {ST, GT}
        new{ST, GT}(initial_state, generator, target_state, weight)
    end
end

"""A full control problem with multiple objectives.

```julia
ControlProblem(
   objectives=<list of objectives>,
   pulse_options=<dict of controls to pulse options>,
   tlist=<time grid>,
   kwargs...
)
```

Note that the control problem can only be instantiated via keyword arguments.

The `objectives` are a list of [`AbstractControlObjective`](@ref) instances,
each defining an initial state and a dynamical generator for the evolution of
that state. Usually, the objective will also include a target state (see
[`Objective`](@ref)) and possibly a weight (see [`WeightedObjective`](@ref)).

The `pulse_options` are a dictionary (`IdDict`) mapping controls that occur in
the `objectives` to properties specific to the control method.

The `tlist` is the time grid on which the time evolution of the initial states
of each objective should be propagated.

The remaining `kwargs` are keyword arguments that are passed directly to the
optimal control method. These typically include e.g. the optimization
functional.

The control problem is solved by finding a set of controls that simultaneously
fulfill all objectives.
"""
struct ControlProblem{OT<:AbstractControlObjective, OPT<:AbstractDict, KWT}
    objectives :: Vector{OT}
    pulse_options :: OPT # TODO: make this optional?
    tlist :: Vector{Float64}
    kwargs :: KWT
    function ControlProblem(;objectives, pulse_options, tlist, kwargs...)
        kwargs_dict = Dict(kwargs)  # make the kwargs mutable
        new{typeof(objectives[1]), typeof(pulse_options), typeof(kwargs_dict)}(
            objectives, pulse_options, tlist, kwargs_dict
        )
    end
end

function Base.copy(problem::ControlProblem)
    return ControlProblem(
        objectives=problem.objectives,
        pulse_options=problem.pulse_options,
        tlist=problem.tlist;
        problem.kwargs...
    )
end

# adjoint for the nested-tuple dynamical generator (e.g. `(H0, (H1, ϵ))`)
function dynamical_generator_adjoint(G::Tuple)
    result = []
    for part in G
        if isa(part, Tuple)
            push!(result, (Base.adjoint(part[1]), part[2]))
        else
            push!(result, Base.adjoint(part))
        end
    end
    return Tuple(result)
end

# fallback adjoint
dynamical_generator_adjoint(G) = Base.adjoint(G)

"""
```julia
adjoint(objective)
```

Adjoint of a control objective. The adjoint objective contains the adjoint of
the dynamical generator `obj.generator`, and adjoints of the
`obj.initial_state` / `obj.target_state` if these exist and have an adjoint.
"""
function Base.adjoint(obj::Objective)
    initial_state_adj = obj.initial_state
    try initial_state_adj = Base.adjoint(obj.initial_state) catch end
    generator_adj = dynamical_generator_adjoint(obj.generator)
    target_adj = obj.target_state
    try target_adj = Base.adjoint(obj.target_state) catch end
    return Objective(
        initial_state=initial_state_adj,
        generator=generator_adj,
        target_state=target_adj
    )
end

function Base.adjoint(obj::WeightedObjective)
    initial_state_adj = obj.initial_state
    try initial_state_adj = Base.adjoint(obj.initial_state) catch end
    generator_adj = dynamical_generator_adjoint(obj.generator)
    target_adj = obj.target_state
    try target_adj = Base.adjoint(obj.target_state) catch end
    return WeightedObjective(
        initial_state=initial_state_adj,
        generator=generator_adj,
        target_state=target_adj,
        weight=obj.weight
    )
end


function BigFraction(arg0::BigInteger)
    return BigFraction((BigInteger,), arg0)
end

function BigFraction(arg0::BigInteger, arg1::BigInteger)
    return BigFraction((BigInteger, BigInteger), arg0, arg1)
end

function BigFraction(arg0::jdouble)
    return BigFraction((jdouble,), arg0)
end

function BigFraction(arg0::jdouble, arg1::jdouble, arg2::jint)
    return BigFraction((jdouble, jdouble, jint), arg0, arg1, arg2)
end

function BigFraction(arg0::jdouble, arg1::jint)
    return BigFraction((jdouble, jint), arg0, arg1)
end

function BigFraction(arg0::jint)
    return BigFraction((jint,), arg0)
end

function BigFraction(arg0::jint, arg1::jint)
    return BigFraction((jint, jint), arg0, arg1)
end

function BigFraction(arg0::jlong)
    return BigFraction((jlong,), arg0)
end

function BigFraction(arg0::jlong, arg1::jlong)
    return BigFraction((jlong, jlong), arg0, arg1)
end

function abs(obj::BigFraction)
    return jcall(obj, "abs", BigFraction, ())
end

function add(obj::BigFraction, arg0::BigFraction)
    return jcall(obj, "add", BigFraction, (BigFraction,), arg0)
end

function add(obj::BigFraction, arg0::BigInteger)
    return jcall(obj, "add", BigFraction, (BigInteger,), arg0)
end

function add(obj::BigFraction, arg0::jint)
    return jcall(obj, "add", BigFraction, (jint,), arg0)
end

function add(obj::BigFraction, arg0::jlong)
    return jcall(obj, "add", BigFraction, (jlong,), arg0)
end

function big_decimal_value(obj::BigFraction)
    return jcall(obj, "bigDecimalValue", BigDecimal, ())
end

function big_decimal_value(obj::BigFraction, arg0::jint)
    return jcall(obj, "bigDecimalValue", BigDecimal, (jint,), arg0)
end

function big_decimal_value(obj::BigFraction, arg0::jint, arg1::jint)
    return jcall(obj, "bigDecimalValue", BigDecimal, (jint, jint), arg0, arg1)
end

function byte_value(obj::Number)
    return jcall(obj, "byteValue", jbyte, ())
end

function compare_to(obj::BigFraction, arg0::BigFraction)
    return jcall(obj, "compareTo", jint, (BigFraction,), arg0)
end

function divide(obj::BigFraction, arg0::BigFraction)
    return jcall(obj, "divide", BigFraction, (BigFraction,), arg0)
end

function divide(obj::BigFraction, arg0::BigInteger)
    return jcall(obj, "divide", BigFraction, (BigInteger,), arg0)
end

function divide(obj::BigFraction, arg0::jint)
    return jcall(obj, "divide", BigFraction, (jint,), arg0)
end

function divide(obj::BigFraction, arg0::jlong)
    return jcall(obj, "divide", BigFraction, (jlong,), arg0)
end

function double_value(obj::BigFraction)
    return jcall(obj, "doubleValue", jdouble, ())
end

function equals(obj::BigFraction, arg0::Object)
    return jcall(obj, "equals", jboolean, (Object,), arg0)
end

function float_value(obj::BigFraction)
    return jcall(obj, "floatValue", jfloat, ())
end

function get_denominator(obj::BigFraction)
    return jcall(obj, "getDenominator", BigInteger, ())
end

function get_denominator_as_int(obj::BigFraction)
    return jcall(obj, "getDenominatorAsInt", jint, ())
end

function get_denominator_as_long(obj::BigFraction)
    return jcall(obj, "getDenominatorAsLong", jlong, ())
end

function get_field(obj::BigFraction)
    return jcall(obj, "getField", BigFractionField, ())
end

function get_numerator(obj::BigFraction)
    return jcall(obj, "getNumerator", BigInteger, ())
end

function get_numerator_as_int(obj::BigFraction)
    return jcall(obj, "getNumeratorAsInt", jint, ())
end

function get_numerator_as_long(obj::BigFraction)
    return jcall(obj, "getNumeratorAsLong", jlong, ())
end

function get_reduced_fraction(::Type{BigFraction}, arg0::jint, arg1::jint)
    return jcall(BigFraction, "getReducedFraction", BigFraction, (jint, jint), arg0, arg1)
end

function hash_code(obj::BigFraction)
    return jcall(obj, "hashCode", jint, ())
end

function int_value(obj::BigFraction)
    return jcall(obj, "intValue", jint, ())
end

function long_value(obj::BigFraction)
    return jcall(obj, "longValue", jlong, ())
end

function multiply(obj::BigFraction, arg0::BigFraction)
    return jcall(obj, "multiply", BigFraction, (BigFraction,), arg0)
end

function multiply(obj::BigFraction, arg0::BigInteger)
    return jcall(obj, "multiply", BigFraction, (BigInteger,), arg0)
end

function multiply(obj::BigFraction, arg0::jint)
    return jcall(obj, "multiply", BigFraction, (jint,), arg0)
end

function multiply(obj::BigFraction, arg0::jlong)
    return jcall(obj, "multiply", BigFraction, (jlong,), arg0)
end

function negate(obj::BigFraction)
    return jcall(obj, "negate", BigFraction, ())
end

function percentage_value(obj::BigFraction)
    return jcall(obj, "percentageValue", jdouble, ())
end

function pow(obj::BigFraction, arg0::BigInteger)
    return jcall(obj, "pow", BigFraction, (BigInteger,), arg0)
end

function pow(obj::BigFraction, arg0::jdouble)
    return jcall(obj, "pow", jdouble, (jdouble,), arg0)
end

function pow(obj::BigFraction, arg0::jint)
    return jcall(obj, "pow", BigFraction, (jint,), arg0)
end

function pow(obj::BigFraction, arg0::jlong)
    return jcall(obj, "pow", BigFraction, (jlong,), arg0)
end

function reciprocal(obj::BigFraction)
    return jcall(obj, "reciprocal", BigFraction, ())
end

function reduce(obj::BigFraction)
    return jcall(obj, "reduce", BigFraction, ())
end

function short_value(obj::Number)
    return jcall(obj, "shortValue", jshort, ())
end

function signum(obj::BigFraction)
    return jcall(obj, "signum", jint, ())
end

function subtract(obj::BigFraction, arg0::BigFraction)
    return jcall(obj, "subtract", BigFraction, (BigFraction,), arg0)
end

function subtract(obj::BigFraction, arg0::BigInteger)
    return jcall(obj, "subtract", BigFraction, (BigInteger,), arg0)
end

function subtract(obj::BigFraction, arg0::jint)
    return jcall(obj, "subtract", BigFraction, (jint,), arg0)
end

function subtract(obj::BigFraction, arg0::jlong)
    return jcall(obj, "subtract", BigFraction, (jlong,), arg0)
end

function to_string(obj::BigFraction)
    return jcall(obj, "toString", JString, ())
end


function total_makespan(LS::LoadingSequence, CTS::Constants)
    makespan = 0
    flag = Array{Int,1}()
    while Set(collect(1:CTS.Q)) != Set(flag)
        for t in reverse(LS.order)
            for q = 1:CTS.Q
                if t.qc == q && !(q in flag)
                    push!(flag, q)
                    if t.start_time + 2*t.task.t > makespan
                        makespan = t.start_time + 2*t.task.t
                        break
                    end
                end
            end
        end
    end
    return(makespan)
end

#get QC moves and times
function get_qc_moves(LS::LoadingSequence, CTS::Constants)
    makespan = total_makespan(LS, CTS)
    QC_MOVES=Dict{Int, Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}}()
    for q = 1:CTS.Q
        l=Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}()
        push!(l, (bay=init_bay(q, CTS), end_time=0, start_time=0, time=0))
        for t in LS.order
            if t.qc == q
                push!(l, (bay=t.task.b, end_time=t.start_time + 2*t.task.t, start_time=t.start_time, time=t.task.t))
            end
        end
        push!(l,(bay=l[end].bay, end_time=makespan, start_time=makespan, time=0))
        QC_MOVES[q]=l
    end
    return(QC_MOVES)
end

#get QC travel times
function get_qc_travel(QC_MOVES::Dict{Int, Array{NamedTuple{(:bay, :end_time, :start_time, :time),Tuple{Int64,Int64,Int64,Int64}}, 1}}, CTS::Constants)
    QC_TRAVEL=Dict{Int, Array{NamedTuple{(:start_bay, :end_bay, :start_time, :end_time),Tuple{Int64,Int64,Int64,Int64}}, 1}}()
    for q = 1:CTS.Q
        l=Array{NamedTuple{(:start_bay, :end_bay, :start_time, :end_time),Tuple{Int64,Int64,Int64,Int64}}, 1}()
        for t = 1:length(QC_MOVES[q])-1
            if QC_MOVES[q][t].bay != QC_MOVES[q][t+1].bay
                start_time = QC_MOVES[q][t].end_time
                end_time = QC_MOVES[q][t+1].start_time
                start_bay = QC_MOVES[q][t].bay
                end_bay = QC_MOVES[q][t+1].bay
                if end_time - start_time <= abs(start_bay - end_bay)*CTS.tt
                    push!(l, (start_bay = start_bay, end_bay = end_bay, start_time = start_time, end_time = end_time))
                else
                    push!(l, (start_bay = start_bay, end_bay = start_bay, start_time = start_time, end_time=end_time - abs(start_bay - end_bay)*CTS.tt))
                    push!(l, (start_bay = start_bay, end_bay = end_bay, start_time = end_time - abs(start_bay - end_bay)*CTS.tt, end_time = end_time))
                end
            end
        end
        if length(QC_MOVES[q]) > 1 && QC_MOVES[q][end].bay == QC_MOVES[q][end-1].bay
            push!(l, (start_bay = QC_MOVES[q][end].bay, end_bay = QC_MOVES[q][end-1].bay, start_time = QC_MOVES[q][end].end_time, end_time=QC_MOVES[q][end-1].end_time))
        end
        QC_TRAVEL[q]=l
    end
    return(QC_TRAVEL)
end

function check_loaded_filled(LS::LoadingSequence, task::LTask)
    if task.p in LS.filled_pos
        #println("position already filled.")
        return(false)
    elseif task.c in LS.loaded_cont
        #println("container already loaded.")
        return(false)
    else
        return(true)
    end
end

#check position precedences
function check_prec(LS::LoadingSequence, task::LTask, prec::Dict{Int, Array})
    if length(prec[task.p]) > 0
        for req in prec[task.p]
            if !(req in LS.filled_pos)
                #print("trying to add position "*string(task.p)*", but still missing positions: ")
                #print(setdiff(precedences[task.p], LS.filled_pos))
                #print("\n")
                return(false)
            end
        end
    end
    return(true)
end

#check if all positions/containers are filled/loaded without repetitions
function check_tasks(LS::LoadingSequence, CTS::Constants)
    deleteat!(LS.filled_pos, findall(x->x==0, LS.filled_pos))
    deleteat!(LS.loaded_cont, findall(x->x==0, LS.loaded_cont))
    if LS.tasks_left == 0 && length(setdiff(collect(1:CTS.P), LS.filled_pos)) == 0 && length(setdiff(collect(1:CTS.C), LS.loaded_cont)) == 0
        return(true)
    else
        return(false)
    end
end

#check clearance to know if a QC can go to a certain bay
function check_clearance(QC::Array{QuayCrane, 1}, q::Int, target_bay::Int, CTS::Constants)
    if target_bay == QC[q].current_bay
        return(true)
    end

    m=0
    if q == 1
        for n = 1:CTS.Q-1
            if (QC[q+n].next_bay - target_bay) > CTS.delta && (QC[q+n].current_bay - target_bay) > CTS.delta
                if length(QC[q+n].task_buffer) > 0
                    for t_buffer in QC[q+n].task_buffer
                        if (t_buffer.b - target_bay) <= CTS.delta
                            return(false)
                        end
                    end
                end
                m+=1
            end
        end
        if m==CTS.Q-1
            return(true)
        end
    elseif q == CTS.Q
        for n = 1:CTS.Q-1
            if (target_bay - QC[q-1].next_bay) > CTS.delta && (target_bay - QC[q-1].current_bay) > CTS.delta
                if length(QC[q-1].task_buffer) > 0
                    for t_buffer in QC[q-1].task_buffer
                        if (target_bay - t_buffer.b) <= CTS.delta
                            return(false)
                        end
                    end
                end
                m+=1
            end
        end
        if m==CTS.Q-1
            return(true)
        end
    else
        if target_bay < QC[q].current_bay
            for n = 1:q-1
                if (target_bay - QC[q-1].next_bay)> CTS.delta && (target_bay - QC[q-1].current_bay) > CTS.delta
                    if length(QC[q-1].task_buffer) > 0
                        for t_buffer in QC[q-1].task_buffer
                            if (target_bay - t_buffer.b) <= CTS.delta
                                return(false)
                            end
                        end
                    end
                    m+=1
                end
            end
            if m==CTS.Q-1
                return(true)
            end
        elseif target_bay > QC[q].current_bay
            for n = q+1:CTS.Q
                if (QC[q+1].next_bay - target_bay) > CTS.delta && (QC[q+1].current_bay - target_bay) > CTS.delta
                    if length(QC[q+1].task_buffer) > 0
                        for t_buffer in QC[q+1].task_buffer
                            if (t_buffer.b - target_bay) <= CTS.delta
                                return(false)
                            end
                        end
                    end
                    m+=1
                end
            end
            if m==CTS.Q-1
                return(true)
            end
        end
    end
    return(false)
end

function check_double_clearance(QC::Array{QuayCrane, 1}, q::Int, target_bay::Int, CTS::Constants)
    if CTS.Q > 2
        if q <= 2 && target_bay > QC[q].current_bay
            if QC[q+1].status == "idle" && (QC[q+2].next_bay - target_bay) > CTS.delta + 2
                    return(true, q+1, target_bay + CTS.delta + 1, "idle")
            elseif QC[q+1].status == "moving" && (QC[q+1].next_bay - target_bay) > CTS.delta
                    return(true, q+1, target_bay + CTS.delta + 1, "moving")
            end
        elseif q >= CTS.Q - 1 && target_bay < QC[q].current_bay
            if QC[q-1].status == "idle" && (target_bay - QC[q-2].next_bay) > CTS.delta + 2
                    return(true, q-1, target_bay - CTS.delta - 1, "idle")
            elseif QC[q-1].status == "moving" && (target_bay - QC[q-1].next_bay) > CTS.delta
                    return(true, q-1, target_bay - CTS.delta - 1, "moving")
            end
        elseif 2 < q && q < CTS.Q - 1
            if target_bay < QC[q].current_bay
                if QC[q-1].status == "idle" && (target_bay - QC[q-2].current_bay) > CTS.delta + 2 && (target_bay - QC[q-2].next_bay) > CTS.delta + 2
                        return(true, q-1, target_bay - CTS.delta - 1, "idle")
                elseif QC[q-1].status == "moving" && (target_bay - QC[q-1].next_bay) > CTS.delta
                        return(true, q-1, target_bay - CTS.delta - 1, "moving")
                end
            elseif target_bay > QC[q].current_bay
                if QC[q+1].status == "idle" && (QC[q+2].next_bay - target_bay) > CTS.delta + 2
                        return(true, q+1, target_bay + CTS.delta + 1, "idle")
                elseif QC[q+1].status == "moving" && (QC[q+1].next_bay - target_bay) > CTS.delta
                        return(true, q+1, target_bay + CTS.delta + 1, "moving")
                end
            end
        end
    else
        if q == 1 && target_bay > QC[q].current_bay
            #if crane is idle, look if there is enough space to move it
            if QC[q+1].status == "idle" && target_bay < CTS.J - CTS.delta
                return(true, q+1, target_bay + CTS.delta + 1, "idle")
            #if crane is moving, look if it will move enough
            elseif QC[q+1].status == "moving" && (QC[q+1].next_bay - target_bay) > CTS.delta
                if target_bay < CTS.J - CTS.delta
                    return(true, q+1, target_bay + CTS.delta + 1, "moving")
                end
            end
        elseif q == CTS.Q && target_bay < QC[q].current_bay
            #if crane is idle, look if there is enough space to move it
            if QC[q-1].status == "idle" && target_bay > CTS.delta + 1
                return(true, q-1, target_bay - CTS.delta - 1, "idle")
            #if crane is moving, look if it will move enough
            elseif QC[q-1].status == "moving" && (target_bay - QC[q-1].next_bay) > CTS.delta
                if target_bay > 1 + CTS.delta
                    return(true, q-1, target_bay - CTS.delta - 1, "moving")
                end
            end
        end
    end
    return(false, 0, 0, "false")
end

function check_triple_clearance(QC::Array{QuayCrane, 1}, q::Int, target_bay::Int, CTS::Constants)
    if CTS.Q > 2
        if q == 1 && target_bay > QC[q].current_bay
            if QC[q+1].status == "idle" && QC[q+2].status == "idle" && (QC[q+3].next_bay - target_bay) > CTS.delta + 4
                    return(true, q+1, target_bay + CTS.delta + 1, q+2, target_bay + CTS.delta + 3)
            end
        elseif q == CTS.Q && target_bay < QC[q].current_bay
            if QC[q-1].status == "idle" && QC[q-2].status == "idle" && (target_bay - QC[q-3].next_bay) > CTS.delta + 4
                    return(true, q-1, target_bay - CTS.delta - 1, q-2, target_bay - CTS.delta - 3)
            end
        elseif q == 2 && target_bay > QC[q].current_bay
            if QC[q+1].status == "idle" && QC[q+2].status == "idle" && (CTS.J - target_bay) > CTS.delta + 4
                    return(true, q+1, target_bay + CTS.delta + 1, q+2, target_bay + CTS.delta + 3)
            end
        elseif q == 3 && target_bay < QC[q].current_bay
            if QC[q-1].status == "idle" && QC[q-2].status == "idle" && (target_bay - 1) > CTS.delta + 4
                    return(true, q-1, target_bay - CTS.delta - 1, q-2, target_bay - CTS.delta - 3)
            end
        end
    end
    return(false, 0, 0, 0, 0)
end

function is_crane_done(q::Int, QC::Array{QuayCrane,1}, CTS::Constants)
    work_load = 0
    for j in QC[q].available_bays
        work_load += remaining_bay_time(j, tasks_by_w, bj, LS, CTS)
    end
    if work_load == 0
        return(true)
    else
        return(false)
    end
end

#check if a solution is correct
function check_solution(prec::Dict{Int, Array}, LS::LoadingSequence, CTS::Constants)
    #check positions and containers
    if check_tasks(LS, CTS) == false
        # println("missing tasks")
        return(false)
    end

    #check precedences are followed
    for task in LS.order
        if task[1].p != 0
            if check_prec(LS, task[1], prec) == false
                println("wrong precedences")
                return(false)
            end
        end
    end

    #check the QC constraints
    QC_MOVES=get_qc_moves(LS, CTS)
    QC_TRAVEL=get_qc_travel(QC_MOVES, CTS)
    for clock = 1:total_makespan(LS, CTS)
        # println()
        # println("|||||||||||   "*string(clock)*"   ||||||||||||||")
        # println()
        current_cranes=Array{Tuple{Int, Number}, 1}()
        # println("LOADING CRANE")
        for (key, value) in QC_MOVES
            if clock == 1
                for i = 1: length(value)-1
                    #check loading + travel time
                    if value[i].end_time + travel_time(value[i].bay, value[i+1].bay, CTS) > value[i+1].start_time
                        # println("wrong task/travel time")
                        # println("QUAY CRANE: "*string(key))
                        # println(value[i])
                        # println(value[i+1])
                        # println("-------------- ")
                        return(false)
                    end
                    #check that cranes respect bays
                    if !(value[i].bay in subset_bay(CTS, key))
                        println("wrong bays")
                        return(false)
                    end
                end
                if !(value[length(value)].bay in subset_bay(CTS, key))
                    println("wrong bays")
                    return(false)
                end
            end

            for tuple in value
                if tuple.start_time <= clock && clock <= tuple.start_time + 2*tuple.time
                    push!(current_cranes, (key, tuple.bay))
                    # println("time: "*string(clock)*", crane: "*string(key)*", bay: "*string(tuple.bay))
                end
            end
        end

        # println("-----------------------------")
        # println("MOVING CRANE")
        for (key_travel, value_travel) in QC_TRAVEL
            for tuple in value_travel
                if tuple.start_time <= clock && clock <= tuple.end_time && tuple.start_bay != tuple.end_bay
                    current_bay = (clock-tuple.end_time)*(tuple.start_bay-tuple.end_bay)/(tuple.start_time-tuple.end_time) + tuple.end_bay
                    push!(current_cranes, (key_travel, current_bay))
                    # println("time: "*string(clock)*", crane: "*string(key_travel)*", bay: "*string(current_bay))
                end
            end
        end
        # println("-----------------------------")

        #check clearance
        sort(current_cranes, by = first)
        unique!(current_cranes)
        #println(current_cranes)

        for b = 1:length(current_cranes)-1
            if abs(current_cranes[b][2] - current_cranes[b+1][2]) <= CTS.delta
                # println(QC)
                # println("wrong clearance")
                return(false)
            end
        end
    end
    return(true)
end

#plot the solution
using Gadfly
using DataFrames
Gadfly.set_default_plot_size(25cm, 10cm)
function plot_solution(LS::LoadingSequence, makespan::Int, CTS::Constants)
    QC_MOVES=get_qc_moves(LS, CTS)
    QC_TRAVEL=get_qc_travel(QC_MOVES, CTS)

    list_moves=[]
    list_travel=[]
    for n=1:CTS.Q
        push!(list_moves, DataFrame(Bay = [x.bay for x in QC_MOVES[n]], Start = [x.start_time for x in QC_MOVES[n]], End = [x.end_time for x in QC_MOVES[n]], QuayCrane = map(i -> "QC - " * string(i), n)))
        push!(list_travel, DataFrame(start_bay = [x.start_bay for x in QC_TRAVEL[n]], end_bay = [x.end_bay for x in QC_TRAVEL[n]], start_time = [x.start_time for x in QC_TRAVEL[n]], end_time = [x.end_time for x in QC_TRAVEL[n]], QuayCrane = map(i -> "QC - " * string(i), n)))
    end
    df_moves=vcat(list_moves...)
    df_travel=vcat(list_travel...)

    #plot scale
    xsc  = Scale.x_continuous(minvalue=0, maxvalue=makespan)
    ysc  = Scale.y_continuous(minvalue=0, maxvalue=CTS.J+1)
    xtk = collect(0:60:makespan)
    push!(xtk, makespan)
    ytk = collect(1:CTS.J)
    #plot(df, x = :Start, xend = :Time, y = :Bay, yend = :Bay, color = :QuayCrane, Geom.segment(filled=false), Theme(line_width=1cm, major_label_font="CMU Serif",minor_label_font="CMU Serif",
          #     major_label_font_size=16pt,minor_label_font_size=14pt), xsc, ysc)

    layer1 = layer(df_moves, x = :Start, xend = :End, y = :Bay, yend = :Bay, color = :QuayCrane, Geom.segment(filled=false), Theme(alphas=[0.5], line_width=0.25cm, major_label_font="CMU Serif",minor_label_font="CMU Serif",
               major_label_font_size=16pt,minor_label_font_size=14pt))
    layer2 = layer(df_travel, x = :start_time, xend = :end_time, y = :start_bay, yend = :end_bay, color = :QuayCrane, Geom.segment(filled=false), Theme(line_style=[:dot] ,line_width=0.05cm, major_label_font="CMU Serif",minor_label_font="CMU Serif",
               major_label_font_size=16pt,minor_label_font_size=14pt))

    plot(layer1, layer2, xsc, ysc, Guide.xticks(ticks=xtk), Guide.yticks(ticks=ytk))

end


# ccall will call the Conversions we have defined
# TODO: I am dubious about all of this; check it all.

using Base.Cartesian.@ntuple

@inline vinit8{T}(x::T) = @ntuple 8 k->k==1 ? VecElement{T}(x) : VecElement{T}(zero(T))
@inline vinit4{T}(x::T) = @ntuple 4 k->k==1 ? VecElement{T}(x) : VecElement{T}(zero(T))
@inline vinit2{T}(x::T) = @ntuple 2 k->k==1 ? VecElement{T}(x) : VecElement{T}(zero(T))

##Float64
typealias __m256d NTuple{4, VecElement{Float64}}
@inline Base.convert(::Type{__m256d}, x::Float64) = __m256d(vinit4(x))
@inline Base.convert(::Type{Float64}, x::__m256d) = first(x).value

typealias __m128d NTuple{2, VecElement{Float64}}
@inline Base.convert(::Type{__m128d}, x::Float64) = __m128d(vinit2(x))
@inline Base.convert(::Type{Float64}, x::__m128d) = first(x).value

##Float32
typealias __m256 NTuple{8, VecElement{Float32}}
@inline Base.convert(::Type{__m256}, x::Float32) = __m256(vinit8(x))
@inline Base.convert(::Type{Float32}, x::__m256) = first(x).value

typealias __m128 NTuple{4, VecElement{Float32}}
@inline Base.convert(::Type{__m128}, x::Float32) = __m128(vinit4(x))
@inline Base.convert(::Type{Float32}, x::__m128) = first(x).value

##Int32
typealias __m256i NTuple{8, VecElement{Int32}}
@inline Base.convert(::Type{__m256i}, x::Int32) = __m256i(vinit8(x))
@inline Base.convert(::Type{Int32}, x::__m256i) = first(x).value

typealias __m128i NTuple{4, VecElement{Int32}}
@inline Base.convert(::Type{__m128i}, x::Int32) = __m128i(vinit4(x))
@inline Base.convert(::Type{Int32}, x::__m128i) = first(x).value

##########

if SIMD_ARCH == "sse2"
	typealias VDouble __m128d
	typealias VInt __m128i
	typealias VFloat __m128
	typealias VInt2 __m128i

elseif SIMD_ARCH == "avx" || SIMD_ARCH == "fma4"
	typealias VDouble __m256d
	typealias VInt __m128i
	typealias VFloat __m256

	immutable VInt2
		x::VInt
		y::VInt
	end
elseif SIMD_ARCH == "avx2"
	typealias VDouble __m256d
	typealias VInt __m128i
	typealias VFloat __m256
	typealias VInt2 __m256i
elseif SIMD_ARCH == "purec"
	typealias VDouble Float64
	typealias VInt Int32
	typealias VFloat Float32
	typealias VInt2 Int32
else
	error("SIMD instruction set: $(SIMD_ARCH). Not Supported. This error should never occur. Please report this bug.")
end

###########
#Always:

immutable VDouble2
	x::VDouble
	y::VDouble
end
Base.convert(::Type{Tuple{Float64,Float64}}, d::VDouble2) = (convert(Float64, d.x), convert(Float64, d.y))

immutable VFloat2
	x::VFloat
	y::VFloat
end
Base.convert(::Type{Tuple{Float32,Float32}}, d::VFloat2) = (convert(Float32, d.x), convert(Float32, d.y))


# new_LP.jl
# New LP formulation for travel time estimation
# Authored by Arthur J Delarue on 7/2/15

MODEL = "relaxed_smooth"		# relaxed/strict _ smooth/nothing/random
LAST_SMOOTH = false 			# true if last iteration is smooth, false otherwise. Irrelevant if model is smooth already.
ERROR_COMPUTATION = "average" 		# average/single-ride/both

MAX_ROUNDS = 2					# max number of iterations

MIN_RIDES = 1					# min number of rides
RADIUS = 140					# radius (4D Euclidian)
YEAR = 2013
START_TIME = 12
END_TIME = 14
START_MONTH = 1
END_MONTH = 1
WEEKDAYS = true 				# true for weekdays, false for weekends
METHOD = "average"				# average/nearest [neighbor]

RANDOM_CONSTRAINTS = false 		# true for purely random constraints, false otw
DYNAMIC_CONSTRAINTS = true 		# true for dynamic constraints, false otw
GLOBAL_CONSTRAINT_UPDATE = false # true if we remove worst paths globally, false otw
SAMPLE_SIZE = 1000 				# starting number of constraints
NUM_OD_ADDED = 1000 			# number of (O,D) pairs to add
NUM_OD_REMOVED = 1000			# number of (O,D) pairs to remove
UPDATE_EVERY_N_ITERATIONS = 1 	# add number of (O,D) above every $N iterations
ADD_IF_ABOVE_PERCENTILE = 0.7		# only add rides that have an error above this percentile
REMOVE_IF_BELOW_PERCENTILE = 0.3	# only remove rides that have an error below this percentile

TURN_COST = 0.	 				# turning cost initial value
TURN_COST_AS_VARIABLE = false 	# true if LP updates turning cost, false otw

DELTA_BOUND = [0.06, 0.05, 0.04]	# relaxation base (sketchy)

MAX_NUM_PATHS_PER_OD = 3 		# at least 1 please

COMPUTE_FINAL_SHORTEST_PATHS = true 	# Should always be true unless trying to save time somehow

START_SIMPLE = false 			# true if initial simple LP is used, false otw

METROPOLIS = false 				# always set this to false unless you wish to crash everything

WARMSTART = false

LOCALIZED_TESTING = false

function fast_LP(
	manhattan::Manhattan,
	travelTimes::Array{Float64,2},
	numRides::Array{Int,2},
	testingData::Union{DataFrame, Array{Float64,2}},
	startTimes::AbstractArray{Float64,2};
	errorComputation::AbstractString=ERROR_COMPUTATION,
	model_type::AbstractString=MODEL,
	last_smooth::Bool=LAST_SMOOTH,
	max_rounds::Int=MAX_ROUNDS,
	min_rides::Int=MIN_RIDES,
	radius::Int=RADIUS,
	year::Int=YEAR,
	startTime::Int=START_TIME,
	endTime::Int=END_TIME,
	startMonth::Int=START_MONTH,
	endMonth::Int=END_MONTH,
	method::AbstractString=METHOD,
	sample_size::Int=SAMPLE_SIZE,
	turnCost::Float64=TURN_COST,
	turnCostAsVariable::Bool=TURN_COST_AS_VARIABLE,
	delta_bound::Union{Array{Float64,1},Array{Any,1}}=DELTA_BOUND,
	maxNumPathsPerOD::Int=MAX_NUM_PATHS_PER_OD,
	computeFinalSP::Bool = COMPUTE_FINAL_SHORTEST_PATHS,
	startWithSimpleLP::Bool=START_SIMPLE,
	randomConstraints::Bool=RANDOM_CONSTRAINTS,
	dynamicConstraints::Bool=DYNAMIC_CONSTRAINTS,
	numPairsToAdd::Int = NUM_OD_ADDED,
	numPairsToRemove::Int = NUM_OD_REMOVED,
	iterationMultiple::Int = UPDATE_EVERY_N_ITERATIONS,
	addOnlyIfAbovePercentile::Float64 = ADD_IF_ABOVE_PERCENTILE,
	removeOnlyIfBelowPercentile::Float64 = REMOVE_IF_BELOW_PERCENTILE,
	globalConstraintUpdate::Bool=GLOBAL_CONSTRAINT_UPDATE,
	warmstart::Bool=WARMSTART,
	metropolis::Bool=METROPOLIS,
	localized_testing::Bool=LOCALIZED_TESTING,
	real_TOD_metropolis::AbstractArray{Float64}=zeros(1,1),
	real_tij_metropolis::AbstractArray{Float64}=zeros(1,1),
	prob::Float64=0.0,
	testingData2::Array{Float64,2}=zeros(1,1))

	graph = manhattan.network
	roadTimes = deepcopy(manhattan.roadTime)
	distances = manhattan.distances
	positions = manhattan.positions
	roads = edges(graph)
	nodes = vertices(graph)
	out = [copy(out_neighbors(graph,i)) for i in nodes]
	inn = [copy(in_neighbors(graph,i)) for i in nodes]

	# Find nonzero travel times
	if DYNAMIC_CONSTRAINTS
		numDataPoints = sample_size
		pairs = [copy(Int[]) for i = 1:length(nodes)]
	else
		pairs = [find(travelTimes[i,:]) for i=nodes]
		numDataPoints = sum([length(pairs[i]) for i=nodes])
	end

	# Create output directory name (sorry this is so complicated)
	if ((!last_smooth) && (!metropolis))
		TESTDIR = "fast_r$(radius)_minr$(min_rides)_i$(max_rounds)_wd_$(startTime)$(endTime)_m$(startMonth)$(endMonth)_y$(year)_$(model_type)_ppc$(maxNumPathsPerOD)_data$(method)_error$errorComputation"
	elseif last_smooth && (!metropolis)
		TESTDIR = "fast_r$(radius)_minr$(min_rides)_i$(max_rounds)_wd_$(startTime)$(endTime)_m$(startMonth)$(endMonth)_y$(year)_$(model_type)_lsmooth_ppc$(maxNumPathsPerOD)_data$(method)_error$errorComputation"
	elseif (!last_smooth) && metropolis
		TESTDIR = "fast_metropolis$(prob)_minr$(min_rides)_i$(max_rounds)_$(model_type)_ppc$(maxNumPathsPerOD)"
	elseif last_smooth && metropolis
		TESTDIR = "fast_metropolis$(prob)_minr$(min_rides)_i$(max_rounds)_$(model_type)_lsmooth_ppc$(maxNumPathsPerOD)"
	end
	if randomConstraints
		TESTDIR=string(TESTDIR, "_rnd_rides$(sample_size)")
	elseif dynamicConstraints
		TESTDIR=string(TESTDIR, "_dynConstr_st$(sample_size)_add$(numPairsToAdd)_thr$(addOnlyIfAbovePercentile)_rem$(numPairsToRemove)_thr$(removeOnlyIfBelowPercentile)_every$(iterationMultiple)")
	end
	if globalConstraintUpdate
		TESTDIR=string(TESTDIR, "_gcu")
	end
	if turnCostAsVariable
		TESTDIR=string(TESTDIR, "_tcvar_start$(turnCost)")
	else
		TESTDIR=string(TESTDIR, "_tc$(turnCost)")
	end
	if startWithSimpleLP
		TESTDIR=string(TESTDIR, "_ss")
	end
	if warmstart
		TESTDIR=string(TESTDIR, "_ws")
	end
	if localized_testing
		TESTDIR=string(TESTDIR, "_loctest")
	end

	# Create directory if necessary:
	if !isdir("Outputs/$TESTDIR")
		mkdir("Outputs/$TESTDIR")
	end
	# Add vital information to file
	writeDataToFile("Outputs/$TESTDIR", model_type, max_rounds, min_rides, radius, false, 0, sample_size, turnCost)
	# Turn cost file
	if turnCostAsVariable
		turnCostFile = open("Outputs/$(TESTDIR)/tc.csv", "w")
	end
	# Metropolis error output
	if metropolis
		# specific error output
	else
		errorFile = open("Outputs/$(TESTDIR)/errorstats.csv", "w")
	end
	costFunctionFile = open("Outputs/$(TESTDIR)/cf.csv", "w")
	# Runtime info output
	timeFile = open("Outputs/$(TESTDIR)/timestats.csv", "w")
	if dynamicConstraints
		numConstraintsFile = open("Outputs/$(TESTDIR)/numODpairs.csv", "w")
	end

	# Tell us where output is going
	println("-- Saving outputs to directory Outputs/$(TESTDIR)/")

	# Create path storing array and initialize. By convention totalPaths[i,1] is the equality constraint
	numPaths = zeros(Int, numDataPoints)
	totalPaths = Array(Any, numDataPoints)
	totalNumExpensiveTurns = Array(Any, numDataPoints)
	for i = 1:numDataPoints
		totalPaths[i] = Array{Int}[]
		totalNumExpensiveTurns[i] = Int[]
		sizehint!(totalPaths[i], maxNumPathsPerOD)
		sizehint!(totalNumExpensiveTurns[i], maxNumPathsPerOD)
	end

	if errorComputation == "single-ride" || errorComputation == "both"
		println("**** Building KDTree of nodes for testing ****")
		nodeTree, nodePairs = buildNodeKDTree(manhattan.positions)
	end

	println("**** Initializing dataset ****")
	if dynamicConstraints
		srcs, dsts, newTravelTimes, newNumRides, pairs = chooseStartingConstraints(travelTimes, numRides, pairs, sample_size = sample_size, num_nodes = length(nodes))
	else
		# Run over all pairs of nodes that have data
		srcs = Int[]
		dsts = Int[]
		sizehint!(srcs, sample_size)
		sizehint!(dsts, sample_size)
		for i in nodes, j in nodes
			if travelTimes[i,j] > 0
				# Load sources and destinations: will be useful when adding constraints
				push!(srcs, i)
				push!(dsts, j)
			end
		end
	end

	status = 0
	newTimes = startTimes
	old_objective = 0

	# Compute shortest paths (with turn cost)
	if startWithSimpleLP
		println("**** Finding initial set of paths ****")
		status, newTimes, turnCost, totalPaths, totalNumExpensiveTurns = simple_LP(manhattan, travelTimes, numRides, testingData, TESTDIR, model_type=split(model_type, "_")[1], max_rounds=max_rounds, turnCost=turnCost, turnCostAsVariable=turnCostAsVariable, delta_bound=delta_bound, maxNumPathsPerOD=maxNumPathsPerOD, computeFinalSP=false)
	else
		println("**** Computing shortest paths ****")
		new_graph, new_edge_dists, new_nodes, new_edge_isExpensive = modifyGraphForDijkstra(graph, newTimes, positions, turn_cost=turnCost)
		old_nodes = getInverseMapping(new_nodes, nv(new_graph))
		@time new_sp = parallelShortestPathsWithTurnsAuto(graph, new_graph, new_edge_dists, new_nodes)
	end

	# l is round number
	l = 1
	while l <= max_rounds
		println("###### ROUND $l ######")
		tic()
		actual_l = l
		# Update bound on relaxation variables
		if split(model_type, "_")[1] == "relaxed"
			delta = (1/2)^(div(actual_l-1,length(delta_bound)))*delta_bound[((actual_l-1)%length(delta_bound)) + 1]
			# Avoid numerical issues
			if delta < 1e-4 || l == max_rounds
				delta = 0
			end
		end

		# Create JuMP model for LP and QP
		println("**** Creating LP instance ****")
		if l == 1 && !startWithSimpleLP && !dynamicConstraints
			tolerance = 1e-4
		else
			tolerance = 1e-6
		end
		m = Model(solver=GurobiSolver(TimeLimit=10000, OutputFlag=1, Method=3, BarConvTol=tolerance))

		# Add one variable for each road, for each model
		if !metropolis
			@defVar(m, t[i=nodes,j=out[i]] >= 0.038 * manhattan.distances[i,j])
		else
			@defVar(m, t[i=nodes,j=out[i]] >= startTimes[i,j])
		end

		# Add decision variable for turn cost if necessary
		if turnCostAsVariable
			@defVar(m, tc >= 0)
		else
			tc = turnCost
		end

		# Epsilon is the variable used for the absolute value difference between T and \hat{T}
		@defVar(m, epsilon[i=nodes,j=pairs[i]] >= 0)

		# Create handles for inequality constraints
		if maxNumPathsPerOD > 1
			@defConstrRef inequalityPath[1:numDataPoints, 1:(max_rounds-1)]
		end

		# Set first LP objective
		@setObjective(m, Min, sum{ sqrt(numRides[i,j]/travelTimes[i,j]) * epsilon[i,j], i=nodes, j=pairs[i]})

		# Add path constraints
		println("**** Adding constraints ****")
		if !(startWithSimpleLP) || l > 1
			println("---Fetching paths")
			paths, numExpensiveTurns = reconstructMultiplePathsWithExpensiveTurns(new_sp.previous, srcs, dsts, old_nodes, new_sp.real_destinations, new_edge_isExpensive)
			println("---Updating path array")
		end
		# Update paths
		if !(startWithSimpleLP) || l > 1
			totalPaths, totalNumExpensiveTurns, numPaths = updatePaths(paths, numExpensiveTurns, totalPaths, totalNumExpensiveTurns, numPaths, maxNumPathsPerOD=maxNumPathsPerOD, times=newTimes, turnCost=turnCost, travelTimes=travelTimes, numRides=numRides, srcs=srcs, dsts=dsts, dynamicConstraints=dynamicConstraints, globalConstraintUpdate=globalConstraintUpdate)
		else
			for i = 1:numDataPoints
				numPaths[i] = length(totalPaths[i])
			end
		end
		for i=1:numDataPoints
			# Add inequality constraints
			if numPaths[i] > 1
				for j = 1:(numPaths[i]-1)
					if split(model_type, "_")[1] == "strict"
						inequalityPath[i,j] = @addConstraint(m, sum{t[totalPaths[i][j+1][a],totalPaths[i][j+1][a+1]], a=1:(length(totalPaths[i][j+1])-1)} - sum{t[totalPaths[i][1][a],totalPaths[i][1][a+1]], a=1:(length(totalPaths[i][1])-1)} >= - tc * totalNumExpensiveTurns[i][j+1] + tc * totalNumExpensiveTurns[i][1])
					elseif split(model_type, "_")[1] == "relaxed"
						inequalityPath[i,j] = @addConstraint(m, sum{t[totalPaths[i][j+1][a],totalPaths[i][j+1][a+1]], a=1:(length(totalPaths[i][j+1])-1)} - sum{t[totalPaths[i][1][a],totalPaths[i][1][a+1]], a=1:(length(totalPaths[i][1])-1)} >= - tc * totalNumExpensiveTurns[i][j+1] + tc * totalNumExpensiveTurns[i][1] - delta * travelTimes[srcs[i],dsts[i]])
					end
				end
			end
		end
		# Equality constraints (shortest path close to travel time data)
		@addConstraint(m, TLowerBound[i=1:numDataPoints], sum{t[totalPaths[i][1][a],totalPaths[i][1][a+1]], a=1:(length(totalPaths[i][1])-1)} + tc * totalNumExpensiveTurns[i][1] - travelTimes[srcs[i],dsts[i]] >= - epsilon[srcs[i],dsts[i]])
		@addConstraint(m, TUpperBound[i=1:numDataPoints], sum{t[totalPaths[i][1][a],totalPaths[i][1][a+1]], a=1:(length(totalPaths[i][1])-1)} + tc * totalNumExpensiveTurns[i][1] - travelTimes[srcs[i],dsts[i]] <= epsilon[srcs[i],dsts[i]])

		# Define objective variables and constraints for second part of m
		if split(model_type, "_")[2] == "smooth" || (last_smooth && l == max_rounds)
			@defVar(m, delta2[i=nodes,j=out[i]] >= 0)
			@addConstraint(m, objConstrLower[i=nodes,j=out[i]], -1 * t[i,j]/distances[i,j] + 1/(length(inn[i]) + length(out[j])) * (sum{1/distances[j,k] * t[j,k], k = out[j]} + sum{1/distances[h,i] * t[h,i], h=inn[i]}) <= delta2[i,j])
			@addConstraint(m, objConstrUpper[i=nodes,j=out[i]], t[i,j]/distances[i,j] - 1/(length(inn[i]) + length(out[j])) * (sum{1/distances[j,k] * t[j,k], k = out[j]} + sum{1/distances[h,i] * t[h,i], h=inn[i]}) <= delta2[i,j])
		elseif split(model_type, "_")[2] == "random"
			coeffVec = 2 * rand(length(roads)) - 1
			coeffs = spzeros(length(nodes), length(nodes))
			k = 1
			for i = nodes, j = out[i]
				coeffs[i,j] = coeffVec[k]
				k += 1
			end
			if turnCostAsVariable
				coeff_tc = 2 * rand() - 1
			end
		end
		# Solve LP
		println("**** Solving LP ****")
		status = solve(m)
		if status == :Infeasible
			buildInternalModel(m)
			print_iis_gurobi(m)
			break
		end
		result = getValue(t)
		objective = getObjectiveValue(m)
		# Update cost function log
		write(costFunctionFile, string(l, ",", objective, "\n"))
		flush(costFunctionFile)
		if turnCostAsVariable
			println("Left turn cost: ", getValue(tc))
		end

		println("**** Setting up second LP ****")
		if split(model_type, "_")[2] == "smooth" || (last_smooth && l == max_rounds)
			if turnCostAsVariable
				@setObjective(m, Min, sum{delta2[i,j], i=nodes, j=out[i]} + tc)
			else
				@setObjective(m, Min, sum{delta2[i,j], i=nodes, j=out[i]})
			end
		elseif split(model_type, "_")[2] == "random"
			# Need to minimize absolute value (otherwise unbounded) so define new variable
			@defVar(m, zAbsVal >= 0)
			# Depending on whether turn cost is a variable, add it (or not) to the objective
			if turnCostAsVariable
				@addConstraint(m, randomObjectiveConstraint1, sum{coeffs[i,j] * t[i,j]/distances[i,j], i=nodes, j=out[i]} + coeff_tc * tc <= zAbsVal)
				@addConstraint(m, randomObjectiveConstraint2, sum{coeffs[i,j] * t[i,j]/distances[i,j], i=nodes, j=out[i]} + coeff_tc * tc >= - zAbsVal)
			else
				@addConstraint(m, randomObjectiveConstraint1, sum{coeffs[i,j] * t[i,j]/distances[i,j], i=nodes, j=out[i]} <= zAbsVal)
				@addConstraint(m, randomObjectiveConstraint2, sum{coeffs[i,j] * t[i,j]/distances[i,j], i=nodes, j=out[i]} >= - zAbsVal)
			end
			@setObjective(m, Min, zAbsVal)
		end

		if split(model_type, "_")[2] == "nothing" && !(last_smooth && l == max_rounds)
			# do nothing
			if turnCostAsVariable
				turnCost = getValue(tc)
				write(turnCostFile, string(l,",", turnCost, "\n"))
			end
		else
			# Solve second LP
			println("**** Solving second LP ****")
			@addConstraint(m, fixObjective, sum{sqrt(numRides[i,j]/travelTimes[i,j]) * epsilon[i,j], i=nodes, j=pairs[i]} <=objective + 1e-1)
			status = solve(m)
			# Get new turning cost
			if turnCostAsVariable
				turnCost = getValue(tc)
				println(turnCost)
				write(turnCostFile, string(l,",", turnCost, "\n"))
			end
		end

		# Debug if infeasible
		if status == :Infeasible
			println("!!!! Computing IIS !!!!")
			buildInternalModel(m)
			print_iis_gurobi(m)
			break
		# Prepare output
		elseif status == :Optimal || status == :Suboptimal || status == :UserLimit
			if status != :UserLimit
				result = getValue(t)
			end
			newTimes = spzeros(length(nodes), length(nodes))
			for key in keys(result)
				newTimes[key[1], key[2]] = result[key[1], key[2]]
			end
			# Save updated Manhattan road times to file
			if metropolis
				saveRoadTimes(newTimes, "$TESTDIR/metropolis-times-$l")
			else
				saveRoadTimes(newTimes, "$TESTDIR/manhattan-times-$l")
			end
			if abs(objective - old_objective)/old_objective < 1e-6
				save("Outputs/$TESTDIR/end.jld", "num_iter", l)
				l = max_rounds
			else
				old_objective = objective
			end
		end
		if l < max_rounds || computeFinalSP
			println("**** Computing shortest paths ****")
			new_graph, new_edge_dists, new_nodes, new_edge_isExpensive = modifyGraphForDijkstra(graph, newTimes, manhattan.positions, turn_cost=turnCost)
			old_nodes = getInverseMapping(new_nodes, nv(new_graph))
			@time new_sp = parallelShortestPathsWithTurnsAuto(graph, new_graph, new_edge_dists, new_nodes)
			if metropolis
				printErrorStatsToFile("Outputs/$TESTDIR/errorstats-$(actual_l).txt", real_TOD_metropolis, travelTimes, new_sp.traveltime, numRides, real_tij_metropolis, newTimes, num_nodes = nv(graph))
			else
				if errorComputation == "single-ride"
					@time avg_sq_err, avg_rel_err, avg_bias = computeTestingError(testingData, nodeTree, nodePairs, new_sp.traveltime, method)
					write(errorFile, string(l, ",", avg_sq_err, ",", avg_rel_err, ",", avg_bias,"\n"))
					flush(errorFile)
				elseif errorComputation == "average"
					@time avg_sq_err, avg_rel_err, avg_bias = computeAverageTestingError(testingData, new_sp.traveltime, num_nodes=nv(graph))
					write(errorFile, string(l, ",", avg_sq_err, ",", avg_rel_err, ",", avg_bias,"\n"))
					flush(errorFile)
				elseif errorComputation == "both"
					@time avg_sq_err, avg_rel_err, avg_bias = computeTestingError(testingData, nodeTree, nodePairs, new_sp.traveltime, method)
					@time avg_sq_err_2, avg_rel_err_2, avg_bias_2 = computeAverageTestingError(testingData2, new_sp.traveltime, num_nodes=nv(graph))
					write(errorFile, string(l, ",", avg_sq_err, ",", avg_rel_err, ",", avg_bias, ",", avg_sq_err_2, ",", avg_rel_err_2, ",", avg_bias_2,"\n"))
					flush(errorFile)
				end
			end
			if dynamicConstraints
				write(numConstraintsFile, string(l,",",length(srcs),",", count_number_edges_used(totalPaths, manhattan),"\n"))
				flush(numConstraintsFile)
				if l < max_rounds && l % iterationMultiple == 0
					println("**** Updating constraint set ****")
					srcs, dsts, totalPaths, totalNumExpensiveTurns, numPaths, pairs = updateConstraints(travelTimes, numRides, new_sp.traveltime, totalPaths, totalNumExpensiveTurns, numPaths, srcs, dsts, pairs, numNodePairsToAdd = numPairsToAdd, numNodePairsToRemove = numPairsToRemove, addOnlyIfAbovePercentile = addOnlyIfAbovePercentile, removeOnlyIfBelowPercentile = removeOnlyIfBelowPercentile)
					numDataPoints = length(srcs)
				end
			end
		end
		write(timeFile, string(l, ",", toc(), "\n"))
		flush(timeFile)
		l += 1
	end
	println("-- Saved outputs to directory Outputs/$(TESTDIR)/")
	# Close log files
	if turnCostAsVariable
		close(turnCostFile)
	end
	if !metropolis
		close(errorFile)
	end
	close(costFunctionFile)
	close(timeFile)
	if dynamicConstraints
		close(numConstraintsFile)
	end
	return status, newTimes, "Outputs/$TESTDIR/"
end

# manhattan = loadCityGraph()
# travel_times, num_rides, trainTestDf, testing_travel_times, testing_num_rides, testDf = loadInputTravelTimes(manhattan.positions, METHOD, year=YEAR, startTime=START_TIME, endTime=END_TIME, startMonth=START_MONTH, endMonth=END_MONTH, radius = RADIUS)
# if RANDOM_CONSTRAINTS
# 	travel_times, num_rides = chooseConstraints(travel_times, num_rides, sample_size=SAMPLE_SIZE);
# end
# if ERROR_COMPUTATION == "single-ride"
# 	@time status, new_times = fast_LP(manhattan, travel_times, num_rides, trainTestDf, manhattan.roadTime)
# elseif ERROR_COMPUTATION == "average"
# 	@time status, new_times = fast_LP(manhattan, travel_times, num_rides, testing_travel_times, manhattan.roadTime)
# elseif ERROR_COMPUTATION == "both"
# 	@time status, new_times = fast_LP(manhattan, travel_times, num_rides, trainTestDf, manhattan.roadTime, testingData2 = testing_travel_times)
# end


"""
EcoTrialStructure is a package for representing behavioral/physiological data from economic choice experiments.

You can learn more about each of the following from its own docstring, e.g., `?CellsTrial`:

- `CellsTrial`: Store cellular responses for a single trial.
- `EventTiming`: Specify the timing of events during a trial.
- `FrameSeq`: A sequence of `nframes` frames starting nearest to a specified time.
- `OfferType`: Encode the offer quantities and side (left/right).
- `TrialResult`: Encode the `OfferType` and the animal's choice.
- `isforced`: Return `true` for a forced-choice trial.
- `iswrong`: Return `true` if the animal chose incorrectly on a forced-choice trial.
- `madechoice`: Return `true` if the animal made a choice in the trial.
- `ncells`: Return the number of cells in a `CellsTrial`
- `parsemat`: Parse the trial data in `filename`.
- `positive_cells`: Extract the vector of `idx_positive_cells` from the .mat file.
"""
module EcoTrialStructure

using Unitful
using Unitful: ms, s
using IntervalSets
using OffsetArrays
using OrderedCollections
using MAT

export CellsTrial, FrameSeq, OfferType, TrialResult, EventTiming
export isdeferred, isforced, iswrong, madechoice, ncells, .., ms, s
export parsemat, positive_cells

include("types.jl")
include("utils.jl")
include("matlab.jl")

Base.@deprecate_binding TrialType OfferType

end


# Use
#    @warnpcfail precompile(args...)
# if you want to be warned when a precompile directive fails
macro warnpcfail(ex::Expr)
    modl = __module__
    file = __source__.file === nothing ? "?" : String(__source__.file)
    line = __source__.line
    quote
        $(esc(ex)) || @warn """precompile directive
     $($(Expr(:quote, ex)))
 failed. Please report an issue in $($modl) (after checking for duplicates) or remove this directive.""" _file=$file _line=$line
    end
end

function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    @warnpcfail Base.precompile(Tuple{Type{replace_inds},Pair{QuIndex, QuIndex},Vararg{Pair{QuIndex, QuIndex}, N} where N})
    @warnpcfail Base.precompile(Tuple{typeof(*),Complex{Int},QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(*),Complex{Rational{Int}},QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(*),QuExpr,Int})
    @warnpcfail Base.precompile(Tuple{typeof(*),QuExpr,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(+),Int,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(+),QuExpr,Complex{Rational{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(+),QuExpr,Float64})
    @warnpcfail Base.precompile(Tuple{typeof(+),QuExpr,Int})
    @warnpcfail Base.precompile(Tuple{typeof(+),QuExpr,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(+),Rational{Int},QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(-),Int,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(-),QuExpr,Int})
    @warnpcfail Base.precompile(Tuple{typeof(==),QuExpr,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(Avac),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(CorrTup_isless),NTuple{4, Int},Tuple{Int, Int}})
    @warnpcfail Base.precompile(Tuple{typeof(CorrTup_isless),NTuple{6, Int},Tuple{Int, Int, Int}})
    @warnpcfail Base.precompile(Tuple{typeof(CorrTup_isless),NTuple{8, Int},NTuple{4, Int}})
    @warnpcfail Base.precompile(Tuple{typeof(_add_corrs!),QuExpr,QuTerm,Int,typeof(_add_with_normal_order!)})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,ComplexF64,ComplexF64})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,ComplexF64,Float64})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Complex{Int},Complex{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Complex{Int},Int})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Complex{Int},Rational{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Complex{Rational{Int}},Complex{Rational{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Complex{Rational{Int}},Int})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Complex{Rational{Int}},Rational{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Float64,Float64})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Int,Int})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Int,Rational{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Rational{Int},Int})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Rational{Int},Rational{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(_add_sum_term!),QuExpr,QuTerm,Rational{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(adjoint),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(boson_ops),Symbol})
    @warnpcfail Base.precompile(Tuple{typeof(comm),QuExpr,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(corr),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(expval_as_corrs),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(fermion_ops),Symbol})
    @warnpcfail Base.precompile(Tuple{typeof(isapprox),QuExpr,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(isempty),QuTerm})
    @warnpcfail Base.precompile(Tuple{typeof(julia_expression),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(julia_expression),Param,Nothing})
    @warnpcfail Base.precompile(Tuple{typeof(latex),Complex{Rational{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(latex),Float64})
    @warnpcfail Base.precompile(Tuple{typeof(latex),Int})
    @warnpcfail Base.precompile(Tuple{typeof(latex),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(latex),QuTerm})
    @warnpcfail Base.precompile(Tuple{typeof(latex),Rational{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(make_indices),Int,Int,Vararg{Any, N} where N})
    @warnpcfail Base.precompile(Tuple{typeof(make_indices),Int,Symbol,Vararg{Any, N} where N})
    @warnpcfail Base.precompile(Tuple{typeof(make_indices),Int,Symbol})
    @warnpcfail Base.precompile(Tuple{typeof(make_indices),Symbol,Int,Vararg{Any, N} where N})
    @warnpcfail Base.precompile(Tuple{typeof(make_indices),Symbol,Int})
    @warnpcfail Base.precompile(Tuple{typeof(make_indices),Symbol,String})
    @warnpcfail Base.precompile(Tuple{typeof(make_indices),Symbol,Symbol,Vararg{Any, N} where N})
    @warnpcfail Base.precompile(Tuple{typeof(normal_form),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char,Int,Vararg{Any, N} where N})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char,Int})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char,Symbol,Vararg{Any, N} where N})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char,Tuple{}})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char,Vector{Any}})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char,Vector{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char,Vector{Symbol}})
    @warnpcfail Base.precompile(Tuple{typeof(param),Symbol,Char})
    @warnpcfail Base.precompile(Tuple{typeof(parse_paramstr),String})
    @warnpcfail Base.precompile(Tuple{typeof(print),IOBuffer,BaseOperator})
    @warnpcfail Base.precompile(Tuple{typeof(print),IOBuffer,Corr})
    @warnpcfail Base.precompile(Tuple{typeof(print_term_scalar),IOBuffer,QuTerm,Complex{Int},Bool})
    @warnpcfail Base.precompile(Tuple{typeof(print_term_scalar),IOBuffer,QuTerm,Int,Bool})
    @warnpcfail Base.precompile(Tuple{typeof(print_term_scalar),IOBuffer,QuTerm,Rational{Int},Bool})
    @warnpcfail Base.precompile(Tuple{typeof(show),IOBuffer,MIME{Symbol("text/latex")},QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(simplify_number),Rational{Int}})
    @warnpcfail Base.precompile(Tuple{typeof(symmetric_index_nums),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,NTuple{4, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,NTuple{4, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,NTuple{5, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,NTuple{6, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,NTuple{7, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,NTuple{8, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,NTuple{9, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{NTuple{4, Int}, NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{NTuple{5, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{NTuple{6, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{NTuple{7, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{NTuple{8, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{NTuple{9, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int, Int, Int}, Tuple{Int, Int, Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int, Int, Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int, Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, NTuple{4, Int}, NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, NTuple{5, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, NTuple{6, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, NTuple{7, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, NTuple{8, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int, Int, Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, NTuple{5, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, NTuple{6, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, NTuple{7, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int, Int, Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, NTuple{5, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, NTuple{6, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int, Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, NTuple{5, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, NTuple{4, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int}, Tuple{Int, Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}, Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(term2corr),QuTerm,Tuple{Tuple{Int}}})
    @warnpcfail Base.precompile(Tuple{typeof(use_σpm),Bool})
    @warnpcfail Base.precompile(Tuple{typeof(vacA),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(vacExpVal),QuExpr,QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(vacExpVal),QuExpr})
    @warnpcfail Base.precompile(Tuple{typeof(∑),Symbol,QuExpr})
end


# This file holds the definition of the Recruitment type. This type is used to
#   describe a recruitment scheme for a specific part of the organisation and
#   defines which attributes need to be initialised and what the distributions
#   of those attributes are.

# The Recruitment type requires no additional types.
requiredTypes = [ ]

for reqType in requiredTypes
    if !isdefined( Symbol( uppercase( string( reqType[ 1 ] ) ) * reqType[ 2:end ] ) )
        include( joinpath( typePath, reqType * ".jl" ) )
    end  # if !isdefined( Symbol( ...
end  # for reqType in requiredTypes

export Recruitment
type Recruitment

    # The name of this recruitment type.
    name::String

    # The time between two recruitment drives.
    recruitFreq::Float64

    # The offset of the recruitment cycle.
    recruitOffset::Float64

    # Vary recruitment according to needs.
    isAdaptive::Bool

    # The min/max number of personnel members to recruit.
    minRecruit::Int
    maxRecruit::Int

    # The state to recruit into.
    recState::String

    # The type of the recruitment distribution.
    recDistType::Symbol

    # The nodes of the recruitment distribution, if applicable.
    recDistNodes::Dict{Float64, Float64}

    # This is the type of the age distribution.
    ageDistType::Symbol

    # The distribution function of the number of recruits in a single cycle.
    recDist::Function

    # These are the nodes (with probability weights) of the age distribution.
    ageDistNodes::Dict{Float64, Float64}

    # The distribution function of the age of a single recruit.
    ageDist::Function

    function Recruitment( name::String, freq::T1, offset::T2 = 0.0 ) where T1 <: Real where T2 <: Real

        if freq <= 0.0
            error( "Recruitment frequency must be > 0.0." )
        end  # if freq <= 0.0

        newRec = new()
        newRec.name = name
        newRec.recruitFreq = freq
        newRec.recruitOffset = offset % freq + ( offset < 0.0 ? freq : 0.0 )
        newRec.isAdaptive = true
        newRec.minRecruit = 0
        newRec.maxRecruit = typemax( Int )
        newRec.recState = ""
        newRec.recDistType = :disc
        newRec.recDistNodes = Dict{Float64, Float64}()
        newRec.recDist = function() return 0 end
        newRec.ageDistType = :disc
        newRec.ageDistNodes = Dict( 0.0 => 1.0 )
        newRec.ageDist = function() return 0.0 end
        return newRec

    end  # Recruitment( freq, offset )

end  # type Recruitment


using Revise
using lipm

init_position = [0.0, 0.0, 0.0]
goal_position = [5.0, 0.0, 0.0]
X = move_to_position(init_position; goal_position=goal_position)
animate_walking_trajectory(X)


############################################################
## types supporting conversion from local to DA arrays #####
############################################################

############################################################
# joDA{distribute,gather} ##################################

export joDAdistribute, joDAgather

# type definition
"""
    joDAdistribute is DAarray toggle type & constructor

    !!! Do not use it to create the operators

"""
struct joDAdistribute{DDT<:Number,RDT<:Number,N} <: joAbstractDAparallelToggleOperator{DDT,RDT,N}
    name::String
    m::Integer
    n::Integer
    nvc::Integer
    fop::Function    # forward
    fop_T::Function  # transpose
    fop_A::Function  # adjoint
    fop_C::Function  # conj
    iop::Nullable{Function}
    iop_T::Nullable{Function}
    iop_A::Nullable{Function}
    iop_C::Nullable{Function}
    PAs_out::joPAsetup  # output distributor
    gclean::Bool        # clean input vector post gathering
end
"""
    joDAgather is DAarray toggle type & constructor

    !!! Do not use it to create the operators

"""
struct joDAgather{DDT<:Number,RDT<:Number,N} <: joAbstractDAparallelToggleOperator{DDT,RDT,N}
    name::String
    m::Integer
    n::Integer
    nvc::Integer
    fop::Function    # forward
    fop_T::Function  # transpose
    fop_A::Function  # adjoint
    fop_C::Function  # conj
    iop::Nullable{Function}
    iop_T::Nullable{Function}
    iop_A::Nullable{Function}
    iop_C::Nullable{Function}
    PAs_in::joPAsetup  # input distributor
    gclean::Bool       # clean input vector post gathering
end


using LaserTag
using POMDPToolbox
using ParticleFilters
using ContinuousPOMDPTreeSearchExperiments
using QMDP
using JLD
using BasicPOMCP
using StaticArrays
using POMDPs
using Reel
using ARDESPOT

f = ARGS[1]
k = ARGS[2]
i = parse(Int, ARGS[3])

rdict = load(f, "rdict")

n = 100_000

pomdp = gen_lasertag(rng=MersenneTwister(i+600_000))

sol = DESPOTSolver(lambda=0.1,
                   T_max=5.0,
                   max_trials=1_000_000,
                   bounds=LaserBounds{typeof(pomdp)}(),
                   rng=MersenneTwister(4))

if isa(sol,Solver)
    p = solve(deepcopy(sol), pomdp)
else
    p = sol
end
hr = HistoryRecorder(max_steps=100, rng=MersenneTwister(i))
up_rng = MersenneTwister(i+100_000)
# up = ObsAdaptiveParticleFilter(deepcopy(pomdp), LowVarianceResampler(10_000), 0.05, up_rng)
up = ObsAdaptiveParticleFilter(deepcopy(pomdp), LowVarianceResampler(100_000), 0.05, up_rng)

frames = Frames(MIME("image/png"), fps=2)
disc = 1.0
rew = 0.0
states = []
beliefs = []
for (s, b, a, r, sp, o, bp) in stepthrough(pomdp, p, up, "sbarspobp", max_steps=100, rng=MersenneTwister(i))
    rew += disc*r
    disc*=discount(pomdp)
    show(STDOUT, MIME("text/plain"), LaserTagVis(pomdp, s=s))
    println()
    push!(frames, LaserTagVis(pomdp, s=sp, b=bp, o=o, a=a))
    push!(states, s)
    push!(beliefs, b)
end

println("Total discounted reward for this simulation: $rew")
println("Total discounted reward from original: $(rdict[k][i])")

gifname = "out"*randstring()*".gif"
println("Writing $gifname...")
write(gifname, frames)
println("done.")


using CartesianGeneticProgramming
using PyCall
using Cambrian
using ArgParse
import Distances
import Random
import Formatting
import Base.GC

```
Continuous gym problems using pybullet
```

"Gym BERL environment"
mutable struct BulletEnv <: BERLenv
    name::String
    memory::Array # Array of storable data
    gym
    pybullet
    env_name
    gen
    n_steps::Int64
end

environments["pybullet"]=BulletEnv

function BulletEnv(cfg::Dict)
    pybullet_envs = pyimport("pybullet_envs")
    gym = pyimport("gym")
    cfg["env"] = get!(cfg, "gym_env", "AntBulletEnv-v0") #'HopperBulletEnv-v0'
    env = gym.make(cfg["env"])
    cfg["n_out"] = length(env.action_space.sample())
    cfg["n_in"] = length(env.observation_space.sample())
    gen = get!(cfg, "seed", 0)
    Random.seed!(gen)
    cfg["nsteps"] = 0

    cfg["env"]=

    BulletEnv(cfg["env"], [], gym, pybullet_envs, cfg["env"], gen, 0)
end

"Returns the fitness of one Cambrian Individual in this environment"
function fitness(indiv::Cambrian.Individual, env::BulletEnv)
    game = env.gym.make(env.env_name)
    game.seed(env.gen)
    obs = game.reset()
    total_reward = 0.0
    done = false

    max_obs = 2*pi

    while ~done
        action = process(indiv, obs ./ max_obs)
        obs, reward, done, _ = game.step(action)
        newmax = maximum(abs.(obs))
        if newmax > max_obs
            println("Increased max_obs from ", max_obs, " to ", newmax)
            max_obs = newmax
        end
        total_reward += reward
        env.n_steps += 1
    end

    game.close()
    game = nothing
    Base.GC.gc()
    [total_reward]
end

"Sets up the env for the next generation"
function new_gen!(env::BulletEnv, cfg::Dict=nothing)
    #env.gen += 1
end