function generate_model_parameters_dictionary()
	# load stoichiometry
	stoichiometric_matrix = readdlm("./stoichiometry.dat")

	# initial species concentration
	initial_condition = [
		0.0,  # 1  BIOMASS
		0.0,  # 2  m_A_e
		0.0,  # 3  m_B_e
		0.0,  # 4  m_C_e
		0.0,  # 5  m_A_c
		0.0,  # 6  m_B_c
		0.0,  # 7  m_C_c
		0.0,  # 8  p_E1_c
		0.0,  # 9  p_E2_c
		0.0,  # 10  p_E3_c
		0.0,  # 11  p_E4_c
		0.0,  # 12  p_TA_c
		0.0,  # 13  p_TB_c
		0.0,  # 14  p_TC_c
		0.0,  # 15  mRNA_E1_c
		0.0,  # 16  mRNA_E2_c
		0.0,  # 17  mRNA_E3_c
		0.0,  # 18  mRNA_E4_c
		0.0,  # 19  mRNA_TA_c
		0.0,  # 20  mRNA_TB_c
		0.0   # 21 mRNA_TC_c
	]

	# all species dictionary
	all_species_dict = Dict{String, Int64}()  # String --> Int
	all_species_dict["m_A_e"] = 2
	all_species_dict["m_C_c"] = 7
	all_species_dict["mRNA_E2_c"] = 16
	all_species_dict["mRNA_E4_c"] = 18
	all_species_dict["BIOMASS"] = 1
	all_species_dict["p_TC_c"] = 14
	all_species_dict["p_E2_c"] = 9
	all_species_dict["mRNA_TB_c"] = 20
	all_species_dict["p_E3_c"] = 10
	all_species_dict["mRNA_TA_c"] = 19
	all_species_dict["p_E1_c"] = 8
	all_species_dict["m_C_e"] = 4
	all_species_dict["m_B_c"] = 6
	all_species_dict["p_TA_c"] = 12
	all_species_dict["p_TB_c"] = 13
	all_species_dict["mRNA_E1_c"] = 15
	all_species_dict["m_A_c"] = 5
	all_species_dict["mRNA_E3_c"] = 17
	all_species_dict["m_B_e"] = 3
	all_species_dict["p_E4_c"] = 11
	all_species_dict["mRNA_TC_c"] = 21

	# all species dictionary_reversed
	all_species_reversed_dict = Dict{Int64, String}()  # Int --> String
	for (key,val) in all_species_dict
		all_species_reversed_dict[val] = key
	end

	# all rnx species array
	rnx_species_array::Array{String,1} = [
		"m_A_e",
		"m_B_e",
		"m_C_e",
		"m_A_c",
		"m_B_c",
		"m_C_c",
		"p_E1_c",
		"p_E2_c",
		"p_E3_c",
		"p_E4_c",
		"p_TA_c",
		"p_TB_c",
		"p_TC_c"
	]

	# all mRNA species array
	mRNA_species_array::Array{String,1} = [
		"mRNA_E4_c",
		"mRNA_E2_c",
		"mRNA_E3_c",
		"mRNA_TA_c",
		"mRNA_TB_c",
		"mRNA_E1_c",
		"mRNA_TC_c"
	]

	# all protein species array
	protein_species_array::Array{String,1} = [
		"p_TA_c",
		"p_TC_c",
		"p_E2_c",
		"p_TB_c",
		"p_E1_c",
		"p_E4_c",
		"p_E3_c"
	]

	# signaling reaction kinetic constants, reaction name: reactant(s)<rnx type:catalyst(s)>product(s)
	kcat_signaling = ones(7)  # kcat[#reaction]: reaction name
	kcat_signaling[1] = 1.1e-3  # kcat: 1.0*m_A_e<uptake:1.0*p_TA_c>1.0*m_A_c
	kcat_signaling[2] = 8e-4  # kcat: 1.0*m_B_c<secrete:1.0*p_TB_c>1.0*m_B_e
	kcat_signaling[3] = 9e-4  # kcat: 1.0*m_C_c<secrete:1.0*p_TC_c>1.0*m_C_e
	kcat_signaling[4] = 1.8e-4  # kcat: 1.0*m_A_c<catalyze:1.0*p_E1_c>1.0*m_B_c
	kcat_signaling[5] = 1e-4  # kcat: 1.0*m_B_c<catalyze:1.0*p_E4_c>1.0*m_A_c
	kcat_signaling[6] = 1e-4  # kcat: 1.0*m_A_c<catalyze:1.0*p_E2_c>1.0*m_C_c
	kcat_signaling[7] = 1e-4  # kcat: 1.0*m_C_c<catalyze:1.0*p_E3_c>1.0*m_B_c

	# Monod affinity constant in signaling reaction, nomenclature: MonodK_#reaction_Substrate
	MonodAffinityConstantDict = Dict{String, Float64}()
	MonodAffinityConstantDict["MonodK~rnx1~m_A_e"] = 0.1
	MonodAffinityConstantDict["MonodK~rnx2~m_B_c"] = 0.1
	MonodAffinityConstantDict["MonodK~rnx3~m_C_c"] = 0.1
	MonodAffinityConstantDict["MonodK~rnx4~m_A_c"] = 0.1
	MonodAffinityConstantDict["MonodK~rnx5~m_B_c"] = 0.1
	MonodAffinityConstantDict["MonodK~rnx6~m_A_c"] = 0.1
	MonodAffinityConstantDict["MonodK~rnx7~m_C_c"] = 0.1

	# W_value in transcription control term, nomenclature: W_targetmRNA_actor
	W_value_dict = Dict{String, Float64}()
	W_value_dict["W~m_B_c~mRNA_E4_c"] = 0.1
	W_value_dict["W~m_A_c~mRNA_E2_c"] = 0.7
	W_value_dict["W~m_C_c~mRNA_E3_c"] = 0.2
	W_value_dict["W~m_A_e~mRNA_TA_c"] = 0.1
	W_value_dict["W~m_B_c~mRNA_TB_c"] = 1.0
	W_value_dict["W~m_A_c~mRNA_E1_c"] = 0.6
	W_value_dict["W~m_C_c~mRNA_TC_c"] = 1.1

	# disassociation constants in transfer function, nomenclature: KD_speciesName
	transferFunctionDisassociationConstantDict = Dict{String, Float64}()
	transferFunctionDisassociationConstantDict["KD~m_B_c"] = 1.0
	transferFunctionDisassociationConstantDict["KD~m_A_c"] = 1.0
	transferFunctionDisassociationConstantDict["KD~m_C_c"] = 1.0
	transferFunctionDisassociationConstantDict["KD~m_A_e"] = 1.0
	transferFunctionDisassociationConstantDict["KD~m_B_c"] = 1.0
	transferFunctionDisassociationConstantDict["KD~m_A_c"] = 1.0
	transferFunctionDisassociationConstantDict["KD~m_C_c"] = 1.0

	# transcription specific correction factor
	transcriptionSpecificCorrectionFactorDict = Dict{String, Float64}()
	transcriptionSpecificCorrectionFactorDict["mRNA_E4_c"] = 1.0  # mRNA_E4_c
	transcriptionSpecificCorrectionFactorDict["mRNA_E2_c"] = 1.0  # mRNA_E2_c
	transcriptionSpecificCorrectionFactorDict["mRNA_E3_c"] = 1.0  # mRNA_E3_c
	transcriptionSpecificCorrectionFactorDict["mRNA_TA_c"] = 1.0  # mRNA_TA_c
	transcriptionSpecificCorrectionFactorDict["mRNA_TB_c"] = 1.0  # mRNA_TB_c
	transcriptionSpecificCorrectionFactorDict["mRNA_E1_c"] = 1.0  # mRNA_E1_c
	transcriptionSpecificCorrectionFactorDict["mRNA_TC_c"] = 1.0  # mRNA_TC_c

	# background gene expression control term
	backgroundGeneExpressionControlTermDict = Dict{String, Float64}()
	backgroundGeneExpressionControlTermDict["mRNA_E4_c"] = 0.001  # mRNA_E4_c
	backgroundGeneExpressionControlTermDict["mRNA_E2_c"] = 0.001  # mRNA_E2_c
	backgroundGeneExpressionControlTermDict["mRNA_E3_c"] = 0.001  # mRNA_E3_c
	backgroundGeneExpressionControlTermDict["mRNA_TA_c"] = 0.001  # mRNA_TA_c
	backgroundGeneExpressionControlTermDict["mRNA_TB_c"] = 0.001  # mRNA_TB_c
	backgroundGeneExpressionControlTermDict["mRNA_E1_c"] = 0.001  # mRNA_E1_c
	backgroundGeneExpressionControlTermDict["mRNA_TC_c"] = 0.001  # mRNA_TC_c

	# translation specific correction factor
	translationSpecificCorrectionFactor = Dict{String, Float64}()
	translationSpecificCorrectionFactor["p_TA_c"] = 1.0  # p_TA_c
	translationSpecificCorrectionFactor["p_TC_c"] = 1.0  # p_TC_c
	translationSpecificCorrectionFactor["p_E2_c"] = 1.0  # p_E2_c
	translationSpecificCorrectionFactor["p_TB_c"] = 1.0  # p_TB_c
	translationSpecificCorrectionFactor["p_E1_c"] = 1.0  # p_E1_c
	translationSpecificCorrectionFactor["p_E4_c"] = 1.0  # p_E4_c
	translationSpecificCorrectionFactor["p_E3_c"] = 1.0  # p_E3_c

	# cooperativity number in transfer function
	cooperativity = 1

	# ------------------------------------------------------------------------------------------#
	# constants (from bionumbers)       units
	# ------------------------------------------------------------------------------------------#
	cell_diameter = 1.1                 # mum
	number_of_rnapII = 4600            	# copies/cells
	number_of_ribosome = 50000         	# copies/cells
	mRNA_half_life_TF = 0.083           # hrs
	protein_half_life = 70              # hrs
	doubling_time_cell = 0.33           # hrs
	max_translation_rate = 16.5         # aa/sec
	max_transcription_rate = 60.0       # nt/sec
	average_transcript_length = 1200   	# nt
	average_protein_length = 400       	# aa
	fraction_nucleus = 0.0             	# dimensionless
	av_number = 6.02e23                 # number/mol
	avg_gene_number = 2                 # number of copies of a gene
	polysome_number = 4									# number of ribsomoses per transcript
	# ------------------------------------------------------------------------------------------#
	#
	# ------------------------------------------------------------------------------------------#
	# Calculate constants using bionumber values
	# ------------------------------------------------------------------------------------------#
	# Calculate the volume (convert to L)
	V = ((1-fraction_nucleus)*(1/6)*(3.14159)*(cell_diameter)^3)*(1e-15)     # volume

	# Calculate the rnapII_concentration and ribosome_concentration
	rnapII_concentration = number_of_rnapII*(1/av_number)*(1/V)*1e9                   # nM
	ribosome_concentration = number_of_ribosome*(1/av_number)*(1/V)*1e9               # nM

	# degrdation rate constants -
	degradation_constant_mRNA = -(1/mRNA_half_life_TF)*log(0.5)                       # hr^-1
	degradation_constant_protein = -(1/protein_half_life)*log(0.5)                    # hr^-1

	# kcats for transcription and translation -
	kcat_transcription = max_transcription_rate*(3600/average_transcript_length)      # hr^-1
	kcat_translation = polysome_number*max_translation_rate*(3600/average_protein_length)             # hr^-1

	# Maximum specific growth rate -
	maximum_specific_growth_rate = (1/doubling_time_cell)*log(2)                      # hr^-1

	# What is the average gene concentration -
	avg_gene_concentration = avg_gene_number*(1/av_number)*(1/V)*1e9                  # nM

	# How fast do my cells die?
	death_rate_constant = 0.05*maximum_specific_growth_rate                            # hr^-1

	# Saturation constants for translation and trascription -
	saturation_transcription = 4600*(1/av_number)*(1/V)*1e9                           # nM
	saturation_translation = 150000*(1/av_number)*(1/V)*1e9                           # nM
	# -------------------------------------------------------------------------------------------#

	####################################
	# put all stuff in a Dictionary
	dataDictionary = Dict{String, Any}()
	dataDictionary["stoichiometric_matrix"] = stoichiometric_matrix
	dataDictionary["all_species_dict"] = all_species_dict
	dataDictionary["all_species_reversed_dict"] = all_species_reversed_dict
	dataDictionary["rnx_species_array"] = rnx_species_array
	dataDictionary["mRNA_species_array"] = mRNA_species_array
	dataDictionary["protein_species_array"] = protein_species_array
	dataDictionary["initial_condition"] = initial_condition
	dataDictionary["kcat_signaling"] = kcat_signaling
	dataDictionary["MonodAffinityConstantDict"] = MonodAffinityConstantDict
	dataDictionary["W_value_dict"] = W_value_dict
	dataDictionary["transferFunctionDisassociationConstantDict"] = transferFunctionDisassociationConstantDict
	dataDictionary["transcriptionSpecificCorrectionFactorDict"] = transcriptionSpecificCorrectionFactorDict
	dataDictionary["backgroundGeneExpressionControlTermDict"] = backgroundGeneExpressionControlTermDict
	dataDictionary["translationSpecificCorrectionFactor"] = translationSpecificCorrectionFactor
	dataDictionary["cooperativity"] = cooperativity
	dataDictionary["RNAPConcentration"] = rnapII_concentration
	dataDictionary["RIBOConcentration"] = ribosome_concentration
	dataDictionary["degradationConstantmRNA"] = degradation_constant_mRNA
	dataDictionary["degradationConstantProtein"] = degradation_constant_protein
	dataDictionary["kcatTranscription"] = kcat_transcription
	dataDictionary["kcatTranslation"] = kcat_translation
	dataDictionary["avgGeneConcentration"] = avg_gene_concentration
	dataDictionary["transcriptionSaturationConstant"] = saturation_transcription
	dataDictionary["translationSaturationConstant"] = saturation_translation
	dataDictionary["specificGrowthRate"] = maximum_specific_growth_rate - death_rate_constant

	return dataDictionary
end


# Copyright (c) 2018: Matthew Wilhelm & Matthew Stuber.
# This code is licensed under MIT license (see LICENSE.md for full details)
#############################################################################
# EAGO
# A development environment for robust and global optimization
# See https://github.com/PSORLab/EAGO.jl
#############################################################################
# src/eago_optimizer/optimize/optimize_nonconvex.jl
# Contains the optimize! routine and subroutines needed in the branch and
# bound routine called by EAGO.
#############################################################################

function set_evaluator_flags!(d, is_post, is_intersect, is_first_eval, interval_intersect)

    d.is_post = is_post
    d.is_intersect = is_intersect
    d.is_first_eval = is_first_eval
    d.interval_intersect = interval_intersect

    return nothing
end

function reset_relaxation!(m::Optimizer)

    m._working_problem._relaxed_evaluator.is_first_eval = true
    fill!(m._working_problem._relaxed_evaluator.subexpressions_eval, false)

    m._new_eval_objective = true
    m._new_eval_constraint = true

    delete_nl_constraints!(m)
    delete_objective_cuts!(m)

    return nothing
end

"""
$(TYPEDSIGNATURES)

Creates an initial node with initial box constraints and adds it to the stack.
"""
function create_initial_node!(m::Optimizer)

    branch_variable_count = m._branch_variable_count

    variable_info = m._working_problem._variable_info
    lower_bound = zeros(Float64, branch_variable_count)
    upper_bound = zeros(Float64, branch_variable_count)
    branch_count = 1

    for i = 1:m._working_problem._variable_count
        vi = variable_info[i]
        if vi.branch_on === BRANCH
            lower_bound[branch_count] = vi.lower_bound
            upper_bound[branch_count] = vi.upper_bound
            branch_count += 1
        end
    end

    n = NodeBB(lower_bound, upper_bound, -Inf, Inf, 1, 1)
    push!(m._stack, n)
    m._node_count = 1
    m._maximum_node_id += 1

    return nothing
end

"""
$(TYPEDSIGNATURES)

Loads variables, linear constraints, and empty storage for first nlp and
quadratic cut.
"""
function load_relaxed_problem!(m::Optimizer)

    relaxed_optimizer = m.relaxed_optimizer

    # add variables and indices and constraints
    wp = m._working_problem
    branch_variable_count = 0

    variable_count = wp._variable_count
    for i = 1:variable_count

        relaxed_variable_indx = MOI.add_variable(relaxed_optimizer)
        relaxed_variable = SV(relaxed_variable_indx)
        push!(m._relaxed_variable_index, relaxed_variable_indx)

        vinfo =  wp._variable_info[i]

        is_branch_variable =  m._branch_variables[i]
        vinfo.branch_on = is_branch_variable ? BRANCH : NO_BRANCH
        is_branch_variable && (branch_variable_count += 1)

        if vinfo.is_integer

        elseif vinfo.is_fixed
            ci_sv_et = MOI.add_constraint(relaxed_optimizer, relaxed_variable, ET(vinfo.lower_bound))
            if is_branch_variable
                push!(m._relaxed_variable_eq, (ci_sv_et, branch_variable_count))
                wp._var_eq_count += 1
            end

        else
            if vinfo.has_lower_bound
                ci_sv_gt = MOI.add_constraint(relaxed_optimizer, relaxed_variable, GT(vinfo.lower_bound))
                if is_branch_variable
                    push!(m._relaxed_variable_gt, (ci_sv_gt, branch_variable_count))
                    wp._var_geq_count += 1
                end
            end

            if vinfo.has_upper_bound
                ci_sv_lt = MOI.add_constraint(relaxed_optimizer, relaxed_variable, LT(vinfo.upper_bound))
                if is_branch_variable
                    push!(m._relaxed_variable_lt, (ci_sv_lt, branch_variable_count))
                    wp._var_leq_count += 1
                end
            end
        end
    end

    # set node index to single variable constraint index maps
    m._node_to_sv_leq_ci = fill(CI{SV,LT}(-1), branch_variable_count)
    m._node_to_sv_geq_ci = fill(CI{SV,GT}(-1), branch_variable_count)
    for i = 1:wp._var_leq_count
        ci_sv_lt, branch_index = m._relaxed_variable_lt[i]
        m._node_to_sv_leq_ci[branch_index] = ci_sv_lt
    end
    for i = 1:wp._var_geq_count
        ci_sv_gt, branch_index = m._relaxed_variable_gt[i]
        m._node_to_sv_geq_ci[branch_index] = ci_sv_gt
    end

    # set number of variables to branch on
    m._branch_variable_count = branch_variable_count

    # add linear constraints
    add_linear_constraints!(m, relaxed_optimizer)

    # sets relaxed problem objective sense to Min as all problems
    # are internally converted in Min problems in EAGO
    MOI.set(relaxed_optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)
    return nothing
end

function presolve_global!(t::ExtensionType, m::Optimizer)

    load_relaxed_problem!(m)
    create_initial_node!(m)

    branch_variable_count = m._branch_variable_count

    m._current_xref             = fill(0.0, branch_variable_count)
    m._candidate_xref           = fill(0.0, branch_variable_count)
    m._current_objective_xref   = fill(0.0, branch_variable_count)
    m._prior_objective_xref     = fill(0.0, branch_variable_count)
    m._lower_lvd                = fill(0.0, branch_variable_count)
    m._lower_uvd                = fill(0.0, branch_variable_count)

    # populate in full space until local MOI nlp solves support constraint deletion
    # uses input model for local nlp solves... may adjust this if a convincing reason
    # to use a reformulated upper problem presents itself
    m._lower_solution      = zeros(Float64, m._working_problem._variable_count)
    m._cut_solution        = zeros(Float64, m._working_problem._variable_count)
    m._continuous_solution = zeros(Float64, m._working_problem._variable_count)
    m._upper_solution      = zeros(Float64, m._working_problem._variable_count)
    m._upper_variables     = fill(VI(-1), m._working_problem._variable_count)

    # add storage for fbbt
    m._lower_fbbt_buffer   = zeros(Float64, m._working_problem._variable_count)
    m._upper_fbbt_buffer   = zeros(Float64, m._working_problem._variable_count)

    # add storage for obbt ( perform obbt on all relaxed variables, potentially)
    m._obbt_working_lower_index = fill(false, branch_variable_count)
    m._obbt_working_upper_index = fill(false, branch_variable_count)
    m._old_low_index            = fill(false, branch_variable_count)
    m._old_upp_index            = fill(false, branch_variable_count)
    m._new_low_index            = fill(false, branch_variable_count)
    m._new_upp_index            = fill(false, branch_variable_count)
    m._lower_indx_diff          = fill(false, branch_variable_count)
    m._upper_indx_diff          = fill(false, branch_variable_count)
    m._obbt_variable_count      = branch_variable_count

    # add storage for objective cut if quadratic or nonlinear
    wp = m._working_problem
    obj_type = wp._objective_type
    if obj_type === SCALAR_QUADRATIC
        wp._objective_saf.terms = copy(wp._objective_sqf.saf.terms)
    elseif obj_type === NONLINEAR
        wp._objective_saf.terms = copy(wp._objective_nl.saf.terms)
    end

    # set subgradient refinement flag
    wp._relaxed_evaluator.is_post = m._parameters.subgrad_tighten
    wp._relaxed_evaluator.subgrad_tighten = m._parameters.subgrad_tighten
    wp._relaxed_evaluator.reverse_subgrad_tighten =  m._parameters.reverse_subgrad_tighten

    m._presolve_time = time() - m._parse_time

    return nothing
end

"""
$(SIGNATURES)

Selects node with the lowest lower bound in stack.
"""
function node_selection!(t::ExtensionType, m::Optimizer)

    m._node_count -= 1
    m._current_node = popmin!(m._stack)

    return nothing

end

"""
$(SIGNATURES)

Creates two nodes from `current_node` using information available the `x`
and stores them to the stack. By default, relative width bisection is perfomed
at a point `branch_pnt` which is a convex combination
(parameter: `branch_cvx_factor`) of the solution to the relaxation and
the midpoint of the node. If this solution lies within `branch_offset/width` of
a bound then the branch point is moved to a distance of `branch_offset/width`
from the bound.
"""
function branch_node!(t::ExtensionType, m::Optimizer)

    n = m._current_node

    lvbs = n.lower_variable_bounds
    uvbs = n.upper_variable_bounds

    max_pos = 0
    max_val = -Inf
    temp_max = 0.0

    flag = true
    for i = 1:m._branch_variable_count
        si = m._branch_to_sol_map[i]
        vi = m._working_problem._variable_info[si]
        if vi.branch_on === BRANCH
            temp_max =  uvbs[i] - lvbs[i]
            temp_max /= vi.upper_bound - vi.lower_bound
            if temp_max > max_val
                max_pos = i
                max_val = temp_max
            end
        end
    end

    lvb  = lvbs[max_pos]
    uvb  = uvbs[max_pos]
    si   = m._branch_to_sol_map[max_pos]
    lsol = m._lower_solution[si]

    cvx_f = m._parameters.branch_cvx_factor
    cvx_g = m._parameters.branch_offset

    branch_pnt = cvx_f*lsol + (1.0 - cvx_f)*(lvb + uvb)/2.0
    if branch_pnt < lvb*(1.0 - cvx_g) + cvx_g*uvb
        branch_pnt = (1.0 - cvx_g)*lvb + cvx_g*uvb
    elseif branch_pnt > cvx_g*lvb + (1.0 - cvx_g)*uvb
        branch_pnt = cvx_g*lvb + (1.0 - cvx_g)*uvb
    end

    # rounds into branch points, which in turn prevents the
    # solution at the branch point from being discarded
    N1::Interval{Float64} = Interval{Float64}(lvb, branch_pnt)
    N2::Interval{Float64} = Interval{Float64}(branch_pnt, uvb)
    lvb_1 = copy(lvbs)
    uvb_1 = copy(uvbs)
    lvb_2 = copy(lvbs)
    uvb_2 = copy(uvbs)
    lvb_1[max_pos] = N1.lo
    uvb_1[max_pos] = N1.hi
    lvb_2[max_pos] = N2.lo
    uvb_2[max_pos] = N2.hi

    lower_bound = max(n.lower_bound, m._lower_objective_value)
    upper_bound = min(n.upper_bound, m._upper_objective_value)
    new_depth = n.depth + 1

    m._maximum_node_id += 1
    X1 = NodeBB(lvb_1, uvb_1, lower_bound, upper_bound, new_depth, m._maximum_node_id)
    m._maximum_node_id += 1
    X2 = NodeBB(lvb_2, uvb_2, lower_bound, upper_bound, new_depth, m._maximum_node_id)

    push!(m._stack, X1)
    push!(m._stack, X2)

    m._node_repetitions = 1
    m._node_count += 2

    return nothing
end

"""
$(SIGNATURES)

Stores the current node to the stack after updating lower/upper bounds.
"""
function single_storage!(t::ExtensionType, m::Optimizer)
    y = m._current_node
    m._node_repetitions += 1
    m._node_count += 1
    lower_bound = max(y.lower_bound, m._lower_objective_value)
    upper_bound = min(y.upper_bound, m._upper_objective_value)
    push!(m._stack, NodeBB(y.lower_variable_bounds, y.upper_variable_bounds,
                           lower_bound, upper_bound, y.depth, y.id))

    return nothing
end

"""
$(SIGNATURES)

Selects and deletes nodes from stack with lower bounds greater than global
upper bound.
"""
function fathom!(t::ExtensionType, m::Optimizer)

    upper = m._global_upper_bound
    continue_flag = !isempty(m._stack)

    while continue_flag
        max_node = maximum(m._stack)
        max_check = (max_node.lower_bound > upper)

        if max_check
            popmax!(m._stack)
            m._node_count -= 1
            if isempty(m._stack)
                continue_flag = false
            end

        else
            if !max_check
                continue_flag = false
            elseif isempty(m._stack)
                continue_flag = false
            end

        end
    end

    return nothing
end

"""
$(SIGNATURES)

Checks to see if current node should be reprocessed.
"""
function repeat_check(t::ExtensionType, m::Optimizer)
    return false
end

relative_gap(L::Float64, U::Float64) = ((L > -Inf) && (U < Inf)) ?  abs(U - L)/(max(abs(L), abs(U))) : Inf
relative_tolerance(L::Float64, U::Float64, tol::Float64) = relative_gap(L, U)  > tol || ~(L > -Inf)

"""
$(SIGNATURES)

Checks for termination of algorithm due to satisfying absolute or relative
tolerance, infeasibility, or a specified limit, returns a boolean valued true
if algorithm should continue.
"""
function termination_check(t::ExtensionType, m::Optimizer)

    node_in_stack = length(m._stack)
    L = m._global_lower_bound
    U = m._global_upper_bound

    if node_in_stack === 0

        if m._first_solution_node > 0
            m._termination_status_code = MOI.OPTIMAL
            m._result_status_code = MOI.FEASIBLE_POINT
            (m._parameters.verbosity >= 3) && println("Empty Stack: Exhaustive Search Finished")

        else
            m._termination_status_code = MOI.INFEASIBLE
            m._result_status_code = MOI.INFEASIBILITY_CERTIFICATE
            (m._parameters.verbosity >= 3) && println("Empty Stack: Infeasible")
        end

    elseif node_in_stack >= m._parameters.node_limit

        m._termination_status_code = MOI.NODE_LIMIT
        m._result_status_code = MOI.UNKNOWN_RESULT_STATUS
        (m._parameters.verbosity >= 3) && println("Node Limit Exceeded")

    elseif m._iteration_count >= m._parameters.iteration_limit

        m._termination_status_code = MOI.ITERATION_LIMIT
        m._result_status_code = MOI.UNKNOWN_RESULT_STATUS
        (m._parameters.verbosity >= 3) && println("Maximum Iteration Exceeded")

    elseif ~relative_tolerance(L, U, m._parameters.relative_tolerance)

        m._termination_status_code = MOI.OPTIMAL
        m._result_status_code = MOI.FEASIBLE_POINT
        (m._parameters.verbosity >= 3) && println("Relative Tolerance Achieved")

    elseif (U - L) < m._parameters.absolute_tolerance

        m._termination_status_code = MOI.OPTIMAL
        m._result_status_code = MOI.FEASIBLE_POINT
        (m._parameters.verbosity >= 3) && println("Absolute Tolerance Achieved")

    elseif m._run_time > m._parameters.time_limit

        m._termination_status_code = MOI.TIME_LIMIT
        m._result_status_code = MOI.UNKNOWN_RESULT_STATUS
        (m._parameters.verbosity >= 3) && println("Time Limit Exceeded")

    else

        return false

    end

    return true
end

"""
$(SIGNATURES)

Checks for convergence of algorithm with respect to absolute and/or relative
tolerances.
"""
function convergence_check(t::ExtensionType, m::Optimizer)

  L = m._lower_objective_value
  U = m._global_upper_bound
  t = (U - L) <= m._parameters.absolute_tolerance
  if (U < Inf) && (L > Inf)
      t |= (abs(U - L)/(max(abs(L), abs(U))) <= m._parameters.relative_tolerance)
  end

  if t && m._min_converged_value < Inf
      m._min_converged_value = min(m._min_converged_value, L)
  else
      m._min_converged_value = L
  end

  return t
end

"""
$(SIGNATURES)

Takes an `MOI.TerminationStatusCode` and a `MOI.ResultStatusCode` and returns
the tuple `(valid_result::Bool, feasible::Bool)`. The value `valid_result` is
`true` if the pair of codes prove that either the subproblem solution was solved
to global optimality or the subproblem solution is infeasible. The value of
`feasible` is true if the problem is feasible and false if the problem is infeasible.
"""
function is_globally_optimal(t::MOI.TerminationStatusCode, r::MOI.ResultStatusCode)

    feasible = false
    valid_result = false

    if (t === MOI.INFEASIBLE && r === MOI.INFEASIBILITY_CERTIFICATE)
        valid_result = true

    elseif (t === MOI.INFEASIBLE && r === MOI.NO_SOLUTION)
        valid_result = true

    elseif (t === MOI.INFEASIBLE && r === MOI.UNKNOWN_RESULT_STATUS)
        valid_result = true

    elseif (t === MOI.OPTIMAL && r === MOI.FEASIBLE_POINT)
        valid_result = true
        feasible = true

    elseif (t === MOI.INFEASIBLE_OR_UNBOUNDED && r === MOI.NO_SOLUTION)
        valid_result = true
        feasible = false

    end

    return valid_result, feasible
end

"""
$(SIGNATURES)

Takes an `MOI.TerminationStatusCode` and a `MOI.ResultStatusCode` and returns `true`
if this corresponds to a solution that is proven to be feasible.
Returns `false` otherwise.
"""
function is_feasible_solution(t::MOI.TerminationStatusCode, r::MOI.ResultStatusCode)

    termination_flag = false
    result_flag = false

    (t === MOI.OPTIMAL) && (termination_flag = true)
    (t === MOI.LOCALLY_SOLVED) && (termination_flag = true)

    # This is default solver specific... the acceptable constraint tolerances
    # are set to the same values as the basic tolerance. As a result, an
    # acceptably solved solution is feasible but non necessarily optimal
    # so it should be treated as a feasible point
    if (t === MOI.ALMOST_LOCALLY_SOLVED) && (r === MOI.NEARLY_FEASIBLE_POINT)
        termination_flag = true
        result_flag = true
    end

    (r === MOI.FEASIBLE_POINT) && (result_flag = true)

    return (termination_flag && result_flag)
end

"""
$(SIGNATURES)

Retrieves the lower and upper duals for variable bounds from the
`relaxed_optimizer` and sets the appropriate values in the
`_lower_lvd` and `_lower_uvd` storage fields.
"""
function set_dual!(m::Optimizer)

    relaxed_optimizer = m.relaxed_optimizer
    relaxed_variable_lt = m._relaxed_variable_lt
    relaxed_variable_gt = m._relaxed_variable_gt

    for i = 1:m._working_problem._var_leq_count
        ci_lt, i_lt = @inbounds relaxed_variable_lt[i]
        @inbounds m._lower_uvd[i_lt] = MOI.get(relaxed_optimizer, MOI.ConstraintDual(), ci_lt)
    end
    for i = 1:m._working_problem._var_geq_count
        ci_gt, i_gt = @inbounds relaxed_variable_gt[i]
        @inbounds m._lower_lvd[i_gt] = MOI.get(relaxed_optimizer, MOI.ConstraintDual(), ci_gt)
    end

    return nothing
end

"""
$(SIGNATURES)

Runs interval, linear, quadratic contractor methods followed by obbt and a
constraint programming walk up to tolerances specified in
`EAGO.Optimizer` object.
"""
function preprocess!(t::ExtensionType, m::Optimizer)

    reset_relaxation!(m)

    wp = m._working_problem
    params = m._parameters

    # Sets initial feasibility
    feasible_flag = true
    m._obbt_performed_flag = false

    # compute initial volume
    m._initial_volume = prod(upper_variable_bounds(m._current_node) -
                             lower_variable_bounds(m._current_node))

    if params.fbbt_lp_depth >= m._iteration_count
        load_fbbt_buffer!(m)
        for i = 1:m._parameters.fbbt_lp_repetitions
            if feasible_flag
                for j = 1:wp._saf_leq_count
                    !feasible_flag && break
                    saf_leq =  wp._saf_leq[j]
                    feasible_flag &= fbbt!(m, saf_leq)
                end
                !feasible_flag && break

                for j = 1:wp._saf_eq_count
                    !feasible_flag && break
                    saf_eq = wp._saf_eq[j]
                    feasible_flag &= fbbt!(m, saf_eq)
                end
                !feasible_flag && break
            end
        end
        unpack_fbbt_buffer!(m)
    end

    # done after cp to prevent using cp specific flags in cut generation
    set_first_relax_point!(m)

    cp_walk_count = 0
    perform_cp_walk_flag = feasible_flag
    perform_cp_walk_flag &= (params.cp_depth >= m._iteration_count)
    perform_cp_walk_flag &= (cp_walk_count < m._parameters.cp_repetitions)
    while perform_cp_walk_flag
        feasible_flag &= set_constraint_propagation_fbbt!(m)
        !feasible_flag && break
        cp_walk_count += 1
        perform_cp_walk_flag = (cp_walk_count < m._parameters.cp_repetitions)
    end

    obbt_count = 0
    perform_obbt_flag = feasible_flag
    perform_obbt_flag &= (params.obbt_depth >= m._iteration_count)
    perform_obbt_flag &= (obbt_count < m._parameters.obbt_repetitions)

    while perform_obbt_flag
        feasible_flag &= obbt!(m)
        m._obbt_performed_flag = true
        !feasible_flag && break
        obbt_count += 1
        perform_obbt_flag     = (obbt_count < m._parameters.obbt_repetitions)
    end

    m._final_volume = prod(upper_variable_bounds(m._current_node) -
                           lower_variable_bounds(m._current_node))

    m._preprocess_feasibility = feasible_flag

    return nothing
end

"""
$(SIGNATURES)

Updates the relaxed constraint by setting the constraint set of `v == x*`` ,
`xL_i <= x_i`, and `x_i <= xU_i` for each such constraint added to the relaxed
optimizer.
"""
function update_relaxed_problem_box!(m::Optimizer)

    opt = m.relaxed_optimizer
    wp = m._working_problem

    n = m._current_node
    lower_bound = n.lower_variable_bounds
    upper_bound = n.upper_variable_bounds

    relaxed_variable_eq = m._relaxed_variable_eq
    for i = 1:wp._var_eq_count
        constr_indx, node_indx =  relaxed_variable_eq[i]
        MOI.set(opt, MOI.ConstraintSet(), constr_indx, ET( lower_bound[node_indx]))
    end

    relaxed_variable_lt = m._relaxed_variable_lt
    for i = 1:wp._var_leq_count
        constr_indx, node_indx =  relaxed_variable_lt[i]
        MOI.set(opt, MOI.ConstraintSet(), constr_indx, LT( upper_bound[node_indx]))
    end

    relaxed_variable_gt = m._relaxed_variable_gt
    for i = 1:wp._var_geq_count
        constr_indx, node_indx =  relaxed_variable_gt[i]
        MOI.set(opt, MOI.ConstraintSet(), constr_indx, GT( lower_bound[node_indx]))
    end

    return nothing
end

function interval_objective_bound(m::Optimizer, n::NodeBB)

    interval_objective_bound = bound_objective(m)

    if interval_objective_bound > m._lower_objective_value
        m._lower_objective_value = interval_objective_bound
        fill!(m._lower_lvd, 0.0)
        fill!(m._lower_uvd, 0.0)
        m._cut_add_flag = false
        return true

    end

    return false
end

"""
$(SIGNATURES)

A fallback lower bounding problem that consists of an natural interval extension
calculation. This is called when the optimizer used to compute the lower bound
does not return a termination and primal status code indicating that it
successfully solved the relaxation to a globally optimal point.
"""
function fallback_interval_lower_bound!(m::Optimizer, n::NodeBB)

    feasible_flag = true

    if !cp_condition(m)
        for i = 1:m._working_problem._saf_leq_count
            saf_leq =  m._working_problem._saf_leq[i]
            feasible_flag &= (lower_interval_bound(m, saf_leq, n) <= 0.0)
            !feasible_flag && break
        end

        if feasible_flag
            for i = 1:m._working_problem._saf_eq_count
                saf_eq =  m._working_problem._saf_eq[i]
                lower_value, upper_value = interval_bound(m, saf_eq, n)
                feasible_flag &= (lower_value <= 0.0 <= upper_value)
                !feasible_flag && break
            end
        end

        if feasible_flag
            for i = 1:m._working_problem._sqf_leq_count
                sqf_leq =  m._working_problem._sqf_leq[i]
                feasible_flag &= (lower_interval_bound(m, sqf_leq, n) <= 0.0)
                !feasible_flag && break
            end
        end

        if feasible_flag
            for i = 1:m._working_problem._sqf_eq_count
                sqf_eq =  m._working_problem._sqf_eq[i]
                lower_value, upper_value = interval_bound(m, sqf_eq, n)
                feasible_flag &= (lower_value <= 0.0 <= upper_value)
                !feasible_flag && break
            end
        end

        if feasible_flag
            for i = 1:m._working_problem._nonlinear_count
                nl_constr =  m._working_problem._nonlinear_constr[i]
                lower_value, upper_value = interval_bound(m, nl_constr, n)
                feasible_flag &= upper_value < nl_constr.lower_bound
                feasible_flag &= lower_value > nl_constr.upper_bound
                !feasible_flag && break
            end
        end
    end

    if feasible_flag
        interval_objective_used = interval_objective_bound(m, n)
        @__dot__ m._current_xref = 0.5*(n.upper_variable_bounds + n.lower_variable_bounds)
        unsafe_check_fill!(isnan, m._current_xref, 0.0, length(m._current_xref))
    else
        m._lower_objective_value = -Inf
    end
    m._lower_feasibility = feasible_flag

    return nothing
end

"""
$(SIGNATURES)

Constructs and solves the relaxation using the default EAGO relaxation scheme
and optimizer on node `y`.
"""
function lower_problem!(t::ExtensionType, m::Optimizer)

    n = m._current_node

    m._working_problem._relaxed_evaluator.is_post = m._parameters.subgrad_tighten
    if !m._obbt_performed_flag
        if m._nonlinear_evaluator_created
            set_node!(m._working_problem._relaxed_evaluator, n)
            set_node_flag!(m)
            set_reference_point!(m)
            fill!(m._working_problem._relaxed_evaluator.subexpressions_eval, false)
        end
        update_relaxed_problem_box!(m)
    end
    m._working_problem._objective_nl.has_value = false
    m._working_problem._relaxed_evaluator.interval_intersect = false

    if !m._obbt_performed_flag
        relax_constraints!(m, 1)
    end
    relax_objective!(m, 1)

    # Optimizes the object
    relaxed_optimizer = m.relaxed_optimizer

    MOI.set(relaxed_optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    MOI.optimize!(relaxed_optimizer)

    m._lower_termination_status = MOI.get(relaxed_optimizer, MOI.TerminationStatus())
    m._lower_result_status = MOI.get(relaxed_optimizer, MOI.PrimalStatus())
    valid_flag, feasible_flag = is_globally_optimal(m._lower_termination_status, m._lower_result_status)

    if valid_flag && feasible_flag
        set_dual!(m)
        m._cut_add_flag = true
        m._lower_feasibility = true
        m._lower_objective_value = MOI.get(relaxed_optimizer, MOI.ObjectiveValue())
        for i = 1:m._working_problem._variable_count
             m._lower_solution[i] = MOI.get(relaxed_optimizer, MOI.VariablePrimal(), m._relaxed_variable_index[i])
        end

    elseif valid_flag
        m._cut_add_flag = false
        m._lower_feasibility  = false
        m._lower_objective_value = -Inf

    else
        fallback_interval_lower_bound!(m, n)
    end

    return nothing
end

"""
$(SIGNATURES)

Updates the internal storage in the optimizer after a valid feasible cut is added.
"""
function cut_update!(m::Optimizer)

    m._cut_feasibility = true

    relaxed_optimizer = m.relaxed_optimizer
    obj_val = MOI.get(relaxed_optimizer, MOI.ObjectiveValue())
    prior_obj_val = (m._cut_iterations == 2) ? m._lower_objective_value : m._cut_objective_value

    m._cut_add_flag = true
    m._lower_termination_status = m._cut_termination_status
    m._lower_result_status = m._cut_result_status
    m._cut_solution[:] = MOI.get(relaxed_optimizer, MOI.VariablePrimal(), m._relaxed_variable_index)

    if prior_obj_val < obj_val
        m._cut_objective_value = obj_val
        m._lower_objective_value = obj_val
        set_dual!(m)
        copyto!(m._lower_solution, m._cut_solution)

    else
        m._cut_objective_value = prior_obj_val
        m._lower_objective_value = prior_obj_val
        m._cut_add_flag = false
    end

    return nothing
end

"""
$(SIGNATURES)

Checks if a cut should be added and computes a new reference point to add the
cut at. If no cut should be added the constraints not modified in place are
deleted from the relaxed optimizer and the solution is compared with the
interval lower bound. The best lower bound is then used.
"""
function cut_condition(t::ExtensionType, m::Optimizer)

    # always add cut if below the minimum iteration limit, otherwise add cut
    # the number of cuts is less than the maximum and the distance between
    # prior solutions exceeded a tolerance.
    continue_cut_flag = m._cut_add_flag
    continue_cut_flag &= (m._cut_iterations < m._parameters.cut_max_iterations)

    # compute distance between prior solutions and compare to tolerances
    n = m._current_node
    ns_indx = m._branch_to_sol_map

    cvx_factor =  m._parameters.cut_cvx
    xsol = (m._cut_iterations > 1) ? m._cut_solution[ns_indx] : m._lower_solution[ns_indx]
    xnew = (1.0 - cvx_factor)*mid(n) + cvx_factor*xsol

    continue_cut_flag &= (norm((xsol - xnew)/diam(n), 1) > m._parameters.cut_tolerance)
    continue_cut_flag |= (m._cut_iterations < m._parameters.cut_min_iterations)

    # update reference point for new cut
    if continue_cut_flag
        copyto!(m._current_xref, xnew)
        if m._nonlinear_evaluator_created
            set_reference_point!(m)
            fill!(m._working_problem._relaxed_evaluator.subexpressions_eval, false)
        end
    end

    # check to see if interval bound is preferable and replaces the objective
    # value with the interval value if so. Any available dual values are then
    # set to zero since the interval bounds are by definition constant
    if m._lower_feasibility && !continue_cut_flag
        objective_lo = -Inf
        obj_type = m._working_problem._objective_type
        if obj_type === SINGLE_VARIABLE
            var_index = m._working_problem._objective_sv.variable.value
            if m._branch_variables[var_index]
                obj_indx = m._sol_to_branch_map[var_index]
                lower_variable_bnd = n.lower_variable_bounds[obj_indx]
                if !isinf(lower_variable_bnd)
                    objective_lo = lower_variable_bnd
                end
            end

        elseif obj_type === SCALAR_AFFINE
            objective_lo = lower_interval_bound(m, m._working_problem._objective_saf_parsed, n)

        elseif obj_type === SCALAR_QUADRATIC
            objective_lo = lower_interval_bound(m, m._working_problem._objective_sqf, n)

        elseif obj_type === NONLINEAR
            objective_lo = lower_interval_bound(m, m._working_problem._objective_nl, n)

        end

        if objective_lo > m._lower_objective_value
            m._lower_objective_value = objective_lo
            fill!(m._lower_lvd, 0.0)
            fill!(m._lower_uvd, 0.0)
        end
    end

    m._cut_iterations += 1

    return continue_cut_flag
end

"""
$(SIGNATURES)

Adds a cut for each constraint and the objective function to the subproblem.
"""
function add_cut!(t::ExtensionType, m::Optimizer)

    fill!(m._working_problem._relaxed_evaluator.subexpressions_eval, false)
    m._working_problem._relaxed_evaluator.is_first_eval = true
    m._working_problem._relaxed_evaluator.is_intersect = false
    m._new_eval_objective = true
    m._new_eval_constraint = true

    relax_constraints!(m, m._cut_iterations)
    relax_objective!(m, m._cut_iterations)

    # Optimizes the object
    relaxed_optimizer = m.relaxed_optimizer
    MOI.optimize!(relaxed_optimizer)

    m._cut_termination_status = MOI.get(relaxed_optimizer, MOI.TerminationStatus())
    m._cut_result_status = MOI.get(relaxed_optimizer, MOI.PrimalStatus())
    valid_flag, feasible_flag = is_globally_optimal(m._cut_termination_status, m._cut_result_status)

    if valid_flag && feasible_flag
        cut_update!(m)

    elseif valid_flag
        m._cut_add_flag = false
        m._lower_feasibility  = false
        m._lower_objective_value = -Inf

    else
        m._cut_add_flag = false
    end

    return nothing
end

"""
$(SIGNATURES)

Default check to see if the upper bounding problem should be run. By default,
The upper bounding problem is run on every node up to depth `upper_bounding_depth`
and is triggered with a probability of `0.5^(depth - upper_bounding_depth)`
afterwards.
"""
function default_nlp_heurestic(m::Optimizer)
    bool = false
    ubd_limit = m._parameters.upper_bounding_depth
    depth = m._current_node.depth
    bool |= (depth <= ubd_limit)
    bool |= (rand() < 0.5^(depth - m._parameters.upper_bounding_depth))
    return bool
end

"""
$(SIGNATURES)

Default upper bounding problem which simply calls `solve_local_nlp!` to solve
the nlp locally.
"""
function upper_problem!(t::ExtensionType, m::Optimizer)

    if !default_nlp_heurestic(m)
        m._upper_feasibility = false
        m._upper_objective_value = Inf

    else
        solve_local_nlp!(m)

    end

    return nothing
end

"""
$(SIGNATURES)

Default postprocess perfoms duality-based bound tightening on the `y`.
"""
function postprocess!(t::ExtensionType, m::Optimizer)

    if m._parameters.dbbt_depth > m._iteration_count
        variable_dbbt!(m._current_node, m._lower_lvd, m._lower_uvd,
                       m._lower_objective_value, m._global_upper_bound,
                       m._branch_variable_count)
    end

    return nothing
end

"""
$(SIGNATURES)

Provides a hook for extensions to EAGO as opposed to standard global, local,
or linear solvers.
"""
optimize_hook!(t::ExtensionType, m::Optimizer) = nothing

function store_candidate_solution!(m::Optimizer)

    if m._upper_feasibility && (m._upper_objective_value < m._global_upper_bound)

        m._feasible_solution_found = true
        m._first_solution_node = m._maximum_node_id
        m._solution_value = m._upper_objective_value
        m._global_upper_bound = m._upper_objective_value
        @__dot__ m._continuous_solution = m._upper_solution

    end
    return nothing
end

function set_global_lower_bound!(m::Optimizer)

    if !isempty(m._stack)

        min_node = minimum(m._stack)
        lower_bound = min_node.lower_bound
        if m._global_lower_bound < lower_bound
            m._global_lower_bound = lower_bound
        end

    end

    return nothing
end

# wraps subroutine call to isolate ExtensionType
parse_global!(m::Optimizer) = parse_global!(m.ext_type, m)
presolve_global!(m::Optimizer) = presolve_global!(m.ext_type, m)
termination_check(m::Optimizer) = termination_check(m.ext_type, m)
cut_condition(m::Optimizer) = cut_condition(m.ext_type, m)
convergence_check(m::Optimizer) = convergence_check(m.ext_type, m)
repeat_check(m::Optimizer) = repeat_check(m.ext_type, m)
node_selection!(m::Optimizer) = node_selection!(m.ext_type, m)
preprocess!(m::Optimizer) = preprocess!(m.ext_type, m)
lower_problem!(m::Optimizer) = lower_problem!(m.ext_type, m)
add_cut!(m::Optimizer) = add_cut!(m.ext_type, m)
upper_problem!(m::Optimizer) = upper_problem!(m.ext_type, m)
postprocess!(m::Optimizer) = postprocess!(m.ext_type, m)
single_storage!(m::Optimizer) = single_storage!(m.ext_type, m)
branch_node!(m::Optimizer) = branch_node!(m.ext_type, m)
fathom!(m::Optimizer) = fathom!(m.ext_type, m)
revert_adjusted_upper_bound!(m::Optimizer) = revert_adjusted_upper_bound!(m.ext_type, m)

"""
$(TYPEDSIGNATURES)

Solves the branch and bound problem with the input EAGO optimizer object.
"""
function global_solve!(m::Optimizer)

    m._iteration_count = 1
    m._node_count = 1

    parse_global!(m)
    presolve_global!(m)

    logging_on = m._parameters.log_on
    verbosity = m._parameters.verbosity

    # terminates when max nodes or iteration is reach, or when node stack is empty
    while !termination_check(m)

        # Selects node, deletes it from stack, prints based on verbosity
        node_selection!(m)
        (verbosity >= 3) && print_node!(m)

        # Performs prepocessing and times
        logging_on && (start_time = time())
        preprocess!(m)
        if logging_on
            m._last_preprocess_time = time() - start_time
        end

        if m._preprocess_feasibility

            # solves & times lower bounding problem
            logging_on && (start_time = time())
            m._cut_iterations = 1
            lower_problem!(m)
            while cut_condition(m)
                add_cut!(m)
            end
            if logging_on
                m._last_lower_problem_time = time() - start_time
            end
            print_results!(m, true)
            print_results_post_cut!(m)

            # checks for infeasibility stores solution
            if m._lower_feasibility
                if !convergence_check(m)

                    logging_on && (start_time = time())
                    upper_problem!(m)
                    if logging_on
                        m._last_upper_problem_time = time() - start_time
                    end
                    print_results!(m, false)
                    store_candidate_solution!(m)
                    if m._input_problem._optimization_sense === MOI.FEASIBILITY_SENSE
                        if !m.feasible_local_continue || m.local_solve_only
                            break
                        end
                    end

                    # Performs and times post processing
                    logging_on && (start_time = time())
                    postprocess!(m)
                    if logging_on
                        m._last_postprocessing_time = time() - start_time
                    end

                    # Checks to see if the node
                    if m._postprocess_feasibility
                        if repeat_check(m)
                            single_storage!(m)
                        else
                            branch_node!(m)
                        end
                    end
                end
            end
            fathom!(m)
        else
            m._lower_objective_value = -Inf
            m._lower_feasibility = false
            m._upper_feasibility = false
        end
        set_global_lower_bound!(m)
        m._run_time = time() - m._start_time
        m._time_left = m._parameters.time_limit - m._run_time
        log_iteration!(m)
        print_iteration!(m)
        m._iteration_count += 1
    end

    revert_adjusted_upper_bound!(m)
    m._objective_value = m._global_upper_bound

    # Prints the solution
    print_solution!(m)

    return nothing
end

optimize!(::Val{MINCVX}, m::Optimizer) = global_solve!(m)


"""
    publish_post!(state, post_list, agent_idx, tick_nr)
Publish a post to the network.
# Arguments
- `state`: a tuple of the current graph and agent_list
- `post_list`: List of all published posts in network
- `agent_idx`: agent index
- `tick_nr`: Number of current simulation tick
See also: [`Config`](@ref), [`Agent`](@ref)
"""
function publish_post!(
    state::Tuple{AbstractGraph, AbstractArray},
    post_list::AbstractArray,
    agent_idx::Integer,
    tick_nr::Integer=0
)

    graph, agent_list = state
    this_agent = agent_list[agent_idx]

    post_opinion = this_agent.opinion + 0.1 * (2 * rand() - 1)

    if post_opinion > 1
        post_opinion = 1.0
    elseif post_opinion < -1
        post_opinion = -1.0
    end

    post = Post(
        post_opinion,
        length(outneighbors(graph, agent_idx)),
        agent_idx,
        tick_nr
    )

    push!(post_list, post)

    for neighbor in outneighbors(graph, agent_idx)
        push!(agent_list[neighbor].feed, post)
    end

    return state, post_list
end

# suppress output of include()
;


resolve_flat(tab, x::Symbol) = (x in colnames(tab)) ? x => bycol(identity) : throw(KeyError("column `$(x)` is not present in this $(typeof(tab))."))

function resolve_flat(tab, x::Pair{<:Any,<:Callable})
    column_names = _resolve(tab, selection(first(x)))
    if length(column_names) > 1
        column_names => byrow.(last(x), Tuple(column_names))
    else
        column_names => transformation(last(x))
    end
end

function resolve_flat(tab, x::Pair{<:Any,<:Union{RowwiseTrans,TabwiseTrans}})
    column_names = _resolve(tab, selection(first(x)))
    column_names => typeof(last(x))(last(x).f, Tuple(column_names))
end

function resolve_flat(tab, x::Pair{<:Any,<:ColwiseTrans})
    _resolve(tab, selection(first(x))) => last(x)
end

resolve_nested(tab, s::SelectionQuery) = SelectionResult(_resolve(tab, s.s), s.r, s.t)
resolve_nested(tab, s::AbstractMultiSelection) = _resolve(tab, s)

positive_selection(tab, symbols::Tuple{Vararg{Symbol}}, b::Bool) = b ? collect(symbols) : setdiff(colnames(tab), symbols)
positive_selection(tab, symbols::Vector{Symbol}, b::Bool) = b ? symbols : setdiff(colnames(tab), symbols)
positive_selection(tab, symbol::Symbol, b::Bool) = b ? [symbol] : setdiff(colnames(tab), [symbol])
positive_selection(tab, s::AbstractArray{Bool}, b::Bool) = b ? @inbounds(colnames(tab)[s]) : @inbounds(colnames(tab)[.-s])

function _resolve(tab, s::SymbolSelection)
    if !(s.s in colnames(tab))
        throw(KeyError(s.s))
    else
        positive_selection(tab, s.s, bool(s))
    end
end

function _resolve(tab, s::IntSelection)
    positive_selection(tab, colnames(tab)[s.s], bool(s))
end

function _resolve(tab, s::BoolSelection)
    c = colnames(tab)
    if length(c) != length(s.s)
        throw(BoundsError(c, s.s))
    else
        @inbounds(c[bool(s) ? s.s : .!s.s])
    end
end

function _resolve(tab, s::RangeSelection{<:Symbol})
    s1, s2 = s.s1, s.s2
    c = colnames(tab)

    i = findfirst(x -> x == s1, c)
    i == nothing && throw(KeyError(s1))

    j = findfirst(x -> x == s2, c)
    j == nothing && throw(KeyError(s2))

    idx = i > j ? reverse(j:s.step:i) : i:s.step:j
    positive_selection(tab, @inbounds(c[idx]), bool(s))
end

function _resolve(tab, s::RangeSelection{<:Int})
    s1, s2 = s.s1, s.s2
    c = colnames(tab)
    idx = s1 > s2 ? reverse(s2:abs(s.step):s1) : s1:s.step:s2
    positive_selection(tab, c[idx], bool(s))
end

function _resolve(tab, s::ArraySelection{<:Symbol})
    sd = setdiff(s.s, colnames(tab))
    !(isempty(sd)) && throw(ArgumentError("column$(length(sd) == 1 ? "" : "s") `$(join(sd, "`, `"))` not found."))
    positive_selection(tab, s.s, bool(s))
end

function _resolve(tab, s::ArraySelection{<:Int})
    positive_selection(tab, colnames(tab)[s.s], bool(s))
end

function _resolve(tab, s::Union{Tuple,AbstractArray{<:AbstractSelection}})
    if length(s) == 1
        _resolve(tab, selection(@inbounds(s[1])))
    else
        _resolve(tab, cols(s...))
    end
end

function _resolve(tab, s::PredicateSelection)
    [k for (k, v) in pairs(map(s.f, columntable(tab))) if bool(s) ? v : !v]
end
function _resolve(tab, s::KeyPredicateSelection)
    c = colnames(tab)
    [k for (k, v) in (c .=> map(s.f, string.(c))) if bool(s) ? v : !v]
end
function _resolve(tab, s::PairPredicateSelection)
    [k for (k, v) in pairs(columntable(tab)) if bool(s) ? s.f(string(k), v) : !s.f(string(k), v)]
end
_resolve(tab, s::AllSelection) = collect(colnames(tab))
# These needs to be resolved at later stage
_resolve(tab, s::OtherSelection) = s
_resolve(tab, s::ElseSelection) = s


####################
#
# Polygon
#
####################

# Union of Shapefile Polygon types
ShpPolygon = Union{Shapefile.Polygon, Shapefile.PolygonZ, Shapefile.PolygonM}

#=
For an array of ESRIShape points
1. convert to a different coordinate system (optional)
2. convert to a Compose polygon

Note:
Units in Compose can be either:
- Context - cx, cy - using the axis coordinates
- Relative - w (width), h (height) - using the size of the image
- Absolute - pt, mm, cm, inch
If you just provide numbers e.g. if pts below was an array of tuples of Float64
the units would default to context units.
=#
function ESRItoComposePolygon(points::AbstractArray{T, 1},
                              convertcoords) where {T<:ShpPoint}
    pts = Array{Tuple{Measures.Length{:cx, Float64}, Measures.Length{:cy, Float64}}}(undef, 0)
    for pt in points
        x, y = convertcoords(pt.x, pt.y)
        push!(pts, (x * cx, y * cy))
    end
    Compose.polygon(pts)
end

# Plot a single part polygon
function compose_single(polygon::T, canvas, convertcoords, line_width,
                        line_color, fill_color) where {T<:ShpPolygon}
    compose(canvas, (context(), ESRItoComposePolygon(polygon.points, convertcoords),
                     linewidth(line_width), stroke(line_color), fill(fill_color)))
end

# Plot a multi part polygon
function compose_multi(polygon::T, canvas, convertcoords, line_width,
                       line_color, fill_color) where {T<:ShpPolygon}
    start = 1
    finish = length(polygon.points)
    for idx in polygon.parts[2:end]
        canvas = compose(canvas, (context(),
                                  ESRItoComposePolygon(polygon.points[start:idx], convertcoords),
                                  linewidth(line_width), stroke(line_color), fill(fill_color)))
        start = idx + 1
    end
    canvas = compose(canvas, (context(),
                              ESRItoComposePolygon(polygon.points[start:finish], convertcoords),
                              linewidth(line_width), stroke(line_color), fill(fill_color)))
end

# Plot an array of polygons
function draw_shp(shapes::AbstractArray{T, 1}, canvas, convertcoords,
                  line_width, line_color, fill_color, radius) where {T<:ShpPolygon}
    for polygon in shapes
        if length(polygon.parts) == 1
            canvas = compose_single(polygon, canvas, convertcoords, line_width, line_color,
                                    fill_color)
        else
            canvas = compose_multi(polygon, canvas, convertcoords, line_width, line_color,
                                   fill_color)
        end
    end
    canvas
end


using FNVHash
using Test

@testset "FNVHash.jl" begin

@testset "type $T" for T in [UInt32, UInt64, UInt128]
    @test fnv1(T, "test") isa T
    @test fnv1a(T, "test") isa T
end

@testset "empty $T" for T in [UInt32, UInt64, UInt128]
    @test fnv1(T, "") == fnv_offset_basis(T)
    @test fnv1a(T, "") == fnv_offset_basis(T)
end

@testset "vector/string equivalence $T" for T in [UInt32, UInt64, UInt128]
    data = UInt8['a', 'b', 'c']
    s = String(copy(data))
    @test fnv1(T, data) == fnv1(T, s)
    @test fnv1a(T, data) == fnv1a(T, s)
end

@testset "changing $T" for T in [UInt32, UInt64, UInt128]
    @test fnv1(T, "test") != fnv1(T, "tesu")
    @test fnv1a(T, "test") != fnv1a(T, "tesu")
end

end


@testset "Initialization" begin

    @testset "Split Variables" begin
        st = build_scen_tree()
        js = create_model(PH.scid(0))
        vid_name_map = PH.report_variable_info(js, st)
        (branch_ids, leaf_ids) = PH._split_variables(st, collect(keys(vid_name_map)))

        for br_vid in branch_ids
            vname = vid_name_map[br_vid].name
            @test (vname == "y") || occursin("x", vname)
            if vname == "y"
                @test br_vid.stage == PH.stid(2)
            else
                @test br_vid.stage == PH.stid(1)
            end
        end

        for lf_vid in leaf_ids
            vname = vid_name_map[lf_vid].name
            @test occursin("z", vname)
            @test lf_vid.stage == PH.stid(3)
        end
    end

    @testset "Build Subproblems" begin
        st = build_scen_tree()

        wr = PH.WorkerRecord(myid())
        rc = RemoteChannel(()->Channel(2*length(PH.scenarios(st))), myid())

        PH._build_subproblems(rc, wr, PH.scenarios(st), st,
                              create_model, (), NamedTuple(), SubproblemCallback[]
                              )

        for (scid, sub) in pairs(wr.subproblems)
            @test length(JuMP.all_variables(sub.problem.model)) == 6
            ncons = 0
            for (ftype,stype) in JuMP.list_of_constraint_types(sub.problem.model)
                ncons += JuMP.num_constraints(sub.problem.model, ftype, stype)
            end
            @test ncons == 7
        end

        count = 0
        my_var_map = Dict{PH.VariableID,PH.VariableInfo}()
        while isready(rc)
            count += 1
            msg = take!(rc)
            @test typeof(msg) <: PH.VariableMap
            merge!(my_var_map, msg.var_info)
            for (vid,vinfo) in pairs(msg.var_info)
                @test vid.scenario == msg.scen
            end
        end
        @test length(my_var_map) == 24
    end

    @testset "Initialize Subproblems (Scalar)" begin
        st = build_scen_tree()

        sp_map = Dict(1 => Set([PH.ScenarioID(0), PH.ScenarioID(1), PH.ScenarioID(2)]),
                      2 => Set([PH.ScenarioID(3)])
                      )

        worker_input_queues = Dict(1 => RemoteChannel(()->Channel{PH.Message}(10), myid()),
                                   2 => RemoteChannel(()->Channel{PH.Message}(10), myid())
                                   )
        worker_output_queue = RemoteChannel(()->Channel{PH.Message}(10), myid())
        futures = Dict(1 => remotecall(PH.worker_loop,
                                       myid(),
                                       1,
                                       worker_input_queues[1],
                                       worker_output_queue),
                       2 => remotecall(PH.worker_loop,
                                       myid(),
                                       2,
                                       worker_input_queues[2],
                                       worker_output_queue),
                       )
        wi = PH.WorkerInf(worker_input_queues, worker_output_queue, futures)

        my_task = @async begin
            PH._initialize_subproblems(sp_map,
                                       wi,
                                       st,
                                       create_model,
                                       (),
                                       PH.ScalarPenaltyParameter(1.0),
                                       false,
                                       SubproblemCallback[])
        end

        if timeout_wait(my_task, 90) && !istaskfailed(my_task)

            var_map = fetch(my_task)

            for scen in PH.scenarios(st)
                for (vid, vinfo) in var_map[scen]
                    vname = vinfo.name
                    @test vid.scenario == scen
                    if vid.stage == PH.stid(1)
                        @test occursin("x", vname)
                    elseif vid.stage == PH.stid(2)
                        @test vname == "y"
                    elseif vid.stage == PH.stid(3)
                        @test occursin("z", vname)
                    else
                        stage_int = PH.value(vid.stage)
                        error("Stage $(stage_int)! There is no stage $(stage_int)!!")
                    end
                end
            end

        elseif istaskfailed(my_task)

            throw(my_task.exception)

        else

            error("Test timed out")

        end

        phd = PH.PHData(PH.ScalarPenaltyParameter(1.0),
                        st,
                        sp_map,
                        var_map,
                        TimerOutputs.TimerOutput()
                        )

        for (xhid, xhat) in pairs(phd.xhat)
            vname = phd.variable_data[first(xhat.vars)].name
            for vid in xhat.vars
                @test xhid == phd.variable_data[vid].xhat_id
                @test vname == phd.variable_data[vid].name
            end
        end

        for (scen, sinfo) in phd.scenario_map
            for vid in keys(sinfo.branch_vars)
                if occursin("x", phd.variable_data[vid].name)
                    @test vid.stage == PH.stid(1)
                else
                    @test vid.stage == PH.stid(2)
                end
            end

            for vid in keys(sinfo.leaf_vars)
                @test vid.stage == PH.stid(3)
            end
        end

        my_task = @async begin
            count = 0
            messages = Vector{PH.Message}()
            while count < length(PH.scenarios(st))
                msg = PH._retrieve_message_type(wi, PH.ReportBranch)
                push!(messages, msg)
                count += 1
            end
            return messages
        end

        if timeout_wait(my_task, 90) && !istaskfailed(my_task)

            msgs = fetch(my_task)
            @test length(msgs) == length(PH.scenarios(st))
            msg_from_scen = Set{PH.ScenarioID}()

            for msg in msgs
                scen = msg.scen
                push!(msg_from_scen, scen)

                sinfo = phd.scenario_map[scen]
                @test keys(sinfo.branch_vars) == keys(msg.vals)
                @test (msg.sts == MOI.OPTIMAL ||
                       msg.sts == MOI.LOCALLY_SOLVED ||
                       msg.sts == MOI.ALMOST_LOCALLY_SOLVED
                       )

                put!(wi.output, msg)
            end

            @test msg_from_scen == PH.scenarios(st)

        elseif istaskfailed(my_task)

            throw(my_task.exception)

        else

            error("Test timed out")

        end

        my_task = @async PH._set_initial_values(phd, wi)

        if timeout_wait(my_task, 90) && !istaskfailed(my_task)
            @test fetch(my_task) >= 0.0
        elseif istaskfailed(my_task)
            throw(my_task.exception)
        else
            error("Test timedout")
        end

        my_task = @async PH._set_penalty_parameter(phd, wi)
        if timeout_wait(my_task, 90) && !istaskfailed(my_task)
            @test fetch(my_task) >= 0.0
        elseif istaskfailed(my_task)
            throw(my_task.exception)
        else
            error("Test timedout")
        end

        PH._shutdown(wi)
        PH._wait_for_shutdown(wi)
    end

    @testset "Initialize Subproblems (Proportional)" begin
        st = build_scen_tree()

        sp_map = Dict(1 => Set([PH.ScenarioID(0), PH.ScenarioID(1), PH.ScenarioID(2)]),
                      2 => Set([PH.ScenarioID(3)])
                      )

        worker_input_queues = Dict(1 => RemoteChannel(()->Channel{PH.Message}(10), myid()),
                                   2 => RemoteChannel(()->Channel{PH.Message}(10), myid())
                                   )
        worker_output_queue = RemoteChannel(()->Channel{PH.Message}(10), myid())
        futures = Dict(1 => remotecall(PH.worker_loop,
                                       myid(),
                                       1,
                                       worker_input_queues[1],
                                       worker_output_queue),
                       2 => remotecall(PH.worker_loop,
                                       myid(),
                                       2,
                                       worker_input_queues[2],
                                       worker_output_queue),
                       )
        wi = PH.WorkerInf(worker_input_queues, worker_output_queue, futures)

        my_task = @async begin
            PH._initialize_subproblems(sp_map,
                                       wi,
                                       st,
                                       create_model,
                                       (),
                                       PH.ProportionalPenaltyParameter(1.0),
                                       false,
                                       SubproblemCallback[])
        end

        if timeout_wait(my_task, 90) && !istaskfailed(my_task)

            var_map = fetch(my_task)

            for scen in PH.scenarios(st)
                for (vid, vinfo) in var_map[scen]
                    vname = vinfo.name
                    @test vid.scenario == scen
                    if vid.stage == PH.stid(1)
                        @test occursin("x", vname)
                    elseif vid.stage == PH.stid(2)
                        @test vname == "y"
                    elseif vid.stage == PH.stid(3)
                        @test occursin("z", vname)
                    else
                        stage_int = PH.value(vid.stage)
                        error("Stage $(stage_int)! There is no stage $(stage_int)!!")
                    end
                end
            end

        elseif istaskfailed(my_task)

            throw(my_task.exception)

        else

            error("Test timed out")

        end

        phd = PH.PHData(PH.ProportionalPenaltyParameter(1.0),
                        st,
                        sp_map,
                        var_map,
                        TimerOutputs.TimerOutput()
                        )

        for (xhid, xhat) in pairs(phd.xhat)
            vname = phd.variable_data[first(xhat.vars)].name
            for vid in xhat.vars
                @test xhid == phd.variable_data[vid].xhat_id
                @test vname == phd.variable_data[vid].name
            end
        end

        for (scen, sinfo) in phd.scenario_map
            for vid in keys(sinfo.branch_vars)
                if occursin("x", phd.variable_data[vid].name)
                    @test vid.stage == PH.stid(1)
                else
                    @test vid.stage == PH.stid(2)
                end
            end

            for vid in keys(sinfo.leaf_vars)
                @test vid.stage == PH.stid(3)
            end
        end

        my_task = @async begin
            messages = Vector{PH.Message}()
            count = 0
            while count < 2*length(PH.scenarios(st))
                msg = PH._retrieve_message_type(wi, Union{PH.ReportBranch,PH.PenaltyInfo})
                push!(messages, msg)
                count += 1
            end
            return messages
        end

        if timeout_wait(my_task, 90) && !istaskfailed(my_task)

            msgs = fetch(my_task)
            @test length(msgs) == 2 * length(PH.scenarios(st))

            iv_msg_from_scen = Set{PH.ScenarioID}()
            pp_msg_from_scen = Set{PH.ScenarioID}()

            for msg in msgs

                scen = msg.scen
                sinfo = phd.scenario_map[scen]

                if typeof(msg) <: PH.ReportBranch
                    push!(iv_msg_from_scen, scen)
                    @test (msg.sts == MOI.OPTIMAL ||
                           msg.sts == MOI.LOCALLY_SOLVED ||
                           msg.sts == MOI.ALMOST_LOCALLY_SOLVED
                           )
                    @test keys(sinfo.branch_vars) == keys(msg.vals)
                elseif typeof(msg) <: PH.PenaltyInfo
                    push!(pp_msg_from_scen, scen)
                    @test keys(sinfo.branch_vars) == keys(msg.penalty)
                else
                    error("Unexpected message of type $(typeof(msg))")
                end

                put!(wi.output, msg)

            end

            @test iv_msg_from_scen == PH.scenarios(st)
            @test pp_msg_from_scen == PH.scenarios(st)

        elseif istaskfailed(my_task)

            throw(my_task.exception)

        else

            error("Test timed out")

        end

        my_task = @async PH._set_initial_values(phd, wi)
        if timeout_wait(my_task, 90) && !istaskfailed(my_task)
            @test fetch(my_task) >= 0.0
        elseif istaskfailed(my_task)
            throw(my_task.exception)
        else
            error("Test timedout")
        end

        my_task = @async PH._set_penalty_parameter(phd, wi)
        if timeout_wait(my_task, 90) && !istaskfailed(my_task)
            @test fetch(my_task) >= 0.0
        elseif istaskfailed(my_task)
            throw(my_task.exception)
        else
            error("Test timedout")
        end

        PH._shutdown(wi)
        PH._wait_for_shutdown(wi)
    end

    @testset "Initialize Subproblems (SEP)" begin
        st = build_scen_tree()

        sp_map = Dict(1 => Set([PH.ScenarioID(0), PH.ScenarioID(1), PH.ScenarioID(2)]),
                      2 => Set([PH.ScenarioID(3)])
                      )

        worker_input_queues = Dict(1 => RemoteChannel(()->Channel{PH.Message}(10), myid()),
                                   2 => RemoteChannel(()->Channel{PH.Message}(10), myid())
                                   )
        worker_output_queue = RemoteChannel(()->Channel{PH.Message}(10), myid())
        futures = Dict(1 => remotecall(PH.worker_loop,
                                       myid(),
                                       1,
                                       worker_input_queues[1],
                                       worker_output_queue),
                       2 => remotecall(PH.worker_loop,
                                       myid(),
                                       2,
                                       worker_input_queues[2],
                                       worker_output_queue),
                       )
        wi = PH.WorkerInf(worker_input_queues, worker_output_queue, futures)

        my_task = @async begin
            PH._initialize_subproblems(sp_map,
                                       wi,
                                       st,
                                       create_model,
                                       (),
                                       PH.SEPPenaltyParameter(),
                                       false,
                                       SubproblemCallback[])
        end

        if timeout_wait(my_task, 90) && !istaskfailed(my_task)

            var_map = fetch(my_task)

            for scen in PH.scenarios(st)
                for (vid, vinfo) in var_map[scen]
                    vname = vinfo.name
                    @test vid.scenario == scen
                    if vid.stage == PH.stid(1)
                        @test occursin("x", vname)
                    elseif vid.stage == PH.stid(2)
                        @test vname == "y"
                    elseif vid.stage == PH.stid(3)
                        @test occursin("z", vname)
                    else
                        stage_int = PH.value(vid.stage)
                        error("Stage $(stage_int)! There is no stage $(stage_int)!!")
                    end
                end
            end

        elseif istaskfailed(my_task)

            throw(my_task.exception)

        else

            error("Test timed out")

        end

        phd = PH.PHData(PH.SEPPenaltyParameter(),
                        st,
                        sp_map,
                        var_map,
                        TimerOutputs.TimerOutput()
                        )

        for (xhid, xhat) in pairs(phd.xhat)
            vname = phd.variable_data[first(xhat.vars)].name
            for vid in xhat.vars
                @test xhid == phd.variable_data[vid].xhat_id
                @test vname == phd.variable_data[vid].name
            end
        end

        for (scen, sinfo) in phd.scenario_map
            for vid in keys(sinfo.branch_vars)
                if occursin("x", phd.variable_data[vid].name)
                    @test vid.stage == PH.stid(1)
                else
                    @test vid.stage == PH.stid(2)
                end
            end

            for vid in keys(sinfo.leaf_vars)
                @test vid.stage == PH.stid(3)
            end
        end

        my_task = @async begin
            messages = Vector{PH.Message}()
            count = 0
            while count < 2*length(PH.scenarios(st))
                msg = PH._retrieve_message_type(wi, Union{PH.ReportBranch,PH.PenaltyInfo})
                push!(messages, msg)
                count += 1
            end
            return messages
        end

        if timeout_wait(my_task, 90) && !istaskfailed(my_task)

            msgs = fetch(my_task)
            @test length(msgs) == 2 * length(PH.scenarios(st))

            iv_msg_from_scen = Set{PH.ScenarioID}()
            pp_msg_from_scen = Set{PH.ScenarioID}()

            for msg in msgs

                scen = msg.scen
                sinfo = phd.scenario_map[scen]

                if typeof(msg) <: PH.ReportBranch
                    push!(iv_msg_from_scen, scen)
                    @test (msg.sts == MOI.OPTIMAL ||
                           msg.sts == MOI.LOCALLY_SOLVED ||
                           msg.sts == MOI.ALMOST_LOCALLY_SOLVED
                           )
                    @test keys(sinfo.branch_vars) == keys(msg.vals)
                elseif typeof(msg) <: PH.PenaltyInfo
                    push!(pp_msg_from_scen, scen)
                    @test keys(sinfo.branch_vars) == keys(msg.penalty)
                else
                    error("Unexpected message of type $(typeof(msg))")
                end

                put!(wi.output, msg)

            end

            @test iv_msg_from_scen == PH.scenarios(st)
            @test pp_msg_from_scen == PH.scenarios(st)

        elseif istaskfailed(my_task)

            throw(my_task.exception)

        else

            error("Test timed out")

        end

        my_task = @async PH._set_initial_values(phd, wi)
        if timeout_wait(my_task, 90) && !istaskfailed(my_task)
            @test fetch(my_task) >= 0.0
        elseif istaskfailed(my_task)
            throw(my_task.exception)
        else
            error("Test timedout")
        end

        my_task = @async PH._set_penalty_parameter(phd, wi)
        if timeout_wait(my_task, 90) && !istaskfailed(my_task)
            @test fetch(my_task) >= 0.0
        elseif istaskfailed(my_task)
            throw(my_task.exception)
        else
            error("Test timedout")
        end

        PH._shutdown(wi)
        PH._wait_for_shutdown(wi)
    end

    @testset "Lower Bound Initialization" begin

        st = build_scen_tree()
        sp_map = PH.assign_scenarios_to_procs(st)

        worker_input_queues = Dict(1 => RemoteChannel(()->Channel{PH.Message}(10), myid()))
        worker_output_queue = RemoteChannel(()->Channel{PH.Message}(10), myid())
        futures = Dict(1 => remotecall(PH.worker_loop,
                                       myid(),
                                       1,
                                       worker_input_queues[1],
                                       worker_output_queue)
                       )
        wi = PH.WorkerInf(worker_input_queues, worker_output_queue, futures)

        PH._initialize_lb_subproblems(sp_map, wi, st, create_model, (), false)
        PH._send_message(wi, 1, PH.DumpState())
        my_task = @async PH._retrieve_message(wi)
        if timeout_wait(my_task, 90) && !istaskfailed(my_task)
            msg = fetch(my_task)
        elseif istaskfailed(my_task)
            throw(my_task.exception)
        else
            error("Test timedout")
        end

        @test typeof(msg) <: PH.WorkerState{PH.WorkerRecord}
        @test length(msg.state.lb_subproblems) == length(scenarios(st))

        for (scen,sub) in pairs(msg.state.lb_subproblems)
            @test scen in [PH.scid(0), PH.scid(1), PH.scid(2), PH.scid(3)]
            @test length(sub.branch_vars) == 4
            @test length(sub.leaf_vars) == 2
        end

        PH._shutdown(wi)
        PH._wait_for_shutdown(wi)

    end
end


function get_test_roadway()
    roadway = Roadway()

    seg1 = RoadSegment(1,
        [Lane(LaneTag(1,1),
            [CurvePt(VecSE2(-2.0,0.0,0.0), 0.0),
             CurvePt(VecSE2(-1.0,0.0,0.0), 1.0)]),
         Lane(LaneTag(1,2),
            [CurvePt(VecSE2(-2.0,1.0,0.0), 0.0),
             CurvePt(VecSE2(-1.0,1.0,0.0), 1.0)]),
         Lane(LaneTag(1,3),
            [CurvePt(VecSE2(-2.0,2.0,0.0), 0.0),
             CurvePt(VecSE2(-1.0,2.0,0.0), 1.0)])
        ])

    seg2 = RoadSegment(2,
        [Lane(LaneTag(2,1),
            [CurvePt(VecSE2(0.0,0.0,0.0), 0.0),
             CurvePt(VecSE2(1.0,0.0,0.0), 1.0),
             CurvePt(VecSE2(2.0,0.0,0.0), 2.0),
             CurvePt(VecSE2(3.0,0.0,0.0), 3.0)]),
         Lane(LaneTag(2,2),
            [CurvePt(VecSE2(0.0,1.0,0.0), 0.0),
             CurvePt(VecSE2(1.0,1.0,0.0), 1.0),
             CurvePt(VecSE2(2.0,1.0,0.0), 2.0),
             CurvePt(VecSE2(3.0,1.0,0.0), 3.0)]),
         Lane(LaneTag(2,3),
            [CurvePt(VecSE2(0.0,2.0,0.0), 0.0),
             CurvePt(VecSE2(1.0,2.0,0.0), 1.0),
             CurvePt(VecSE2(2.0,2.0,0.0), 2.0),
             CurvePt(VecSE2(3.0,2.0,0.0), 3.0)])
         ])

    seg3 = RoadSegment(3,
        [Lane(LaneTag(3,1),
            [CurvePt(VecSE2(4.0,0.0,0.0), 0.0),
             CurvePt(VecSE2(5.0,0.0,0.0), 1.0)]),
         Lane(LaneTag(3,2),
            [CurvePt(VecSE2(4.0,1.0,0.0), 0.0),
             CurvePt(VecSE2(5.0,1.0,0.0), 1.0)]),
         Lane(LaneTag(3,3),
            [CurvePt(VecSE2(4.0,2.0,0.0), 0.0),
             CurvePt(VecSE2(5.0,2.0,0.0), 1.0)])
        ])

    for i in 1:3
        connect!(seg1.lanes[i], seg2.lanes[i])
        connect!(seg2.lanes[i], seg3.lanes[i])
    end

    push!(roadway.segments, seg1)
    push!(roadway.segments, seg2)
    push!(roadway.segments, seg3)

    roadway
end

let
    curve = get_test_curve1()

    lanetag = LaneTag(1,1)
    lane = Lane(lanetag, curve)
    show(IOBuffer(), lanetag)

    @test !has_next(lane)
    @test !has_prev(lane)

    show(IOBuffer(), RoadIndex(CurveIndex(1,0.0), lanetag))

    roadway = get_test_roadway()

    lane = roadway[LaneTag(1,1)]
    @test has_next(lane)
    @test !has_prev(lane)
    @test lane.exits[1].target == RoadIndex(CurveIndex(1,0.0), LaneTag(2,1))

    lane = roadway[LaneTag(2,1)]
    @test has_next(lane)
    @test has_prev(lane)
    @test lane.exits[1].target == RoadIndex(CurveIndex(1,0.0), LaneTag(3,1))
    @test lane.entrances[1].target == RoadIndex(CurveIndex(1,1.0), LaneTag(1,1))

    res = proj(VecSE2(1.0,0.0,0.0), lane, roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(1.0,0.0,0.0), lane, roadway, move_along_curves=false)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(1.5,0.25,0.1), lane, roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.5)
    @test isapprox(res.curveproj.t, 0.25)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == lane.tag

    res = proj(VecSE2(0.0,0.0,0.0), lane, roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(-0.75,0.0,0.0), lane, roadway)
    @test res.curveproj.ind == CurveIndex(0, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(-1.75,0.0,0.0), lane, roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == prev_lane(lane, roadway).tag

    res = proj(VecSE2(-1.75,0.0,0.0), lane, roadway, move_along_curves=false)
    @test res.curveproj.ind == CurveIndex(0, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(4.25,0.2,0.1), lane, roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == next_lane(lane, roadway).tag

    res = proj(VecSE2(4.25,0.2,0.1), lane, roadway, move_along_curves=false)
    @test res.curveproj.ind == CurveIndex(4, 1.0)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == lane.tag

    res = proj(VecSE2(3.25,0.2,0.1), lane, roadway)
    @test res.curveproj.ind == CurveIndex(4, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == lane.tag

    res = proj(VecSE2(4.25,0.2,0.1), prev_lane(lane, roadway), roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == next_lane(lane, roadway).tag

    res = proj(VecSE2(-0.75,0.0,0.0), next_lane(lane, roadway), roadway)
    @test res.curveproj.ind == CurveIndex(0, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    ####

    seg = roadway[2]

    res = proj(VecSE2(1.0,0.0,0.0), seg, roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(1.5,0.25,0.1), seg, roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.5)
    @test isapprox(res.curveproj.t, 0.25)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == lane.tag

    res = proj(VecSE2(0.0,0.0,0.0), seg, roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(-0.75,0.0,0.0), seg, roadway)
    @test res.curveproj.ind == CurveIndex(0, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(-1.75,0.0,0.0), seg, roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == prev_lane(lane, roadway).tag

    res = proj(VecSE2(4.25,0.2,0.1), seg, roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == next_lane(lane, roadway).tag

    res = proj(VecSE2(3.25,0.2,0.1), seg, roadway)
    @test res.curveproj.ind == CurveIndex(4, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == lane.tag

    res = proj(VecSE2(4.25,0.2,0.1), roadway[1], roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == next_lane(lane, roadway).tag

    res = proj(VecSE2(-0.75,0.0,0.0), roadway[3], roadway)
    @test res.curveproj.ind == CurveIndex(0, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(1.0,1.0,0.0), seg, roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == LaneTag(2,2)

    res = proj(VecSE2(1.0,2.0,0.0), seg, roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == LaneTag(2,3)

    ###

    res = proj(VecSE2(1.0,0.0,0.0), roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(1.5,0.25,0.1), roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.5)
    @test isapprox(res.curveproj.t, 0.25)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == lane.tag

    res = proj(VecSE2(0.0,0.0,0.0), roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(-0.75,0.0,0.0), roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == prev_lane(lane, roadway).tag

    res = proj(VecSE2(-1.75,0.0,0.0), roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == prev_lane(lane, roadway).tag

    res = proj(VecSE2(4.25,0.2,0.1), roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == next_lane(lane, roadway).tag

    res = proj(VecSE2(3.25,0.2,0.1), roadway)
    @test res.curveproj.ind == CurveIndex(4, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == lane.tag

    res = proj(VecSE2(4.25,0.2,0.1), roadway[1], roadway)
    @test res.curveproj.ind == CurveIndex(1, 0.25)
    @test isapprox(res.curveproj.t, 0.2)
    @test isapprox(res.curveproj.ϕ, 0.1)
    @test res.tag == next_lane(lane, roadway).tag

    res = proj(VecSE2(-0.75,0.0,0.0), roadway[3], roadway)
    @test res.curveproj.ind == CurveIndex(0, 0.25)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == lane.tag

    res = proj(VecSE2(1.0,1.0,0.0), roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == LaneTag(2,2)

    res = proj(VecSE2(1.0,2.0,0.0), roadway)
    @test res.curveproj.ind == CurveIndex(2, 0.0)
    @test isapprox(res.curveproj.t, 0.0)
    @test isapprox(res.curveproj.ϕ, 0.0)
    @test res.tag == LaneTag(2,3)

    ###

    roadind_0 = RoadIndex(CurveIndex(1,0), LaneTag(2,1))
    roadind = move_along(roadind_0, roadway, 0.0)
    @test roadind == roadind_0

    roadind = move_along(roadind_0, roadway, 1.0)
    @test roadind == RoadIndex(CurveIndex(2,0), LaneTag(2,1))

    roadind = move_along(roadind_0, roadway, 1.25)
    @test roadind == RoadIndex(CurveIndex(2,0.25), LaneTag(2,1))

    roadind = move_along(roadind_0, roadway, 3.0)
    @test roadind == RoadIndex(CurveIndex(3,1.0), LaneTag(2,1))

    roadind = move_along(roadind_0, roadway, 4.0)
    @test roadind == RoadIndex(CurveIndex(1,0.0), LaneTag(3,1))

    roadind = move_along(roadind_0, roadway, 4.5)
    @test roadind == RoadIndex(CurveIndex(1,0.5), LaneTag(3,1))

    roadind = move_along(roadind_0, roadway, 3.75)
    @test roadind == RoadIndex(CurveIndex(0,0.75), LaneTag(3,1))

    roadind = move_along(roadind_0, roadway, -1.0)
    @test roadind == RoadIndex(CurveIndex(0,0.0), LaneTag(2,1))

    roadind = move_along(roadind_0, roadway, -1.75)
    @test roadind == RoadIndex(CurveIndex(1,0.25), LaneTag(1,1))

    roadind = move_along(roadind_0, roadway, -0.75)
    @test roadind == RoadIndex(CurveIndex(0,0.25), LaneTag(2,1))

    roadind = move_along(roadind_0, roadway, -Inf)
    @test roadind == RoadIndex(CurveIndex(1,0.0), LaneTag(1,1))

    roadind = move_along(roadind_0, roadway, Inf)
    @test roadind == RoadIndex(CurveIndex(1,1.0), LaneTag(3,1))

    ####

    @test n_lanes_right(roadway[LaneTag(2,1)], roadway) == 0
    @test n_lanes_right(roadway[LaneTag(2,2)], roadway) == 1
    @test n_lanes_right(roadway[LaneTag(2,3)], roadway) == 2
    @test n_lanes_left(roadway[LaneTag(2,1)], roadway) == 2
    @test n_lanes_left(roadway[LaneTag(2,2)], roadway) == 1
    @test n_lanes_left(roadway[LaneTag(2,3)], roadway) == 0

end

let
    roadway = Roadway()

    seg1 = RoadSegment(1,
        [Lane(LaneTag(1,1),
            [CurvePt(VecSE2(0.0,0.0,0.0), 0.0),
             CurvePt(VecSE2(1.0,0.0,0.0), 1.0)],
             width=1.0,
             boundary_left = LaneBoundary(:solid, :white),
             boundary_right = LaneBoundary(:broken, :yellow)),
         Lane(LaneTag(1,2),
            [CurvePt(VecSE2(0.0,1.0,0.0), 0.0),
             CurvePt(VecSE2(1.0,1.0,0.0), 1.0)],
             width=2.0,
             boundary_left = LaneBoundary(:double, :white))],
         )

    seg2 = RoadSegment(2,
        [Lane(LaneTag(2,1),
            [CurvePt(VecSE2(4.0,0.0,0.0), 0.0),
             CurvePt(VecSE2(5.0,0.0,0.0), 1.0)]),
         Lane(LaneTag(2,2),
            [CurvePt(VecSE2(4.0,1.0,0.0), 0.0),
             CurvePt(VecSE2(5.0,1.0,0.0), 1.0)])],
         )

    connect!(seg1.lanes[1], seg2.lanes[2])

    push!(roadway.segments, seg1)
    push!(roadway.segments, seg2)

    ############

    path, io = mktemp()
    write(io, MIME"text/plain"(), roadway)
    close(io)

    lines = open(readlines, path)

    for (line_orig, line_test) in zip(lines,
            [
             "ROADWAY",
             "2",
             "1",
             "   2",
             "   1",
             "       1.000",
             "       -Inf Inf",
             "       solid white",
             "       broken yellow",
             "       1",
             "           D (1 1.000000) 1 0.000000 2 2",
             "       2",
             "           (0.0000 0.0000 0.000000) 0.0000 NaN NaN",
             "           (1.0000 0.0000 0.000000) 1.0000 NaN NaN",
             "   2",
             "       2.000",
             "       -Inf Inf",
             "       double white",
             "       unknown unknown",
             "       0",
             "       2",
             "           (0.0000 1.0000 0.000000) 0.0000 NaN NaN",
             "           (1.0000 1.0000 0.000000) 1.0000 NaN NaN",
             "2",
             "   2",
             "   1",
             "       3.000",
             "       -Inf Inf",
             "       unknown unknown",
             "       unknown unknown",
             "       0",
             "       2",
             "           (4.0000 0.0000 0.000000) 0.0000 NaN NaN",
             "           (5.0000 0.0000 0.000000) 1.0000 NaN NaN",
             "   2",
             "       3.000",
             "       -Inf Inf",
             "       unknown unknown",
             "       unknown unknown",
             "       1",
             "           U (1 0.000000) 1 1.000000 1 1",
             "       2",
             "           (4.0000 1.0000 0.000000) 0.0000 NaN NaN",
             "           (5.0000 1.0000 0.000000) 1.0000 NaN NaN",
             ]
        )
        @test strip(line_orig) == strip(line_test)
    end

    io = open(path)
    roadway2 = read(io, MIME"text/plain"(), Roadway)
    close(io)
    rm(path)

    @test length(roadway.segments) == length(roadway2.segments)
    for (seg1, seg2) in zip(roadway.segments, roadway2.segments)
        @test seg1.id == seg2.id
        @test length(seg1.lanes) == length(seg2.lanes)
        for (lane1, lane2) in zip(seg1.lanes, seg2.lanes)
            @test lane1.tag == lane2.tag
            @test isapprox(lane1.width, lane2.width, atol=1e-3)
            @test isapprox(lane1.speed_limit.lo, lane2.speed_limit.lo, atol=1e-3)
            @test isapprox(lane1.speed_limit.hi, lane2.speed_limit.hi, atol=1e-3)
            @test lane1.boundary_left == lane2.boundary_left
            @test lane1.boundary_right == lane2.boundary_right
            for (conn1, conn2) in zip(lane1.exits, lane2.exits)
                @test conn1.downstream == conn2.downstream
                @test conn1.mylane.i == conn2.mylane.i
                @test isapprox(conn1.mylane.t, conn2.mylane.t, atol=1e-3)
                @test conn1.target.tag == conn2.target.tag
                @test conn1.target.ind.i == conn2.target.ind.i
                @test isapprox(conn1.target.ind.t, conn2.target.ind.t, atol=1e-3)
            end
            for (conn1, conn2) in zip(lane1.entrances, lane2.entrances)
                @test conn1.downstream == conn2.downstream
                @test conn1.mylane.i == conn2.mylane.i
                @test isapprox(conn1.mylane.t, conn2.mylane.t, atol=1e-3)
                @test conn1.target.tag == conn2.target.tag
                @test conn1.target.ind.i == conn2.target.ind.i
                @test isapprox(conn1.target.ind.t, conn2.target.ind.t, atol=1e-3)
            end
            for (pt1, pt2) in zip(lane1.curve, lane2.curve)
                @test abs2(convert(VecE2, pt1.pos) - convert(VecE2, pt2.pos)) < 0.01
                @test angledist(pt1.pos.θ, pt2.pos.θ) < 1e-5
                @test isnan(pt1.s) || isapprox(pt1.s, pt2.s, atol=1e-3)
                @test isnan(pt1.k) || isapprox(pt1.k, pt2.k, atol=1e-6)
                @test isnan(pt1.kd) || isapprox(pt1.kd, pt2.kd, atol=1e-6)
            end
        end
    end
end

let
    lc = LaneConnection(true, CurveIndex(1,0.0), RoadIndex(CurveIndex(2,0.0), LaneTag(2,2)))
    show(IOBuffer(), lc)
end


include("roadway/main.jl")
include("definitions.jl")
include("vehicles/main.jl")
include("actions/main.jl")
include("utils/main.jl")
include("features/main.jl")
include("behaviors/main.jl")
# include("evaluation/main.jl")


import DataStructures

function (f::DiffableFunction)(vars...)
    f_gradfield = f.grad_field
    f_gradfield.inputs = collect(vars)

    xs = get_values.(vars)
    ys = as_tuple(forward(f, xs...))

    f_gradfield.generation = minimum((x -> x.generation), f_gradfield.inputs)
    f_gradfield.outputs = [Variable(y, creator=f, grad=nothing, generation=f_gradfield.generation - 1) for y in ys]

    return length(f_gradfield.outputs)  == 1 ? f_gradfield.outputs[1] : f_gradfield.outputs
end

"""
    backward(y::Variable; retain_grad=false)
Compute the back propagation, `y` as the end of the computational graph.
For a more detailed explanation, see the documentation (here).

```julia-repl
julia> x
name: nothing
values: 1
creator: User-Defined(nothing)

julia> x = Variable(1)
name: nothing
values: 1
creator: User-Defined(nothing)

julia> y = x +  2
name: nothing
values: 3
creator: JITrench.Add

julia> backward!(y)

julia> x.grad
name: nothing
values: 1
creator: User-Defined(nothing)
```
"""
function backward!(y::Variable; retain_grad=false)
    funcs = DataStructures.PriorityQueue{DiffableFunction,Int}()
    seen_set = Set{DiffableFunction}()
    if y.grad isa Nothing
        y.grad = Variable(ones_like(y.values))
    end
    DataStructures.enqueue!(funcs, y.creator, 1)
    push!(seen_set, y.creator)
    while !(isempty(funcs))
        f = DataStructures.dequeue!(funcs)
        gy = [output.grad for output in f.grad_field.outputs]
        gxs = backward(f, gy...)
        gxs = as_tuple(gxs)
        for (x, gx) in zip(f.grad_field.inputs, gxs)
            if x.grad isa Nothing
                x.grad =  gx
            else
                x.grad = x.grad + gx
            end
            if (!(isnothing(x.creator))) && (!(x.creator in seen_set))
                push!(seen_set, x.creator)
                DataStructures.enqueue!(funcs, x.creator, x.creator.grad_field.generation)
            end
        end
        if !(retain_grad)
            for y in f.grad_field.outputs
                y.grad = nothing
            end
        end
    end
    return nothing
end