# ----------------------------------------------------------------------------------- #
# Copyright (c) 2016 Varnerlab
# Robert Frederick Smith School of Chemical and Biomolecular Engineering
# Cornell University, Ithaca NY 14850
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ----------------------------------------------------------------------------------- #
#
# ----------------------------------------------------------------------------------- #
# Function: Control
# Description: Calculate the transcriptional control array at time t
# Generated on: 2016-11-02T07:00:01.342
#
# Input arguments:
# t::Float64 => Current time value (scalar)
# x::Array{Float64,1} => State array (number_of_species x 1)
# data_dictionary::Dict{AbstractString,Any} => Dictionary holding model parameters
#
# Output arguments:
# control_array::Array{Float64,1} => Transcriptional control array (number_of_genes x 1) at time t
# ----------------------------------------------------------------------------------- #
function Control(t::Float64,x::Array{Float64,1},data_dictionary::Dict{AbstractString,Any})

	# initialize the control -
	control_array = zeros(18)

	# Alias the species -
	gene_AP1 = x[1]
	gene_AhR = x[2]
	gene_CD11b = x[3]
	gene_CD14 = x[4]
	gene_CD38 = x[5]
	gene_CEBPa = x[6]
	gene_E2F = x[7]
	gene_EGR1 = x[8]
	gene_GFI1 = x[9]
	gene_IRF1 = x[10]
	gene_OCT1 = x[11]
	gene_OCT4 = x[12]
	gene_P21 = x[13]
	gene_P47Phox = x[14]
	gene_PPARg = x[15]
	gene_PU1 = x[16]
	gene_Trigger = x[17]
	gene_cRAF = x[18]
	mRNA_gene_AP1 = x[19]
	mRNA_gene_AhR = x[20]
	mRNA_gene_CD11b = x[21]
	mRNA_gene_CD14 = x[22]
	mRNA_gene_CD38 = x[23]
	mRNA_gene_CEBPa = x[24]
	mRNA_gene_E2F = x[25]
	mRNA_gene_EGR1 = x[26]
	mRNA_gene_GFI1 = x[27]
	mRNA_gene_IRF1 = x[28]
	mRNA_gene_OCT1 = x[29]
	mRNA_gene_OCT4 = x[30]
	mRNA_gene_P21 = x[31]
	mRNA_gene_P47Phox = x[32]
	mRNA_gene_PPARg = x[33]
	mRNA_gene_PU1 = x[34]
	mRNA_gene_Trigger = x[35]
	mRNA_gene_cRAF = x[36]
	protein_gene_AP1 = x[37]
	protein_gene_AhR = x[38]
	protein_gene_CD11b = x[39]
	protein_gene_CD14 = x[40]
	protein_gene_CD38 = x[41]
	protein_gene_CEBPa = x[42]
	protein_gene_E2F = x[43]
	protein_gene_EGR1 = x[44]
	protein_gene_GFI1 = x[45]
	protein_gene_IRF1 = x[46]
	protein_gene_OCT1 = x[47]
	protein_gene_OCT4 = x[48]
	protein_gene_P21 = x[49]
	protein_gene_P47Phox = x[50]
	protein_gene_PPARg = x[51]
	protein_gene_PU1 = x[52]
	protein_gene_Trigger = x[53]
	protein_gene_cRAF = x[54]

	# Alias the binding parameters -
	binding_parameter_dictionary = data_dictionary["binding_parameter_dictionary"]
	n_gene_AP1_gene_AhR = binding_parameter_dictionary["n_gene_AP1_gene_AhR"]
	K_gene_AP1_gene_AhR = binding_parameter_dictionary["K_gene_AP1_gene_AhR"]
	n_gene_AP1_gene_PU1 = binding_parameter_dictionary["n_gene_AP1_gene_PU1"]
	K_gene_AP1_gene_PU1 = binding_parameter_dictionary["K_gene_AP1_gene_PU1"]
	n_gene_AP1_gene_PPARg = binding_parameter_dictionary["n_gene_AP1_gene_PPARg"]
	K_gene_AP1_gene_PPARg = binding_parameter_dictionary["K_gene_AP1_gene_PPARg"]
	n_gene_AhR_gene_Trigger = binding_parameter_dictionary["n_gene_AhR_gene_Trigger"]
	K_gene_AhR_gene_Trigger = binding_parameter_dictionary["K_gene_AhR_gene_Trigger"]
	n_gene_CD11b_gene_PU1_gene_cRAF = binding_parameter_dictionary["n_gene_CD11b_gene_PU1_gene_cRAF"]
	K_gene_CD11b_gene_PU1_gene_cRAF = binding_parameter_dictionary["K_gene_CD11b_gene_PU1_gene_cRAF"]
	n_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF = binding_parameter_dictionary["n_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"]
	K_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF = binding_parameter_dictionary["K_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"]
	n_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF = binding_parameter_dictionary["n_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"]
	K_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF = binding_parameter_dictionary["K_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"]
	n_gene_CEBPa_gene_Trigger = binding_parameter_dictionary["n_gene_CEBPa_gene_Trigger"]
	K_gene_CEBPa_gene_Trigger = binding_parameter_dictionary["K_gene_CEBPa_gene_Trigger"]
	n_gene_CEBPa_gene_PPARg = binding_parameter_dictionary["n_gene_CEBPa_gene_PPARg"]
	K_gene_CEBPa_gene_PPARg = binding_parameter_dictionary["K_gene_CEBPa_gene_PPARg"]
	n_gene_CEBPa_gene_CEBPa = binding_parameter_dictionary["n_gene_CEBPa_gene_CEBPa"]
	K_gene_CEBPa_gene_CEBPa = binding_parameter_dictionary["K_gene_CEBPa_gene_CEBPa"]
	n_gene_CEBPa_gene_GFI1 = binding_parameter_dictionary["n_gene_CEBPa_gene_GFI1"]
	K_gene_CEBPa_gene_GFI1 = binding_parameter_dictionary["K_gene_CEBPa_gene_GFI1"]
	n_gene_E2F_gene_E2F = binding_parameter_dictionary["n_gene_E2F_gene_E2F"]
	K_gene_E2F_gene_E2F = binding_parameter_dictionary["K_gene_E2F_gene_E2F"]
	n_gene_E2F_gene_PPARg = binding_parameter_dictionary["n_gene_E2F_gene_PPARg"]
	K_gene_E2F_gene_PPARg = binding_parameter_dictionary["K_gene_E2F_gene_PPARg"]
	n_gene_E2F_gene_CEBPa = binding_parameter_dictionary["n_gene_E2F_gene_CEBPa"]
	K_gene_E2F_gene_CEBPa = binding_parameter_dictionary["K_gene_E2F_gene_CEBPa"]
	n_gene_E2F_gene_GFI1 = binding_parameter_dictionary["n_gene_E2F_gene_GFI1"]
	K_gene_E2F_gene_GFI1 = binding_parameter_dictionary["K_gene_E2F_gene_GFI1"]
	n_gene_E2F_gene_cRAF = binding_parameter_dictionary["n_gene_E2F_gene_cRAF"]
	K_gene_E2F_gene_cRAF = binding_parameter_dictionary["K_gene_E2F_gene_cRAF"]
	n_gene_EGR1_gene_Trigger = binding_parameter_dictionary["n_gene_EGR1_gene_Trigger"]
	K_gene_EGR1_gene_Trigger = binding_parameter_dictionary["K_gene_EGR1_gene_Trigger"]
	n_gene_EGR1_gene_PU1 = binding_parameter_dictionary["n_gene_EGR1_gene_PU1"]
	K_gene_EGR1_gene_PU1 = binding_parameter_dictionary["K_gene_EGR1_gene_PU1"]
	n_gene_EGR1_gene_PPARg = binding_parameter_dictionary["n_gene_EGR1_gene_PPARg"]
	K_gene_EGR1_gene_PPARg = binding_parameter_dictionary["K_gene_EGR1_gene_PPARg"]
	n_gene_EGR1_gene_GFI1 = binding_parameter_dictionary["n_gene_EGR1_gene_GFI1"]
	K_gene_EGR1_gene_GFI1 = binding_parameter_dictionary["K_gene_EGR1_gene_GFI1"]
	n_gene_GFI1_gene_CEBPa = binding_parameter_dictionary["n_gene_GFI1_gene_CEBPa"]
	K_gene_GFI1_gene_CEBPa = binding_parameter_dictionary["K_gene_GFI1_gene_CEBPa"]
	n_gene_GFI1_gene_EGR1 = binding_parameter_dictionary["n_gene_GFI1_gene_EGR1"]
	K_gene_GFI1_gene_EGR1 = binding_parameter_dictionary["K_gene_GFI1_gene_EGR1"]
	n_gene_IRF1_gene_Trigger = binding_parameter_dictionary["n_gene_IRF1_gene_Trigger"]
	K_gene_IRF1_gene_Trigger = binding_parameter_dictionary["K_gene_IRF1_gene_Trigger"]
	n_gene_IRF1_gene_AhR = binding_parameter_dictionary["n_gene_IRF1_gene_AhR"]
	K_gene_IRF1_gene_AhR = binding_parameter_dictionary["K_gene_IRF1_gene_AhR"]
	n_gene_IRF1_gene_PPARg = binding_parameter_dictionary["n_gene_IRF1_gene_PPARg"]
	K_gene_IRF1_gene_PPARg = binding_parameter_dictionary["K_gene_IRF1_gene_PPARg"]
	n_gene_OCT1_gene_PPARg = binding_parameter_dictionary["n_gene_OCT1_gene_PPARg"]
	K_gene_OCT1_gene_PPARg = binding_parameter_dictionary["K_gene_OCT1_gene_PPARg"]
	n_gene_OCT4_gene_Trigger = binding_parameter_dictionary["n_gene_OCT4_gene_Trigger"]
	K_gene_OCT4_gene_Trigger = binding_parameter_dictionary["K_gene_OCT4_gene_Trigger"]
	n_gene_OCT4_gene_AhR = binding_parameter_dictionary["n_gene_OCT4_gene_AhR"]
	K_gene_OCT4_gene_AhR = binding_parameter_dictionary["K_gene_OCT4_gene_AhR"]
	n_gene_OCT4_gene_cRAF = binding_parameter_dictionary["n_gene_OCT4_gene_cRAF"]
	K_gene_OCT4_gene_cRAF = binding_parameter_dictionary["K_gene_OCT4_gene_cRAF"]
	n_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF = binding_parameter_dictionary["n_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"]
	K_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF = binding_parameter_dictionary["K_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"]
	n_gene_P21_gene_GFI1 = binding_parameter_dictionary["n_gene_P21_gene_GFI1"]
	K_gene_P21_gene_GFI1 = binding_parameter_dictionary["K_gene_P21_gene_GFI1"]
	n_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF = binding_parameter_dictionary["n_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"]
	K_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF = binding_parameter_dictionary["K_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"]
	n_gene_P47Phox_gene_PPARg = binding_parameter_dictionary["n_gene_P47Phox_gene_PPARg"]
	K_gene_P47Phox_gene_PPARg = binding_parameter_dictionary["K_gene_P47Phox_gene_PPARg"]
	n_gene_PPARg_gene_Trigger = binding_parameter_dictionary["n_gene_PPARg_gene_Trigger"]
	K_gene_PPARg_gene_Trigger = binding_parameter_dictionary["K_gene_PPARg_gene_Trigger"]
	n_gene_PPARg_gene_CEBPa = binding_parameter_dictionary["n_gene_PPARg_gene_CEBPa"]
	K_gene_PPARg_gene_CEBPa = binding_parameter_dictionary["K_gene_PPARg_gene_CEBPa"]
	n_gene_PPARg_gene_EGR1 = binding_parameter_dictionary["n_gene_PPARg_gene_EGR1"]
	K_gene_PPARg_gene_EGR1 = binding_parameter_dictionary["K_gene_PPARg_gene_EGR1"]
	n_gene_PPARg_gene_PU1 = binding_parameter_dictionary["n_gene_PPARg_gene_PU1"]
	K_gene_PPARg_gene_PU1 = binding_parameter_dictionary["K_gene_PPARg_gene_PU1"]
	n_gene_PPARg_gene_AP1 = binding_parameter_dictionary["n_gene_PPARg_gene_AP1"]
	K_gene_PPARg_gene_AP1 = binding_parameter_dictionary["K_gene_PPARg_gene_AP1"]
	n_gene_PU1_gene_Trigger = binding_parameter_dictionary["n_gene_PU1_gene_Trigger"]
	K_gene_PU1_gene_Trigger = binding_parameter_dictionary["K_gene_PU1_gene_Trigger"]
	n_gene_PU1_gene_CEBPa = binding_parameter_dictionary["n_gene_PU1_gene_CEBPa"]
	K_gene_PU1_gene_CEBPa = binding_parameter_dictionary["K_gene_PU1_gene_CEBPa"]
	n_gene_PU1_gene_PU1 = binding_parameter_dictionary["n_gene_PU1_gene_PU1"]
	K_gene_PU1_gene_PU1 = binding_parameter_dictionary["K_gene_PU1_gene_PU1"]
	n_gene_PU1_gene_AP1 = binding_parameter_dictionary["n_gene_PU1_gene_AP1"]
	K_gene_PU1_gene_AP1 = binding_parameter_dictionary["K_gene_PU1_gene_AP1"]
	n_gene_PU1_gene_OCT1 = binding_parameter_dictionary["n_gene_PU1_gene_OCT1"]
	K_gene_PU1_gene_OCT1 = binding_parameter_dictionary["K_gene_PU1_gene_OCT1"]
	n_gene_PU1_gene_AhR = binding_parameter_dictionary["n_gene_PU1_gene_AhR"]
	K_gene_PU1_gene_AhR = binding_parameter_dictionary["K_gene_PU1_gene_AhR"]
	n_gene_PU1_gene_GFI1 = binding_parameter_dictionary["n_gene_PU1_gene_GFI1"]
	K_gene_PU1_gene_GFI1 = binding_parameter_dictionary["K_gene_PU1_gene_GFI1"]

	# Alias the control function parameters -
	control_parameter_dictionary = data_dictionary["control_parameter_dictionary"]
	W_gene_AP1_RNAP = control_parameter_dictionary["W_gene_AP1_RNAP"]
	W_gene_AP1_gene_AhR = control_parameter_dictionary["W_gene_AP1_gene_AhR"]
	W_gene_AP1_gene_PU1 = control_parameter_dictionary["W_gene_AP1_gene_PU1"]
	W_gene_AP1_gene_PPARg = control_parameter_dictionary["W_gene_AP1_gene_PPARg"]
	W_gene_AhR_RNAP = control_parameter_dictionary["W_gene_AhR_RNAP"]
	W_gene_AhR_gene_Trigger = control_parameter_dictionary["W_gene_AhR_gene_Trigger"]
	W_gene_CD11b_RNAP = control_parameter_dictionary["W_gene_CD11b_RNAP"]
	W_gene_CD11b_gene_PU1_gene_cRAF = control_parameter_dictionary["W_gene_CD11b_gene_PU1_gene_cRAF"]
	W_gene_CD14_RNAP = control_parameter_dictionary["W_gene_CD14_RNAP"]
	W_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF = control_parameter_dictionary["W_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF"]
	W_gene_CD38_RNAP = control_parameter_dictionary["W_gene_CD38_RNAP"]
	W_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF = control_parameter_dictionary["W_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF"]
	W_gene_CEBPa_RNAP = control_parameter_dictionary["W_gene_CEBPa_RNAP"]
	W_gene_CEBPa_gene_Trigger = control_parameter_dictionary["W_gene_CEBPa_gene_Trigger"]
	W_gene_CEBPa_gene_PPARg = control_parameter_dictionary["W_gene_CEBPa_gene_PPARg"]
	W_gene_CEBPa_gene_CEBPa = control_parameter_dictionary["W_gene_CEBPa_gene_CEBPa"]
	W_gene_CEBPa_gene_GFI1 = control_parameter_dictionary["W_gene_CEBPa_gene_GFI1"]
	W_gene_E2F_RNAP = control_parameter_dictionary["W_gene_E2F_RNAP"]
	W_gene_E2F_gene_E2F = control_parameter_dictionary["W_gene_E2F_gene_E2F"]
	W_gene_E2F_gene_PPARg = control_parameter_dictionary["W_gene_E2F_gene_PPARg"]
	W_gene_E2F_gene_CEBPa = control_parameter_dictionary["W_gene_E2F_gene_CEBPa"]
	W_gene_E2F_gene_GFI1 = control_parameter_dictionary["W_gene_E2F_gene_GFI1"]
	W_gene_E2F_gene_cRAF = control_parameter_dictionary["W_gene_E2F_gene_cRAF"]
	W_gene_EGR1_RNAP = control_parameter_dictionary["W_gene_EGR1_RNAP"]
	W_gene_EGR1_gene_Trigger = control_parameter_dictionary["W_gene_EGR1_gene_Trigger"]
	W_gene_EGR1_gene_PU1 = control_parameter_dictionary["W_gene_EGR1_gene_PU1"]
	W_gene_EGR1_gene_PPARg = control_parameter_dictionary["W_gene_EGR1_gene_PPARg"]
	W_gene_EGR1_gene_GFI1 = control_parameter_dictionary["W_gene_EGR1_gene_GFI1"]
	W_gene_GFI1_RNAP = control_parameter_dictionary["W_gene_GFI1_RNAP"]
	W_gene_GFI1_gene_CEBPa = control_parameter_dictionary["W_gene_GFI1_gene_CEBPa"]
	W_gene_GFI1_gene_EGR1 = control_parameter_dictionary["W_gene_GFI1_gene_EGR1"]
	W_gene_IRF1_RNAP = control_parameter_dictionary["W_gene_IRF1_RNAP"]
	W_gene_IRF1_gene_Trigger = control_parameter_dictionary["W_gene_IRF1_gene_Trigger"]
	W_gene_IRF1_gene_AhR = control_parameter_dictionary["W_gene_IRF1_gene_AhR"]
	W_gene_IRF1_gene_PPARg = control_parameter_dictionary["W_gene_IRF1_gene_PPARg"]
	W_gene_OCT1_RNAP = control_parameter_dictionary["W_gene_OCT1_RNAP"]
	W_gene_OCT1_gene_PPARg = control_parameter_dictionary["W_gene_OCT1_gene_PPARg"]
	W_gene_OCT4_RNAP = control_parameter_dictionary["W_gene_OCT4_RNAP"]
	W_gene_OCT4_gene_Trigger = control_parameter_dictionary["W_gene_OCT4_gene_Trigger"]
	W_gene_OCT4_gene_AhR = control_parameter_dictionary["W_gene_OCT4_gene_AhR"]
	W_gene_OCT4_gene_cRAF = control_parameter_dictionary["W_gene_OCT4_gene_cRAF"]
	W_gene_P21_RNAP = control_parameter_dictionary["W_gene_P21_RNAP"]
	W_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF = control_parameter_dictionary["W_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF"]
	W_gene_P21_gene_GFI1 = control_parameter_dictionary["W_gene_P21_gene_GFI1"]
	W_gene_P47Phox_RNAP = control_parameter_dictionary["W_gene_P47Phox_RNAP"]
	W_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF = control_parameter_dictionary["W_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF"]
	W_gene_P47Phox_gene_PPARg = control_parameter_dictionary["W_gene_P47Phox_gene_PPARg"]
	W_gene_PPARg_RNAP = control_parameter_dictionary["W_gene_PPARg_RNAP"]
	W_gene_PPARg_gene_Trigger = control_parameter_dictionary["W_gene_PPARg_gene_Trigger"]
	W_gene_PPARg_gene_CEBPa = control_parameter_dictionary["W_gene_PPARg_gene_CEBPa"]
	W_gene_PPARg_gene_EGR1 = control_parameter_dictionary["W_gene_PPARg_gene_EGR1"]
	W_gene_PPARg_gene_PU1 = control_parameter_dictionary["W_gene_PPARg_gene_PU1"]
	W_gene_PPARg_gene_AP1 = control_parameter_dictionary["W_gene_PPARg_gene_AP1"]
	W_gene_PU1_RNAP = control_parameter_dictionary["W_gene_PU1_RNAP"]
	W_gene_PU1_gene_Trigger = control_parameter_dictionary["W_gene_PU1_gene_Trigger"]
	W_gene_PU1_gene_CEBPa = control_parameter_dictionary["W_gene_PU1_gene_CEBPa"]
	W_gene_PU1_gene_PU1 = control_parameter_dictionary["W_gene_PU1_gene_PU1"]
	W_gene_PU1_gene_AP1 = control_parameter_dictionary["W_gene_PU1_gene_AP1"]
	W_gene_PU1_gene_OCT1 = control_parameter_dictionary["W_gene_PU1_gene_OCT1"]
	W_gene_PU1_gene_AhR = control_parameter_dictionary["W_gene_PU1_gene_AhR"]
	W_gene_PU1_gene_GFI1 = control_parameter_dictionary["W_gene_PU1_gene_GFI1"]
	W_gene_Trigger_RNAP = control_parameter_dictionary["W_gene_Trigger_RNAP"]
	W_gene_cRAF_RNAP = control_parameter_dictionary["W_gene_cRAF_RNAP"]

	# Transfer function target:gene_AP1 actor:gene_AhR
	actor_set_gene_AP1_gene_AhR = [
		protein_gene_AhR
	]
	actor = prod(actor_set_gene_AP1_gene_AhR)
	b_gene_AP1_gene_AhR = (actor^(n_gene_AP1_gene_AhR))/(K_gene_AP1_gene_AhR^(n_gene_AP1_gene_AhR)+actor^(n_gene_AP1_gene_AhR))

	# Transfer function target:gene_AP1 actor:gene_PU1
	actor_set_gene_AP1_gene_PU1 = [
		protein_gene_PU1
	]
	actor = prod(actor_set_gene_AP1_gene_PU1)
	b_gene_AP1_gene_PU1 = (actor^(n_gene_AP1_gene_PU1))/(K_gene_AP1_gene_PU1^(n_gene_AP1_gene_PU1)+actor^(n_gene_AP1_gene_PU1))

	# Transfer function target:gene_AP1 actor:gene_PPARg
	actor_set_gene_AP1_gene_PPARg = [
		protein_gene_PPARg
	]
	actor = prod(actor_set_gene_AP1_gene_PPARg)
	b_gene_AP1_gene_PPARg = (actor^(n_gene_AP1_gene_PPARg))/(K_gene_AP1_gene_PPARg^(n_gene_AP1_gene_PPARg)+actor^(n_gene_AP1_gene_PPARg))

	# Control function for gene_AP1 -
	control_array[1] = (W_gene_AP1_RNAP+W_gene_AP1_gene_AhR*b_gene_AP1_gene_AhR+W_gene_AP1_gene_PU1*b_gene_AP1_gene_PU1)/(1+W_gene_AP1_RNAP+W_gene_AP1_gene_AhR*b_gene_AP1_gene_AhR+W_gene_AP1_gene_PU1*b_gene_AP1_gene_PU1+W_gene_AP1_gene_PPARg*b_gene_AP1_gene_PPARg)

	# Transfer function target:gene_AhR actor:gene_Trigger
	actor_set_gene_AhR_gene_Trigger = [
		protein_gene_Trigger
	]
	actor = prod(actor_set_gene_AhR_gene_Trigger)
	b_gene_AhR_gene_Trigger = (actor^(n_gene_AhR_gene_Trigger))/(K_gene_AhR_gene_Trigger^(n_gene_AhR_gene_Trigger)+actor^(n_gene_AhR_gene_Trigger))

	# Control function for gene_AhR -
	control_array[2] = (W_gene_AhR_RNAP+W_gene_AhR_gene_Trigger*b_gene_AhR_gene_Trigger)/(1+W_gene_AhR_RNAP+W_gene_AhR_gene_Trigger*b_gene_AhR_gene_Trigger)

	# Transfer function target:gene_CD11b actor:gene_PU1_gene_cRAF
	actor_set_gene_CD11b_gene_PU1_gene_cRAF = [
		protein_gene_PU1
		protein_gene_cRAF
	]
	actor = prod(actor_set_gene_CD11b_gene_PU1_gene_cRAF)
	b_gene_CD11b_gene_PU1_gene_cRAF = (actor^(n_gene_CD11b_gene_PU1_gene_cRAF))/(K_gene_CD11b_gene_PU1_gene_cRAF^(n_gene_CD11b_gene_PU1_gene_cRAF)+actor^(n_gene_CD11b_gene_PU1_gene_cRAF))

	# Control function for gene_CD11b -
	control_array[3] = (W_gene_CD11b_RNAP+W_gene_CD11b_gene_PU1_gene_cRAF*b_gene_CD11b_gene_PU1_gene_cRAF)/(1+W_gene_CD11b_RNAP+W_gene_CD11b_gene_PU1_gene_cRAF*b_gene_CD11b_gene_PU1_gene_cRAF)

	# Transfer function target:gene_CD14 actor:gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF
	actor_set_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF = [
		protein_gene_PPARg
		protein_gene_CEBPa
		protein_gene_EGR1
		protein_gene_cRAF
	]
	actor = prod(actor_set_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF)
	b_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF = (actor^(n_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF))/(K_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF^(n_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF)+actor^(n_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF))

	# Control function for gene_CD14 -
	control_array[4] = (W_gene_CD14_RNAP+W_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF*b_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF)/(1+W_gene_CD14_RNAP+W_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF*b_gene_CD14_gene_PPARg_gene_CEBPa_gene_EGR1_gene_cRAF)

	# Transfer function target:gene_CD38 actor:gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF
	actor_set_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF = [
		protein_gene_IRF1
		protein_gene_PPARg
		protein_gene_Trigger
		protein_gene_cRAF
	]
	actor = prod(actor_set_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF)
	b_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF = (actor^(n_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF))/(K_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF^(n_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF)+actor^(n_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF))

	# Control function for gene_CD38 -
	control_array[5] = (W_gene_CD38_RNAP+W_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF*b_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF)/(1+W_gene_CD38_RNAP+W_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF*b_gene_CD38_gene_IRF1_gene_PPARg_gene_Trigger_gene_cRAF)

	# Transfer function target:gene_CEBPa actor:gene_Trigger
	actor_set_gene_CEBPa_gene_Trigger = [
		protein_gene_Trigger
	]
	actor = prod(actor_set_gene_CEBPa_gene_Trigger)
	b_gene_CEBPa_gene_Trigger = (actor^(n_gene_CEBPa_gene_Trigger))/(K_gene_CEBPa_gene_Trigger^(n_gene_CEBPa_gene_Trigger)+actor^(n_gene_CEBPa_gene_Trigger))

	# Transfer function target:gene_CEBPa actor:gene_PPARg
	actor_set_gene_CEBPa_gene_PPARg = [
		protein_gene_PPARg
	]
	actor = prod(actor_set_gene_CEBPa_gene_PPARg)
	b_gene_CEBPa_gene_PPARg = (actor^(n_gene_CEBPa_gene_PPARg))/(K_gene_CEBPa_gene_PPARg^(n_gene_CEBPa_gene_PPARg)+actor^(n_gene_CEBPa_gene_PPARg))

	# Transfer function target:gene_CEBPa actor:gene_CEBPa
	actor_set_gene_CEBPa_gene_CEBPa = [
		protein_gene_CEBPa
	]
	actor = prod(actor_set_gene_CEBPa_gene_CEBPa)
	b_gene_CEBPa_gene_CEBPa = (actor^(n_gene_CEBPa_gene_CEBPa))/(K_gene_CEBPa_gene_CEBPa^(n_gene_CEBPa_gene_CEBPa)+actor^(n_gene_CEBPa_gene_CEBPa))

	# Transfer function target:gene_CEBPa actor:gene_GFI1
	actor_set_gene_CEBPa_gene_GFI1 = [
		protein_gene_GFI1
	]
	actor = prod(actor_set_gene_CEBPa_gene_GFI1)
	b_gene_CEBPa_gene_GFI1 = (actor^(n_gene_CEBPa_gene_GFI1))/(K_gene_CEBPa_gene_GFI1^(n_gene_CEBPa_gene_GFI1)+actor^(n_gene_CEBPa_gene_GFI1))

	# Control function for gene_CEBPa -
	control_array[6] = (W_gene_CEBPa_RNAP+W_gene_CEBPa_gene_Trigger*b_gene_CEBPa_gene_Trigger+W_gene_CEBPa_gene_PPARg*b_gene_CEBPa_gene_PPARg+W_gene_CEBPa_gene_CEBPa*b_gene_CEBPa_gene_CEBPa)/(1+W_gene_CEBPa_RNAP+W_gene_CEBPa_gene_Trigger*b_gene_CEBPa_gene_Trigger+W_gene_CEBPa_gene_PPARg*b_gene_CEBPa_gene_PPARg+W_gene_CEBPa_gene_CEBPa*b_gene_CEBPa_gene_CEBPa+W_gene_CEBPa_gene_GFI1*b_gene_CEBPa_gene_GFI1)

	# Transfer function target:gene_E2F actor:gene_E2F
	actor_set_gene_E2F_gene_E2F = [
		protein_gene_E2F
	]
	actor = prod(actor_set_gene_E2F_gene_E2F)
	b_gene_E2F_gene_E2F = (actor^(n_gene_E2F_gene_E2F))/(K_gene_E2F_gene_E2F^(n_gene_E2F_gene_E2F)+actor^(n_gene_E2F_gene_E2F))

	# Transfer function target:gene_E2F actor:gene_PPARg
	actor_set_gene_E2F_gene_PPARg = [
		protein_gene_PPARg
	]
	actor = prod(actor_set_gene_E2F_gene_PPARg)
	b_gene_E2F_gene_PPARg = (actor^(n_gene_E2F_gene_PPARg))/(K_gene_E2F_gene_PPARg^(n_gene_E2F_gene_PPARg)+actor^(n_gene_E2F_gene_PPARg))

	# Transfer function target:gene_E2F actor:gene_CEBPa
	actor_set_gene_E2F_gene_CEBPa = [
		protein_gene_CEBPa
	]
	actor = prod(actor_set_gene_E2F_gene_CEBPa)
	b_gene_E2F_gene_CEBPa = (actor^(n_gene_E2F_gene_CEBPa))/(K_gene_E2F_gene_CEBPa^(n_gene_E2F_gene_CEBPa)+actor^(n_gene_E2F_gene_CEBPa))

	# Transfer function target:gene_E2F actor:gene_GFI1
	actor_set_gene_E2F_gene_GFI1 = [
		protein_gene_GFI1
	]
	actor = prod(actor_set_gene_E2F_gene_GFI1)
	b_gene_E2F_gene_GFI1 = (actor^(n_gene_E2F_gene_GFI1))/(K_gene_E2F_gene_GFI1^(n_gene_E2F_gene_GFI1)+actor^(n_gene_E2F_gene_GFI1))

	# Transfer function target:gene_E2F actor:gene_cRAF
	actor_set_gene_E2F_gene_cRAF = [
		protein_gene_cRAF
	]
	actor = prod(actor_set_gene_E2F_gene_cRAF)
	b_gene_E2F_gene_cRAF = (actor^(n_gene_E2F_gene_cRAF))/(K_gene_E2F_gene_cRAF^(n_gene_E2F_gene_cRAF)+actor^(n_gene_E2F_gene_cRAF))

	# Control function for gene_E2F -
	control_array[7] = (W_gene_E2F_RNAP+W_gene_E2F_gene_E2F*b_gene_E2F_gene_E2F)/(1+W_gene_E2F_RNAP+W_gene_E2F_gene_E2F*b_gene_E2F_gene_E2F+W_gene_E2F_gene_PPARg*b_gene_E2F_gene_PPARg+W_gene_E2F_gene_CEBPa*b_gene_E2F_gene_CEBPa+W_gene_E2F_gene_GFI1*b_gene_E2F_gene_GFI1+W_gene_E2F_gene_cRAF*b_gene_E2F_gene_cRAF)

	# Transfer function target:gene_EGR1 actor:gene_Trigger
	actor_set_gene_EGR1_gene_Trigger = [
		protein_gene_Trigger
	]
	actor = prod(actor_set_gene_EGR1_gene_Trigger)
	b_gene_EGR1_gene_Trigger = (actor^(n_gene_EGR1_gene_Trigger))/(K_gene_EGR1_gene_Trigger^(n_gene_EGR1_gene_Trigger)+actor^(n_gene_EGR1_gene_Trigger))

	# Transfer function target:gene_EGR1 actor:gene_PU1
	actor_set_gene_EGR1_gene_PU1 = [
		protein_gene_PU1
	]
	actor = prod(actor_set_gene_EGR1_gene_PU1)
	b_gene_EGR1_gene_PU1 = (actor^(n_gene_EGR1_gene_PU1))/(K_gene_EGR1_gene_PU1^(n_gene_EGR1_gene_PU1)+actor^(n_gene_EGR1_gene_PU1))

	# Transfer function target:gene_EGR1 actor:gene_PPARg
	actor_set_gene_EGR1_gene_PPARg = [
		protein_gene_PPARg
	]
	actor = prod(actor_set_gene_EGR1_gene_PPARg)
	b_gene_EGR1_gene_PPARg = (actor^(n_gene_EGR1_gene_PPARg))/(K_gene_EGR1_gene_PPARg^(n_gene_EGR1_gene_PPARg)+actor^(n_gene_EGR1_gene_PPARg))

	# Transfer function target:gene_EGR1 actor:gene_GFI1
	actor_set_gene_EGR1_gene_GFI1 = [
		protein_gene_GFI1
	]
	actor = prod(actor_set_gene_EGR1_gene_GFI1)
	b_gene_EGR1_gene_GFI1 = (actor^(n_gene_EGR1_gene_GFI1))/(K_gene_EGR1_gene_GFI1^(n_gene_EGR1_gene_GFI1)+actor^(n_gene_EGR1_gene_GFI1))

	# Control function for gene_EGR1 -
	control_array[8] = (W_gene_EGR1_RNAP+W_gene_EGR1_gene_Trigger*b_gene_EGR1_gene_Trigger+W_gene_EGR1_gene_PU1*b_gene_EGR1_gene_PU1)/(1+W_gene_EGR1_RNAP+W_gene_EGR1_gene_Trigger*b_gene_EGR1_gene_Trigger+W_gene_EGR1_gene_PU1*b_gene_EGR1_gene_PU1+W_gene_EGR1_gene_PPARg*b_gene_EGR1_gene_PPARg+W_gene_EGR1_gene_GFI1*b_gene_EGR1_gene_GFI1)

	# Transfer function target:gene_GFI1 actor:gene_CEBPa
	actor_set_gene_GFI1_gene_CEBPa = [
		protein_gene_CEBPa
	]
	actor = prod(actor_set_gene_GFI1_gene_CEBPa)
	b_gene_GFI1_gene_CEBPa = (actor^(n_gene_GFI1_gene_CEBPa))/(K_gene_GFI1_gene_CEBPa^(n_gene_GFI1_gene_CEBPa)+actor^(n_gene_GFI1_gene_CEBPa))

	# Transfer function target:gene_GFI1 actor:gene_EGR1
	actor_set_gene_GFI1_gene_EGR1 = [
		protein_gene_EGR1
	]
	actor = prod(actor_set_gene_GFI1_gene_EGR1)
	b_gene_GFI1_gene_EGR1 = (actor^(n_gene_GFI1_gene_EGR1))/(K_gene_GFI1_gene_EGR1^(n_gene_GFI1_gene_EGR1)+actor^(n_gene_GFI1_gene_EGR1))

	# Control function for gene_GFI1 -
	control_array[9] = (W_gene_GFI1_RNAP+W_gene_GFI1_gene_CEBPa*b_gene_GFI1_gene_CEBPa)/(1+W_gene_GFI1_RNAP+W_gene_GFI1_gene_CEBPa*b_gene_GFI1_gene_CEBPa+W_gene_GFI1_gene_EGR1*b_gene_GFI1_gene_EGR1)

	# Transfer function target:gene_IRF1 actor:gene_Trigger
	actor_set_gene_IRF1_gene_Trigger = [
		protein_gene_Trigger
	]
	actor = prod(actor_set_gene_IRF1_gene_Trigger)
	b_gene_IRF1_gene_Trigger = (actor^(n_gene_IRF1_gene_Trigger))/(K_gene_IRF1_gene_Trigger^(n_gene_IRF1_gene_Trigger)+actor^(n_gene_IRF1_gene_Trigger))

	# Transfer function target:gene_IRF1 actor:gene_AhR
	actor_set_gene_IRF1_gene_AhR = [
		protein_gene_AhR
	]
	actor = prod(actor_set_gene_IRF1_gene_AhR)
	b_gene_IRF1_gene_AhR = (actor^(n_gene_IRF1_gene_AhR))/(K_gene_IRF1_gene_AhR^(n_gene_IRF1_gene_AhR)+actor^(n_gene_IRF1_gene_AhR))

	# Transfer function target:gene_IRF1 actor:gene_PPARg
	actor_set_gene_IRF1_gene_PPARg = [
		protein_gene_PPARg
	]
	actor = prod(actor_set_gene_IRF1_gene_PPARg)
	b_gene_IRF1_gene_PPARg = (actor^(n_gene_IRF1_gene_PPARg))/(K_gene_IRF1_gene_PPARg^(n_gene_IRF1_gene_PPARg)+actor^(n_gene_IRF1_gene_PPARg))

	# Control function for gene_IRF1 -
	control_array[10] = (W_gene_IRF1_RNAP+W_gene_IRF1_gene_Trigger*b_gene_IRF1_gene_Trigger+W_gene_IRF1_gene_AhR*b_gene_IRF1_gene_AhR+W_gene_IRF1_gene_PPARg*b_gene_IRF1_gene_PPARg)/(1+W_gene_IRF1_RNAP+W_gene_IRF1_gene_Trigger*b_gene_IRF1_gene_Trigger+W_gene_IRF1_gene_AhR*b_gene_IRF1_gene_AhR+W_gene_IRF1_gene_PPARg*b_gene_IRF1_gene_PPARg)

	# Transfer function target:gene_OCT1 actor:gene_PPARg
	actor_set_gene_OCT1_gene_PPARg = [
		protein_gene_PPARg
	]
	actor = prod(actor_set_gene_OCT1_gene_PPARg)
	b_gene_OCT1_gene_PPARg = (actor^(n_gene_OCT1_gene_PPARg))/(K_gene_OCT1_gene_PPARg^(n_gene_OCT1_gene_PPARg)+actor^(n_gene_OCT1_gene_PPARg))

	# Control function for gene_OCT1 -
	control_array[11] = (W_gene_OCT1_RNAP+W_gene_OCT1_gene_PPARg*b_gene_OCT1_gene_PPARg)/(1+W_gene_OCT1_RNAP+W_gene_OCT1_gene_PPARg*b_gene_OCT1_gene_PPARg)

	# Transfer function target:gene_OCT4 actor:gene_Trigger
	actor_set_gene_OCT4_gene_Trigger = [
		protein_gene_Trigger
	]
	actor = prod(actor_set_gene_OCT4_gene_Trigger)
	b_gene_OCT4_gene_Trigger = (actor^(n_gene_OCT4_gene_Trigger))/(K_gene_OCT4_gene_Trigger^(n_gene_OCT4_gene_Trigger)+actor^(n_gene_OCT4_gene_Trigger))

	# Transfer function target:gene_OCT4 actor:gene_AhR
	actor_set_gene_OCT4_gene_AhR = [
		protein_gene_AhR
	]
	actor = prod(actor_set_gene_OCT4_gene_AhR)
	b_gene_OCT4_gene_AhR = (actor^(n_gene_OCT4_gene_AhR))/(K_gene_OCT4_gene_AhR^(n_gene_OCT4_gene_AhR)+actor^(n_gene_OCT4_gene_AhR))

	# Transfer function target:gene_OCT4 actor:gene_cRAF
	actor_set_gene_OCT4_gene_cRAF = [
		protein_gene_cRAF
	]
	actor = prod(actor_set_gene_OCT4_gene_cRAF)
	b_gene_OCT4_gene_cRAF = (actor^(n_gene_OCT4_gene_cRAF))/(K_gene_OCT4_gene_cRAF^(n_gene_OCT4_gene_cRAF)+actor^(n_gene_OCT4_gene_cRAF))

	# Control function for gene_OCT4 -
	control_array[12] = (W_gene_OCT4_RNAP)/(1+W_gene_OCT4_RNAP+W_gene_OCT4_gene_Trigger*b_gene_OCT4_gene_Trigger+W_gene_OCT4_gene_AhR*b_gene_OCT4_gene_AhR+W_gene_OCT4_gene_cRAF*b_gene_OCT4_gene_cRAF)

	# Transfer function target:gene_P21 actor:gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF
	actor_set_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF = [
		protein_gene_Trigger
		protein_gene_AP1
		protein_gene_PPARg
		protein_gene_PU1
		protein_gene_IRF1
		protein_gene_CEBPa
		protein_gene_cRAF
	]
	actor = prod(actor_set_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF)
	b_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF = (actor^(n_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF))/(K_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF^(n_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF)+actor^(n_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF))

	# Transfer function target:gene_P21 actor:gene_GFI1
	actor_set_gene_P21_gene_GFI1 = [
		protein_gene_GFI1
	]
	actor = prod(actor_set_gene_P21_gene_GFI1)
	b_gene_P21_gene_GFI1 = (actor^(n_gene_P21_gene_GFI1))/(K_gene_P21_gene_GFI1^(n_gene_P21_gene_GFI1)+actor^(n_gene_P21_gene_GFI1))

	# Control function for gene_P21 -
	control_array[13] = (W_gene_P21_RNAP+W_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF*b_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF)/(1+W_gene_P21_RNAP+W_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF*b_gene_P21_gene_Trigger_gene_AP1_gene_PPARg_gene_PU1_gene_IRF1_gene_CEBPa_gene_cRAF+W_gene_P21_gene_GFI1*b_gene_P21_gene_GFI1)

	# Transfer function target:gene_P47Phox actor:gene_PU1_gene_CEBPa_gene_cRAF
	actor_set_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF = [
		protein_gene_PU1
		protein_gene_CEBPa
		protein_gene_cRAF
	]
	actor = prod(actor_set_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF)
	b_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF = (actor^(n_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF))/(K_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF^(n_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF)+actor^(n_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF))

	# Transfer function target:gene_P47Phox actor:gene_PPARg
	actor_set_gene_P47Phox_gene_PPARg = [
		protein_gene_PPARg
	]
	actor = prod(actor_set_gene_P47Phox_gene_PPARg)
	b_gene_P47Phox_gene_PPARg = (actor^(n_gene_P47Phox_gene_PPARg))/(K_gene_P47Phox_gene_PPARg^(n_gene_P47Phox_gene_PPARg)+actor^(n_gene_P47Phox_gene_PPARg))

	# Control function for gene_P47Phox -
	control_array[14] = (W_gene_P47Phox_RNAP+W_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF*b_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF)/(1+W_gene_P47Phox_RNAP+W_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF*b_gene_P47Phox_gene_PU1_gene_CEBPa_gene_cRAF+W_gene_P47Phox_gene_PPARg*b_gene_P47Phox_gene_PPARg)

	# Transfer function target:gene_PPARg actor:gene_Trigger
	actor_set_gene_PPARg_gene_Trigger = [
		protein_gene_Trigger
	]
	actor = prod(actor_set_gene_PPARg_gene_Trigger)
	b_gene_PPARg_gene_Trigger = (actor^(n_gene_PPARg_gene_Trigger))/(K_gene_PPARg_gene_Trigger^(n_gene_PPARg_gene_Trigger)+actor^(n_gene_PPARg_gene_Trigger))

	# Transfer function target:gene_PPARg actor:gene_CEBPa
	actor_set_gene_PPARg_gene_CEBPa = [
		protein_gene_CEBPa
	]
	actor = prod(actor_set_gene_PPARg_gene_CEBPa)
	b_gene_PPARg_gene_CEBPa = (actor^(n_gene_PPARg_gene_CEBPa))/(K_gene_PPARg_gene_CEBPa^(n_gene_PPARg_gene_CEBPa)+actor^(n_gene_PPARg_gene_CEBPa))

	# Transfer function target:gene_PPARg actor:gene_EGR1
	actor_set_gene_PPARg_gene_EGR1 = [
		protein_gene_EGR1
	]
	actor = prod(actor_set_gene_PPARg_gene_EGR1)
	b_gene_PPARg_gene_EGR1 = (actor^(n_gene_PPARg_gene_EGR1))/(K_gene_PPARg_gene_EGR1^(n_gene_PPARg_gene_EGR1)+actor^(n_gene_PPARg_gene_EGR1))

	# Transfer function target:gene_PPARg actor:gene_PU1
	actor_set_gene_PPARg_gene_PU1 = [
		protein_gene_PU1
	]
	actor = prod(actor_set_gene_PPARg_gene_PU1)
	b_gene_PPARg_gene_PU1 = (actor^(n_gene_PPARg_gene_PU1))/(K_gene_PPARg_gene_PU1^(n_gene_PPARg_gene_PU1)+actor^(n_gene_PPARg_gene_PU1))

	# Transfer function target:gene_PPARg actor:gene_AP1
	actor_set_gene_PPARg_gene_AP1 = [
		protein_gene_AP1
	]
	actor = prod(actor_set_gene_PPARg_gene_AP1)
	b_gene_PPARg_gene_AP1 = (actor^(n_gene_PPARg_gene_AP1))/(K_gene_PPARg_gene_AP1^(n_gene_PPARg_gene_AP1)+actor^(n_gene_PPARg_gene_AP1))

	# Control function for gene_PPARg -
	control_array[15] = (W_gene_PPARg_RNAP+W_gene_PPARg_gene_Trigger*b_gene_PPARg_gene_Trigger+W_gene_PPARg_gene_CEBPa*b_gene_PPARg_gene_CEBPa+W_gene_PPARg_gene_EGR1*b_gene_PPARg_gene_EGR1)/(1+W_gene_PPARg_RNAP+W_gene_PPARg_gene_Trigger*b_gene_PPARg_gene_Trigger+W_gene_PPARg_gene_CEBPa*b_gene_PPARg_gene_CEBPa+W_gene_PPARg_gene_EGR1*b_gene_PPARg_gene_EGR1+W_gene_PPARg_gene_PU1*b_gene_PPARg_gene_PU1+W_gene_PPARg_gene_AP1*b_gene_PPARg_gene_AP1)

	# Transfer function target:gene_PU1 actor:gene_Trigger
	actor_set_gene_PU1_gene_Trigger = [
		protein_gene_Trigger
	]
	actor = prod(actor_set_gene_PU1_gene_Trigger)
	b_gene_PU1_gene_Trigger = (actor^(n_gene_PU1_gene_Trigger))/(K_gene_PU1_gene_Trigger^(n_gene_PU1_gene_Trigger)+actor^(n_gene_PU1_gene_Trigger))

	# Transfer function target:gene_PU1 actor:gene_CEBPa
	actor_set_gene_PU1_gene_CEBPa = [
		protein_gene_CEBPa
	]
	actor = prod(actor_set_gene_PU1_gene_CEBPa)
	b_gene_PU1_gene_CEBPa = (actor^(n_gene_PU1_gene_CEBPa))/(K_gene_PU1_gene_CEBPa^(n_gene_PU1_gene_CEBPa)+actor^(n_gene_PU1_gene_CEBPa))

	# Transfer function target:gene_PU1 actor:gene_PU1
	actor_set_gene_PU1_gene_PU1 = [
		protein_gene_PU1
	]
	actor = prod(actor_set_gene_PU1_gene_PU1)
	b_gene_PU1_gene_PU1 = (actor^(n_gene_PU1_gene_PU1))/(K_gene_PU1_gene_PU1^(n_gene_PU1_gene_PU1)+actor^(n_gene_PU1_gene_PU1))

	# Transfer function target:gene_PU1 actor:gene_AP1
	actor_set_gene_PU1_gene_AP1 = [
		protein_gene_AP1
	]
	actor = prod(actor_set_gene_PU1_gene_AP1)
	b_gene_PU1_gene_AP1 = (actor^(n_gene_PU1_gene_AP1))/(K_gene_PU1_gene_AP1^(n_gene_PU1_gene_AP1)+actor^(n_gene_PU1_gene_AP1))

	# Transfer function target:gene_PU1 actor:gene_OCT1
	actor_set_gene_PU1_gene_OCT1 = [
		protein_gene_OCT1
	]
	actor = prod(actor_set_gene_PU1_gene_OCT1)
	b_gene_PU1_gene_OCT1 = (actor^(n_gene_PU1_gene_OCT1))/(K_gene_PU1_gene_OCT1^(n_gene_PU1_gene_OCT1)+actor^(n_gene_PU1_gene_OCT1))

	# Transfer function target:gene_PU1 actor:gene_AhR
	actor_set_gene_PU1_gene_AhR = [
		protein_gene_AhR
	]
	actor = prod(actor_set_gene_PU1_gene_AhR)
	b_gene_PU1_gene_AhR = (actor^(n_gene_PU1_gene_AhR))/(K_gene_PU1_gene_AhR^(n_gene_PU1_gene_AhR)+actor^(n_gene_PU1_gene_AhR))

	# Transfer function target:gene_PU1 actor:gene_GFI1
	actor_set_gene_PU1_gene_GFI1 = [
		protein_gene_GFI1
	]
	actor = prod(actor_set_gene_PU1_gene_GFI1)
	b_gene_PU1_gene_GFI1 = (actor^(n_gene_PU1_gene_GFI1))/(K_gene_PU1_gene_GFI1^(n_gene_PU1_gene_GFI1)+actor^(n_gene_PU1_gene_GFI1))

	# Control function for gene_PU1 -
	control_array[16] = (W_gene_PU1_RNAP+W_gene_PU1_gene_Trigger*b_gene_PU1_gene_Trigger+W_gene_PU1_gene_CEBPa*b_gene_PU1_gene_CEBPa+W_gene_PU1_gene_PU1*b_gene_PU1_gene_PU1+W_gene_PU1_gene_AP1*b_gene_PU1_gene_AP1+W_gene_PU1_gene_OCT1*b_gene_PU1_gene_OCT1)/(1+W_gene_PU1_RNAP+W_gene_PU1_gene_Trigger*b_gene_PU1_gene_Trigger+W_gene_PU1_gene_CEBPa*b_gene_PU1_gene_CEBPa+W_gene_PU1_gene_PU1*b_gene_PU1_gene_PU1+W_gene_PU1_gene_AP1*b_gene_PU1_gene_AP1+W_gene_PU1_gene_OCT1*b_gene_PU1_gene_OCT1+W_gene_PU1_gene_AhR*b_gene_PU1_gene_AhR+W_gene_PU1_gene_GFI1*b_gene_PU1_gene_GFI1)

	# Control function for gene_Trigger -
	control_array[17] = (W_gene_Trigger_RNAP)/(1+W_gene_Trigger_RNAP)

	# Control function for gene_cRAF -
	control_array[18] = (W_gene_cRAF_RNAP)/(1+W_gene_cRAF_RNAP)

	# return -
	return control_array
end


# module DFSPlanner
#
# using Parameters
# using LightGraphs
# using DataStructures
# using JLD2, FileIO
# using CRCBS
#
# using ..TaskGraphs

export
    DFSRoutePlanner

"""
    DFSRoutePlanner

Prioritized Depth-First Search route planner.
"""
@with_kw struct DFSRoutePlanner{C}
    logger::SolverLogger{C} = SolverLogger{C}()
end
# function construct_heuristic_model(solver::DFSRoutePlanner,env_graph;
#         ph = PerfectHeuristic(get_dist_matrix(env_graph)),
#         kwargs...)
#     construct_composite_heuristic(ph,ph,NullHeuristic())
# end
# function construct_cost_model(solver::DFSRoutePlanner,
#         schedule, cache, problem_spec, env_graph;
#         extra_T=400, primary_objective=SumOfMakeSpans(), kwargs...)
#     cost_model = construct_composite_cost_model(
#         typeof(primary_objective)(schedule,cache),
#         FullCostModel(maximum,TravelTime()),
#         FullCostModel(maximum,TravelDistance())
#         )
#     heuristic_model = construct_heuristic_model(solver,env_graph;kwargs...)
#     # ph = PerfectHeuristic(get_dist_matrix(env_graph))
#     # heuristic_model = construct_composite_heuristic(ph,ph,NullHeuristic())
#     cost_model, heuristic_model
# end

export
    # sorted_actions,
    get_conflict_idx,
    select_action_dfs!,
    get_next_node_matching_agent_id,
    update_envs!,
    prioritized_dfs_search

@with_kw struct DFS_SearchState
    pickup_i::Int   = 0 # if i < start, iterate from current action (inclusive). Defines where to "pick back up"
    reset_i::Int    = 0 # if i > reset_i, iterate through all possible actions. Defines the point below which to "reset" the action vector
end
function update_search_state(s,i)
    s = DFS_SearchState(s,reset_i = max(s.reset_i,s.pickup_i+1))
    if s.pickup_i > i # reset pickup_i once it has been exceeded
        s = DFS_SearchState(s, pickup_i = 0)
    end
    if s.reset_i < i
       s = DFS_SearchState(s,reset_i = i)
    end
    s
end
# function sorted_actions(env,s)
#     f = (s,a,sp)->add_heuristic_cost(env,get_transition_cost(env,s,a,sp),get_heuristic_cost(env,sp))
#     sort(
#         collect(get_possible_actions(env,s)),
#         by=a->f(s,a,get_next_state(env,s,a))
#     )
# end

"""
    get_conflict_idx(envs,states,actions,i,ordering,idxs)

Check if the planned action for agent with priority `i` conflicts with the
    planned action of any agent with higher priority.
"""
function get_conflict_idx(envs,states,actions,i,ordering,idxs)
    idx = ordering[i]
    env = envs[idx]
    s = states[idx]
    a = actions[idx]
    pi = PathNode(s,a,get_next_state(env,s,a))
    for (j,idx) in enumerate(ordering[1:max(1,i-1)])
        if j == i
            continue
        end
        env = envs[idx]
        s = states[idx]
        a = actions[idx]
        pj = PathNode(s,a,get_next_state(env,s,a))
        if detect_state_conflict(pi,pj) || detect_action_conflict(pi,pj)
            return j
        end
    end
    return -1
end
function update_envs!(solver,search_env,envs,paths)
    cache = get_cache(search_env)
    schedule = get_schedule(search_env)
    cbs_node = initialize_root_node(search_env)
    # update_planning_cache!(solver,search_env)        cache.tF[v] = get_final_state(path).t

    # update cache times
    up_to_date = true
    for i in 1:length(envs)
        env = envs[i]
        path = paths[i]
        v = get_vtx(schedule,env.node_id)
        s = get_final_state(path)
        t_arrival = max(cache.tF[v], s.t + get_distance(get_graph(search_env).dist_function,s.vtx,env.goal.vtx))
        if is_goal(envs[i],s)
            if t_arrival > cache.tF[v] && env.goal.vtx != -1
                @log_info(-1,verbosity(solver),"DFS update_envs!(): extending tF[v] from $(cache.tF[v]) to $t_arrival in ",string(get_node(env)),", s = ",string(s))
                cache.tF[v] = t_arrival
                up_to_date = false
            end
        end
    end
    # reset cache
    if !up_to_date
        t0,tF,slack,local_slack = process_schedule(schedule;t0=cache.t0,tF=cache.tF)
        cache.t0            .= t0
        cache.tF            .= tF
        cache.slack         .= slack
        cache.local_slack   .= local_slack
        # rebuild envs to reset goal time
        for i in 1:length(envs)
            env = envs[i]
            path = paths[i]
            v = get_vtx(schedule,env.node_id)
            envs[i] = build_env(solver,search_env,cbs_node,v)
        end
    end
    # mark finished envs as complete
    for i in 1:length(envs)
        env = envs[i]
        path = paths[i]
        if is_goal(envs[i],get_final_state(path))
            # update schedule and cache
            v = get_vtx(schedule,env.node_id)
            if !(v in cache.closed_set)
                update_planning_cache!(solver,search_env,v,path)
                @assert v in cache.closed_set
                @assert i == env.agent_idx
            end
        end
    end
    update_planning_cache!(solver,search_env)
    i = 0
    while i < length(envs)
        i += 1
        env = envs[i]
        path = paths[i]
        v = get_vtx(schedule,env.node_id)
        if is_goal(envs[i],get_final_state(path))
            # update schedule and cache
            if !(v in cache.closed_set)
                update_planning_cache!(solver,search_env,v,path)
                update_planning_cache!(solver,search_env)
            end
            @assert v in cache.closed_set
            @assert i == env.agent_idx
            for v2 in outneighbors(schedule,v)
                if get_path_spec(schedule,v).agent_id == i
                    if !(v2 in cache.active_set)
                        @log_info(-1,solver.l2_verbosity,"node ",string(get_node_from_vtx(schedule,v2))," not yet in active set")
                        @log_info(-1,solver.l2_verbosity,"inneighbors of ",string(get_node_from_vtx(schedule,v2))," are ",map(v3->string(get_node_from_vtx(schedule,v3)),neighborhood(schedule,v2,3,dir=:in))...)
                    end
                end
            end
            # swap out env for new env
            node_id = get_next_node_matching_agent_id(schedule,cache,env.agent_idx)
            @assert node_id != env.node_id
            if get_vtx(schedule,node_id) in cache.active_set
                envs[i] = build_env(solver,search_env,cbs_node,get_vtx(schedule,node_id))
                # i = 0
                i -= 1
            else
                node_string = string(get_node_from_id(get_schedule(search_env),node_id))
                @log_info(4,verbosity(solver),"cannot update environment for agent $i because next node ",node_string," not in cache.active_set")
            end
        end
    end
    update_cost_model!(search_env)
    envs,paths
end
function select_ordering(solver,search_env,envs)
    schedule = get_schedule(search_env)
    cache = get_cache(search_env)
    ordering = sort(
        collect(1:search_env.num_agents),
        by = i->(
            ~isa(envs[i].schedule_node,Union{COLLECT,DEPOSIT}),
            ~isa(envs[i].schedule_node,CARRY),
            minimum(cache.slack[get_vtx(schedule,envs[i].node_id)])
            )
        )
    ordering
end
function select_action_dfs!(solver,envs,states,actions,i,ordering,idxs,search_state=SearchState())
    search_state = update_search_state(search_state,i)
    if i <= 0
        return false
    elseif i > length(states)
        for (env,s,a) in zip(envs,states,actions)
            if a != CRCBS.wait(env,s) || length(get_possible_actions(env,s)) == 1 || s.t < env.goal.t
                return true
            end
        end
        @log_info(4,verbosity(solver),"action vector $(map(a->string(a),actions)) not eligible")
        return false
    elseif !(ordering[i] in idxs)
        return select_action_dfs!(solver,envs,states,actions,i+1,ordering,idxs,search_state)
    else
        # idx = env.ordering_map[i]zs
        j = 0
        idx = ordering[i]
        env = envs[idx]
        s = states[idx]
        for ai in sorted_actions(env,s)
            a = actions[idx]
            c = get_transition_cost(env,s,ai)
            c0 = get_transition_cost(env,s,a)
            if (i >= search_state.reset_i) || (i < search_state.pickup_i && a == ai) || ((c >= c0 || is_valid(env,a)) && a != ai)
                actions[idx] = ai
                @log_info(5,verbosity(solver),"$(repeat(" ",i))i = $i, trying a=",string(ai)," from s = ",string(s),"for env ",string(get_node(env)), " with env.goal = ",string(env.goal))
                k = get_conflict_idx(envs,states,actions,i,ordering,idxs)
                # @assert k < i "should only check for conflicts with 1:$i, but found conflict with $k"
                if k <= 0
                    if select_action_dfs!(solver,envs,states,actions,i+1,ordering,idxs,search_state)
                        return true
                    end
                elseif !(ordering[k] in idxs)
                    # i = i - 1
                    # break
                    # return false
                else
                    @log_info(5,verbosity(solver),"--- conflict betweeh $i and $k")
                    j = max(k,j)
                end
            end
        end
        if j <= 0
            return false
        end
        # if j > 0
        search_state = DFS_SearchState(pickup_i=j,reset_i=0)
        # search_states[j] = search_states[i]
        # i = j
        # break
        # end
        return select_action_dfs!(solver,envs,states,actions,j,ordering,idxs,search_state) #
    end
    # end
end
function prioritized_dfs_search(solver,search_env,envs,paths;
        t0 = max(0, minimum(map(path->length(path),paths))),
        search_state = DFS_SearchState()
        )
     tip_times = map(path->length(path),paths)
     t = t0
     states     = map(path->get_s(get_path_node(path,t+1)), paths)
     actions    = map(path->get_a(get_path_node(path,t+1)), paths)
     while true
         increment_iteration_count(solver)
         enforce_iteration_limit!(solver)
         update_envs!(solver,search_env,envs,paths)
         if length(get_cache(search_env).active_set) == 0
             return envs, paths, true
         end
         @assert all(map(p->length(p) >= t,paths))
         ordering   = select_ordering(solver,search_env,envs)
         idxs       = Set(findall(tip_times .<= t))
         if select_action_dfs!(solver,envs,states,actions,1,ordering,idxs,search_state)
             # step forward in search
             for idx in idxs
                 env = envs[idx]
                 path = paths[idx]
                 s = states[idx]
                 a = actions[idx]
                 sp = get_next_state(env,s,a)
                 push!(path,PathNode(s,a,sp))
                 set_cost!(path, accumulate_cost(env,get_cost(path),get_transition_cost(env,s,a,sp)))
             end
             t += 1
             search_state = DFS_SearchState()
             @log_info(3,verbosity(solver),"stepping forward, t = $t")
         else
             # step backward in search
             all(tip_times .> t) == 0 ? break : nothing
             for (idx,path) in enumerate(paths)
                 if tip_times[idx] < t
                     pop!(path.path_nodes)
                     @assert length(path) == t-1
                 end
             end
             t -= 1
             idxs    = Set(findall(tip_times .<= t))
              # start new search where previous search left off
             search_state = DFS_SearchState(pickup_i = maximum(idxs))
             @log_info(-1,verbosity(solver),"stepping backward, t = $t")
         end
         states     = map(path->get_s(get_path_node(path,t+1)), paths)
         actions    = map(path->get_a(get_path_node(path,t+1)), paths)
     end
     return envs, paths, false
end

"""
    Iterate over agents in order of priority, allowing them to fill in a
    reservation table for the vtxs they would like to occupy at the next time
    step(s).
"""
function CRCBS.solve!(
    solver::DFSRoutePlanner,
    mapf::P;kwargs...) where {P<:PC_MAPF}

    search_env = mapf.env
    update_planning_cache!(solver,search_env) # NOTE to get rid of all nodes that don't need planning but are in the active set

    route_plan = deepcopy(get_route_plan(search_env))
    paths = get_paths(route_plan)
    # initialize envs
    envs = Vector{PCCBS.LowLevelEnv}([PCCBS.LowLevelEnv() for p in paths])
    cbs_node = initialize_root_node(search_env)
    for i in 1:search_env.num_agents
        node_id = get_next_node_matching_agent_id(get_schedule(search_env),get_cache(search_env),i)
        envs[i] = build_env(solver,search_env,cbs_node,get_vtx(get_schedule(search_env),node_id))
    end

    envs, paths, status = prioritized_dfs_search(solver,search_env,envs,paths;
        max_iters = solver.cbs_model.max_iters
    )
    if validate(get_schedule(search_env),convert_to_vertex_lists(route_plan),get_cache(search_env).t0,get_cache(search_env).tF)
        @log_info(0,verbosity(solver),"DFS: Succeeded in finding a valid route plan!")
    else
        # throw(SolverCBSMaxOutException("ERROR in DFS! Failed to find a valid route plan!"))
        # DUMP
        filename = joinpath(DEBUG_PATH,string("DFS_demo",get_debug_file_id(),".jld2"))
        mkpath(DEBUG_PATH)
        for env in envs
            v = get_vtx(get_schedule(search_env),env.node_id)
            @log_info(-1,verbosity(solver),"node ",string(get_node(env))," t0=$(get_t0(search_env,v)), tF=$(get_tF(search_env,v)), closed=$(v in get_cache(search_env).closed_set),")
        end
        robot_paths = convert_to_vertex_lists(route_plan)
        object_paths, object_intervals, object_ids, path_idxs = get_object_paths(route_plan,search_env)
        @log_info(-1,verbosity(solver),"Dumping DFS route plan to $filename")
        @save filename robot_paths object_paths object_intervals object_ids path_idxs

        throw(AssertionError("ERROR in DFS! Failed to find a valid route plan!"))
    end
    # for (path,base_path,env) in zip(paths,get_route_plan(search_env).paths,envs)
    #     c = base_path.cost
    #     for p in path.path_nodes[length(base_path)+1:end]
    #         c = accumulate_cost(env,c,get_transition_cost(env,p.s,p.a,p.sp))
    #     end
    #     path.cost = c
    # end
    cost = aggregate_costs(get_cost_model(search_env),map(p->get_cost(p),paths))

    return route_plan, get_cache(search_env), cost
end

# end


module Compress

# TODO: import TOML for Julia 1.6
import Pkg.TOML
const STDLIB_TOML = VERSION >= v"1.6.0-DEV.764"
import Pkg.Types: VersionSpec, VersionRange, VersionBound

"""
    compress_versions(pool::Vector{VersionNumber}, subset::Vector{VersionNumber})

Given `pool` as the pool of available versions (of some package) and `subset` as some
subset of the pool of available versions, this function computes a `VersionSpec` which
includes all versions in `subset` and none of the versions in its complement.
"""
function compress_versions(pool::Vector{VersionNumber}, subset::Vector{VersionNumber})
    # Explicitly drop prerelease/build numbers, as those can confuse this.
    # TODO: Rewrite all this to use VersionNumbers instead of VersionBounds
    drop_build_prerelease(v::VersionNumber) = VersionNumber(v.major, v.minor, v.patch)
    pool = drop_build_prerelease.(pool)
    subset = sort!(drop_build_prerelease.(subset))

    complement = sort!(setdiff(pool, subset))
    ranges = VersionRange[]
    @label again
    isempty(subset) && return VersionSpec(ranges)
    a = first(subset)
    for b in reverse(subset)
        a.major == b.major || continue
        for m = 1:3
            lo = VersionBound((a.major, a.minor, a.patch)[1:m]...)
            for n = 1:3
                hi = VersionBound((b.major, b.minor, b.patch)[1:n]...)
                r = VersionRange(lo, hi)
                if !any(v in r for v in complement)
                    filter!(!in(r), subset)
                    push!(ranges, r)
                    @goto again
                end
            end
        end
    end
end
function compress_versions(pool::Vector{VersionNumber}, subset)
    compress_versions(pool, filter(in(subset), pool))
end

function load_versions(path::String)
    versions_file = joinpath(dirname(path), "Versions.toml")
    versions_dict = TOML.parsefile(versions_file)
    sort!([VersionNumber(v) for v in keys(versions_dict)])
end

function load(path::String,
              versions::Vector{VersionNumber} = load_versions(path))
    compressed = TOML.parsefile(path)
    uncompressed = Dict{VersionNumber,Dict{Any,Any}}()
    for (vers, data) in compressed
        vs = VersionSpec(vers)
        for v in versions
            v in vs || continue
            uv = get!(uncompressed, v, Dict())
            for (key, value) in data
                if haskey(uv, key)
                    error("Overlapping ranges for $(key) in Compat. Detected for version $(v).")
                else
                    uv[key] = value
                end
            end
        end
    end
    return uncompressed
end

function compress(path::String, uncompressed::Dict,
                  versions::Vector{VersionNumber} = load_versions(path))
    inverted = Dict{Pair{String,Any},Vector{VersionNumber}}()
    for (ver, data) in uncompressed, (key, val) in data
        val isa Base.UUID && (val = string(val))
        push!(get!(inverted, key => val, VersionNumber[]), ver)
    end
    compressed = Dict{String,Dict{String,Any}}()
    for ((k, v), vers) in inverted
        for r in compress_versions(versions, sort!(vers)).ranges
            get!(compressed, string(r), Dict{String,Any}())[k] = v
        end
    end
    return compressed
end

function save(path::String, uncompressed::Dict,
              versions::Vector{VersionNumber} = load_versions(path))
    compressed = compress(path, uncompressed)
    open(path, write=true) do io
        if STDLIB_TOML
            TOML.print(string, io, compressed, sorted=true)
        else
            TOML.print(io, compressed, sorted=true)
        end
    end
end

#=
=#

end # module


import DataFrames

#TODO: share code with calibrate()
"""
    evaluate(S, obs; <keyword arguments>) -> Number | Tuple

Compare output of simulation results for the given system `S` and observation data `obs` with a choice of evaluation metric.

# Arguments
- `S::Type{<:System}`: type of system to be evaluated.
- `obs::DataFrame`: observatioan data to be used for evaluation.

# Keyword Arguments
## Configuration
- `config=()`: a single configuration for the system (can't be used with `configs`).
- `configs=[]`: multiple configurations for the system (can't be used with `config`).

## Layout
- `index=nothing`: variables to construct index columns of the output; default falls back to `context.clock.time`.
- `target`: variables to construct non-index columns of the output.

## Evaluation
- `metric=nothing`: evaluation metric (`:rmse`, `:nrmse`, `:mae`, `:mape`, `:ef`, `:dr`); default is RMSE.

Remaining keyword arguments are passed down to `simulate` with regard to running system `S`.

See also: [`simulate`](@ref), [`calibrate`](@ref), [`@config`](@ref)

# Examples
```julia-repl
julia> @system S(Controller) begin
           a => 19 ~ preserve(u"m/hr", parameter)
           b(a) ~ accumulate(u"m")
       end;

julia> obs = DataFrame(time=10u"hr", b=200u"m");

julia> configs = @config !(:S => :a => [19, 21]);

julia> evaluate(S, obs; configs, target=:b, stop=10u"hr")
10.0 m
```
"""
evaluate(S::Type{<:System}, obs; config=(), configs=[], kwargs...) = begin
    if isempty(configs)
        evaluate(S, obs, [config]; kwargs...)
    elseif isempty(config)
        evaluate(S, obs, configs; kwargs...)
    else
        @error "redundant configurations" config configs
    end
end
evaluate(S::Type{<:System}, obs, configs; index=nothing, target, metric=nothing, kwargs...) = begin
    #HACK: use copy due to normalize!
    obs = copy(obs)
    I = parseindex(index, S) |> keys |> collect
    T = parsetarget(target, S) |> keys |> collect
    n = length(T)
    multi = n > 1
    isnothing(metric) && (metric = :rmse)
    metric = metricfunc(metric)
    IC = [t for t in zip(getproperty.(Ref(obs), I)...)]
    IV = parseindex(index, S) |> values |> Tuple
    snap(s) = getproperty.(s, IV) .|> value in IC
    NT = DataFrames.make_unique([propertynames(obs)..., T...], makeunique=true)
    T1 = NT[end-n+1:end]
    residual(c) = begin
        est = simulate(S; config=c, index, target, snap, verbose=false, kwargs...)
        isempty(est) && return repeat([Inf], n)
        normalize!(est, obs, on=I)
        df = DataFrames.innerjoin(est, obs, on=I, makeunique=true)
        r = [(df[!, e], df[!, o]) for (e, o) in zip(T, T1)]
    end
    cost() = begin
        l = length(configs)
        R = Vector(undef, l)
        Threads.@threads for i in 1:l
            R[i] = residual(configs[i])
        end
        e = map(getindex.(R, i) for i in 1:n) do r
            metric(vcat(first.(r)...), vcat(last.(r)...))
        end
        multi ? Tuple(e) : only(e)
    end
    cost()
end

export evaluate


@enum PairedReadOrientation::UInt64 FwRv=1 RvFw=2

struct PairedReads <: ReadDatastore{LongSequence{DNAAlphabet{4}}}
    filename::String         # Filename datastore was opened from.
    name::String             # Name of the datastore. Useful for other applications.
    defaultname::String      # Default name, useful for other applications.
    readsize::UInt64         # Maximum size of any read in this datastore.
    chunksize::UInt64        # Number of chunks of sequence data per read.
    fragsize::UInt64         # Fragment size of library.
    readpos_offset::UInt64
    size::UInt64
    orientation::PairedReadOrientation
    stream::IOStream
end

@inline orientation(prds::PairedReads) = prds.orientation
@inline maxseqlen(prds::PairedReads) = prds.readsize
@inline name(prds::PairedReads) = prds.name
@inline stream(prds::PairedReads) = prds.stream

###
### PairedReads Header format
###

# | Field             | Value  | Type        |
# |:-----------------:|:------:|:-----------:|
# | Magic number      | 0x05D5 | UInt16      | 2
# | Datastore type    | 0x0001 | UInt16      | 2
# | Version number    | 0x0001 | UInt16      | 2
# | Default name      | N/A    | String      | 7
# | Maximum read size | N/A    | UInt64      | 8
# | Chunk size        | N/A    | UInt64      | 8
# | Fragment size     | N/A    | UInt64      | 8
# | Orientation       | N/A    | Orientation | 8
# | Number of Reads   | N/A    | UInt64      | 8

const PairedDS_Version = 0x0001

function PairedReads(rdrx::FASTQ.Reader, rdry::FASTQ.Reader,
                     outfile::String, name::String,
                     minsize::UInt64, maxsize::UInt64,
                     fragsize::UInt64, orientation::PairedReadOrientation)

    # Create and allocate the sequence and record objects.
    lread = FASTQ.Record()
    rread = FASTQ.Record()
    lseq = LongSequence{DNAAlphabet{4}}(maxsize)
    rseq = LongSequence{DNAAlphabet{4}}(maxsize)

    #chunksize::UInt64 = BioSequences.seq_data_len(DNAAlphabet{4}, maxsize)
    chunksize::UInt64 = length(BioSequences.encoded_data(lseq))

    fd = open(outfile, "w")

    # Write magic no, datastore type, version number.
    sizepos = write(fd, ReadDatastoreMAGIC, PairedDS, PairedDS_Version) +
    # Write the default name of the datastore.
    writestring(fd, name) +
    # Write the read size, and chunk size.
    write(fd, maxsize, chunksize, fragsize, orientation)
    # Write space for size variable (or number of read pairs).
    readpos = write(fd, UInt64(0)) + sizepos

    pairs = discarded = truncated = 0

    @info "Building paired read datastore from FASTQ files"
    @info "Writing paired reads to datastore"

    while !eof(rdrx) && !eof(rdry)
        # Read in the two records.
        read!(rdrx, lread)
        read!(rdry, rread)

        llen = UInt64(FASTQ.seqlen(lread))
        rlen = UInt64(FASTQ.seqlen(rread))
        # If either read is too short, discard them both.
        if llen < minsize || rlen < minsize
            discarded += 1
            continue
        end

        if llen > maxsize
            truncated += 1
            ln = maxsize
        else
            ln = llen
        end

        if rlen > maxsize
            truncated += 1
            rn = maxsize
        else
            rn = rlen
        end

        pairs += 1

        # Copy FASTQ records to sequence variables, thus encoding
        # them in 2 bit format.
        copyto!(lseq, 1, lread, 1, ln)
        copyto!(rseq, 1, rread, 1, rn)

        # Write the two sizes reads one after the other:
        # For each sequence, write the size, and then the datachunk.

        write(fd, ln)
        write(fd, lseq.data)

        write(fd, rn)
        write(fd, rseq.data)
    end

    nreads::UInt64 = pairs * 2

    seek(fd, sizepos)
    write(fd, nreads)

    close(fd)

    @info "Done writing paired read sequences to datastore"
    @info string(discarded, " read pairs were discarded due to a too short sequence")
    @info string(truncated, " reads were truncated to ", maxsize, " base pairs")
    @info string("Created paired sequence datastore with ", pairs, " sequence pairs")

    stream = open(outfile, "r+")
    return PairedReads(outfile, name, name, maxsize, chunksize, fragsize,
                       readpos, nreads, orientation, stream)
end

function Base.open(::Type{PairedReads}, filename::String)
    fd = open(filename, "r")
    magic = read(fd, UInt16)
    dstype = reinterpret(Filetype, read(fd, UInt16))
    version = read(fd, UInt16)

    @assert magic == ReadDatastoreMAGIC
    @assert dstype == PairedDS
    @assert version == PairedDS_Version

    default_name = readuntil(fd, '\0')

    readsize = read(fd, UInt64)
    chunksize = read(fd, UInt64)
    fragsize = read(fd, UInt64)
    orientation = reinterpret(PairedReadOrientation, read(fd, UInt64))
    nreads = read(fd, UInt64)
    readpos_offset = position(fd)

    return PairedReads(filename, default_name, default_name,
                       readsize, chunksize, fragsize,
                       readpos_offset, nreads, orientation, fd)
end

@inline Base.length(prds::PairedReads) = prds.size

Base.summary(io::IO, prds::PairedReads) = print(io, "Paired Read Datastore '", prds.name, "': ", length(prds), " reads")

function Base.show(io::IO, prds::PairedReads)
    summary(io, prds)
end

bytes_per_read(prds::PairedReads) = (prds.chunksize + 1) * sizeof(UInt64)
@inline unsafe_read_offset_in_file(prds::PairedReads, idx::Integer) = prds.readpos_offset + (bytes_per_read(prds) * (idx - 1))

@inline _inbounds_index_of_sequence(prds::PairedReads, idx::Integer) = prds.readpos_offset + (bytes_per_read(prds) * (idx - 1))

@inline function Base.getindex(prds::PairedReads, idx::Integer)
    @boundscheck checkbounds(prds, idx)
    seq = eltype(prds)(prds.readsize)
    return inbounds_load_sequence!(prds, idx, seq)
end


# MIT License
#
# Copyright (c) 2018 Martin Biel
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
    StochasticProgram

An instance of a stochastic optimization problem.
"""
struct StochasticProgram{N, S <: NTuple{N, Stage}, ST <: AbstractStochasticStructure{N}}
    stages::S
    decisions::Decisions{N}
    structure::ST
    generator::Dict{Symbol, Function}
    problemcache::Dict{Symbol, JuMP.Model}
    solutioncache::Dict{Symbol, SolutionCache}
    optimizer::StochasticProgramOptimizer

    function StochasticProgram(stages::NTuple{N, Stage},
                               scenario_types::ScenarioTypes{M},
                               instantiation::StochasticInstantiation,
                               optimizer_constructor) where {N, M}
        N >= 2 || error("Stochastic program needs at least two stages.")
        M == N - 1 || error("Inconsistent number of stages $N and number of scenario types $M")
        S = typeof(stages)
        decisions = Decisions(Val(N))
        optimizer = StochasticProgramOptimizer(optimizer_constructor)
        structure = StochasticStructure(decisions, scenario_types, default_structure(instantiation, optimizer.optimizer))
        ST = typeof(structure)
        return new{N, S, ST}(stages,
                             decisions,
                             structure,
                             Dict{Symbol, Function}(),
                             Dict{Symbol, JuMP.Model}(),
                             Dict{Symbol, SolutionCache}(),
                             optimizer)
    end

    function StochasticProgram(stages::NTuple{N, Stage},
                               scenarios::NTuple{M, Vector{<:AbstractScenario}},
                               instantiation::StochasticInstantiation,
                               optimizer_constructor) where {N, M}
        N >= 2 || error("Stochastic program needs at least two stages.")
        M == N - 1 || error("Inconsistent number of stages $N and number of scenario types $M")
        S = typeof(stages)
        decisions = Decisions(Val(N))
        optimizer = StochasticProgramOptimizer(optimizer_constructor)
        structure = StochasticStructure(decisions, scenarios, default_structure(instantiation, optimizer.optimizer))
        ST = typeof(structure)
        return new{N, S, ST}(stages,
                             decisions,
                             structure,
                             Dict{Symbol, Function}(),
                             Dict{Symbol, JuMP.Model}(),
                             Dict{Symbol, SolutionCache}(),
                             optimizer)
    end
end
TwoStageStochasticProgram{S <: Tuple{Stage, Stage}, ST <: AbstractStochasticStructure{2}} = StochasticProgram{2, S, ST}

# Constructors #
# ========================== #
# Two-stage
# ========================== #
"""
    StochasticProgram(first_stage_params::Any,
                      second_stage_params::Any,
                      instantiation::StochasticInstantiation,
                      optimizer_constructor=nothing) where T <: AbstractFloat

Create a new two-stage stochastic program with stage data given by `first_stage_params` and `second_stage_params`. After construction, scenarios of type `Scenario` can be added through `add_scenario!`. Optionally, a capable `optimizer_constructor` can be supplied to later optimize the stochastic program. If multiple Julia processes are available, the resulting stochastic program will automatically be memory-distributed on these processes. This can be avoided by setting `procs = [1]`.
"""
function StochasticProgram(first_stage_params::Any,
                           second_stage_params::Any,
                           instantiation::StochasticInstantiation,
                           optimizer_constructor = nothing)
    stages = (Stage(first_stage_params), Stage(second_stage_params))
    return StochasticProgram(stages, (Scenario,), instantiation, optimizer_constructor)
end
"""
    StochasticProgram(first_stage_params::Any,
                      second_stage_params::Any,
                      ::Type{Scenario},
                      instantiation::StochasticInstantiation,
                      optimizer_constructor=nothing) where Scenario <: AbstractScenario

Create a new two-stage stochastic program with stage data given by `first_stage_params` and `second_stage_params`. After construction, scenarios of type `S` can be added through `add_scenario!`. Optionally, a capable `optimizer_constructor` can be supplied to later optimize the stochastic program. If multiple Julia processes are available, the resulting stochastic program will automatically be memory-distributed on these processes. This can be avoided by setting `procs = [1]`.
"""
function StochasticProgram(first_stage_params::Any,
                           second_stage_params::Any,
                           ::Type{Scenario},
                           instantiation::StochasticInstantiation,
                           optimizer_constructor = nothing) where Scenario <: AbstractScenario
    stages = (Stage(first_stage_params), Stage(second_stage_params))
    return StochasticProgram(stages, (Scenario,), instantiation, optimizer_constructor)
end
"""
    StochasticProgram(::Type{Scenario},
                      instantiation::StochasticInstantiation,
                      optimizer_constructor=nothing) where Scenario <: AbstractScenario

Create a new two-stage stochastic program with scenarios of type `Scenario` and no stage data. Optionally, a capable `optimizer_constructor` can be supplied to later optimize the stochastic program.
"""
function StochasticProgram(::Type{Scenario},
                           instantiation::StochasticInstantiation,
                           optimizer_constructor = nothing) where Scenario <: AbstractScenario
    stages = (Stage(nothing), Stage(nothing))
    return StochasticProgram(stages, (Scenario,), instantiation, optimizer_constructor; procs = procs)
end
"""
    StochasticProgram(first_stage_params::Any,
                      second_stage_params::Any,
                      scenarios::Vector{<:AbstractScenario},
                      instantiation::StochasticInstantiation,
                      optimizer_constructor = nothing)

Create a new two-stage stochastic program with a given collection of `scenarios`. Optionally, a capable `optimizer_constructor` can be supplied to later optimize the stochastic program. If multiple Julia processes are available, the resulting stochastic program will automatically be memory-distributed on these processes. This can be avoided by setting `procs = [1]`.
"""
function StochasticProgram(first_stage_params::Any,
                           second_stage_params::Any,
                           scenarios::Vector{<:AbstractScenario},
                           instantiation::StochasticInstantiation,
                           optimizer_constructor = nothing)
    stages = (Stage(first_stage_params), Stage(second_stage_params))
    return StochasticProgram(stages, (scenarios,), instantiation, optimizer_constructor)
end
"""
    StochasticProgram(scenarios::Vector{<:AbstractScenario},
                      instantiation::StochasticInstantiation,
                      optimizer_constructor = nothing)

Create a new two-stage stochastic program with a given collection of `scenarios` and no stage data. Optionally, a capable `optimizer_constructor` can be supplied to later optimize the stochastic program. If multiple Julia processes are available, the resulting stochastic program will automatically be memory-distributed on these processes. This can be avoided by setting `procs = [1]`.
"""
function StochasticProgram(scenarios::Vector{<:AbstractScenario},
                           instantiation::StochasticInstantiation,
                           optimizer_constructor = nothing)
    stages = (Stage(nothing), Stage(nothing))
    return StochasticProgram(stages, (scenarios,), instantiation, optimizer_constructor)
end

function Base.copy(src::StochasticProgram{N}; instantiation = UnspecifiedInstantiation(), optimizer = nothing) where N
    stages = ntuple(Val(N)) do i
        Stage(stage_parameters(src, i))
    end
    scenario_types = ntuple(Val(N-1)) do i
        scenario_type(src, i+1)
    end
    dest = StochasticProgram(stages, scenario_types, instantiation, optimizer)
    merge!(dest.generator, src.generator)
    return dest
end

# Printing #
# ========================== #
function Base.show(io::IO, stochasticprogram::StochasticProgram{N}) where N
    plural(n) = (n == 1 ? "" : "s")
    stage(sp, s) = begin
        stage_key = Symbol(:stage_,s)
        ndecisions = num_decisions(sp, s)
        nscenarios = num_scenarios(sp, s)
        if s == 1
            if N == 2
                return " * $(ndecisions) decision variable$(plural(ndecisions))"
            else
                return " * Stage $s:\n   * $(ndecisions) decision variable$(plural(ndecisions))"
            end
        elseif s == 2 && N == 2
            stype = typename(scenario_type(stochasticprogram))
            return " * $(ndecisions) recourse variables\n * $(nscenarios) scenario$(plural(nscenarios)) of type $stype"
        else
            stype = typename(scenario_type(stochasticprogram, s))
            if distributed(stochasticprogram, s)
                if s == N
                    return " * Distributed stage $s:\n   * $(ndecisions) recourse variables\n * $(nscenarios) scenario$(plural(nscenarios)) of type $stype"
                else
                    return " * Distributed stage $s:\n   * $(ndecisions) decision variable$(plural(ndecisions))\n   * $(nscenarios) scenario$(plural(nscenarios)) of type $stype"
                end
            else
                if s == N
                    return " * Stage $s:\n   * $(ndecisions) recourse variables\n   * $(nscenarios) scenario$(plural(nscenarios)) of type $stype"
                else
                    return " * Stage $s:\n   * $(ndecisions) decision variable$(plural(ndecisions))\n   * $(nscenarios) scenario$(plural(nscenarios)) of type $stype"
                end
            end
        end
    end
    if deferred(stochasticprogram)
        n = num_scenarios(stochasticprogram)
        if n == 0
            return print(io, "Deferred stochastic program")
        else
            return print(io, "Deferred stochastic program with $n scenario$(plural(n))")
        end
    end
    if N == 2 && distributed(stochasticprogram)
        println(io, "Distributed stochastic program with:")
    else
        println(io, "Stochastic program with:")
    end
    print(io, stage(stochasticprogram, 1))
    for s = 2:N
        println(io,"")
        print(io, stage(stochasticprogram, s))
    end
    println(io,"")
    print(io, "Structure: ")
    print(io, structure_name(stochasticprogram))
    println(io,"")
    print(io, "Solver name: ")
    print(io, optimizer_name(stochasticprogram))
end
function Base.print(io::IO, stochasticprogram::StochasticProgram)
    if !deferred(stochasticprogram)
        # Delegate printing according to stochastic structure
        print(io, structure(stochasticprogram))
        print(io, "Solver name: ")
        print(io, optimizer_name(stochasticprogram))
    else
        # Just give summary if the stochastic program has not been initialized yet
        show(io, stochasticprogram)
    end
end
# ========================== #

# MOI #
# ========================== #
function MOI.get(stochasticprogram::StochasticProgram, attr::Union{MOI.TerminationStatus, MOI.PrimalStatus, MOI.DualStatus})
    # Check if there is a cached solution
    cache = solutioncache(stochasticprogram)
    if haskey(cache, :solution)
        # Returned cached solution if possible
        try
            return MOI.get(cache[:solution], attr)
        catch
        end
    end
    if haskey(cache, :node_solution_1)
        # Value was possibly only cached in first-stage solution
        try
            return MOI.get(cache[:node_solution_1], attr)
        catch
        end
    end
    return MOI.get(optimizer(stochasticprogram), attr)
end
function MOI.get(stochasticprogram::StochasticProgram, attr::MOI.AbstractModelAttribute)
    if MOI.is_set_by_optimize(attr)
        # Check if there is a cached solution
        cache = solutioncache(stochasticprogram)
        if haskey(cache, :solution)
            # Returned cached solution if possible
            try
                return MOI.get(cache[:solution], attr)
            catch
            end
        end
        if haskey(cache, :node_solution_1)
            # Value was possibly only cached in first-stage solution
            try
                return MOI.get(cache[:node_solution_1], attr)
            catch
            end
        end
        check_provided_optimizer(stochasticprogram.optimizer)
        if MOI.get(stochasticprogram, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
            throw(OptimizeNotCalled())
        end
        return MOI.get(optimizer(stochasticprogram), attr)
    else
        if is_structure_independent(attr)
            # Get attribute from first stage of proxy if structure independent
            return MOI.get(proxy(stochasticprogram, 1), attr)
        else
            # Handle in structure otherwise
            return MOI.get(structure(stochasticprogram), attr)
        end
    end
end
function MOI.get(stochasticprogram::StochasticProgram, attr::ScenarioDependentModelAttribute)
    if MOI.is_set_by_optimize(attr)
        # Check if there is a cached solution
        cache = solutioncache(stochasticprogram)
        key = Symbol(:node_solution_, attr.stage, :_, attr.scenario_index)
        if haskey(cache, key)
            try
                return MOI.get(cache[key], attr.attr)
            catch
            end
        end
        check_provided_optimizer(stochasticprogram.optimizer)
        # Return statuses without checks
        if typeof(attr.attr) <: Union{MOI.TerminationStatus, MOI.PrimalStatus, MOI.DualStatus}
            try
                # Try to get scenario-dependent value directly
                return MOI.get(optimizer(stochasticprogram), attr)
            catch
                # Fallback to resolving scenario-dependence in structure if
                # not supported natively by optimizer
                return MOI.get(structure(stochasticprogram), attr)
            end
        end
        if MOI.get(stochasticprogram, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
            throw(OptimizeNotCalled())
        end
        try
            # Try to get scenario-dependent value directly
            return MOI.get(optimizer(stochasticprogram), attr)
        catch
            # Fallback to resolving scenario-dependence in structure if
            # not supported natively by optimizer
            MOI.get(structure(stochasticprogram), attr)
        end
    else
        if is_structure_independent(attr)
            # Get attribute from first stage of proxy if structure independent
            return MOI.get(proxy(stochasticprogram, 1), attr)
        else
            # Handle in structure otherwise
            return MOI.get(structure(stochasticprogram), attr)
        end
    end
end
function MOI.get(stochasticprogram::StochasticProgram, attr::MOI.AbstractOptimizerAttribute)
    MOI.get(optimizer(stochasticprogram), attr)
end

function MOI.set(sp::StochasticProgram, attr::MOI.AbstractOptimizerAttribute, value)
    MOI.set(optimizer(sp), attr, value)
    return nothing
end
function MOI.set(sp::StochasticProgram, attr::MOI.AbstractModelAttribute, value)
    if is_structure_independent(attr)
        MOI.set(proxy(sp, attr.stage), attr, value)
    end
    MOI.set(structure(sp), attr, value)
    return nothing
end
function MOI.set(sp::StochasticProgram, attr::ScenarioDependentModelAttribute, value)
    if is_structure_independent(attr)
        MOI.set(proxy(sp, attr.stage), attr, value)
    end
    MOI.set(structure(sp), attr, value)
    return nothing
end

function MOI.set(sp::StochasticProgram, attr::MOI.Silent, flag)
    # Ensure that Silent is always passed
    MOI.set(structure(sp), attr, flag)
    # Pass to optimizer anyway
    MOI.set(optimizer(sp), attr, flag)
    return nothing
end

function JuMP.check_belongs_to_model(con_ref::ConstraintRef{<:StochasticProgram}, stochasticprogram::StochasticProgram)
    if owner_model(con_ref) !== model
        throw(ConstraintNotOwned(con_ref))
    end
end

Base.broadcastable(sp::StochasticProgram) = Ref(sp)


# This file is part of Kpax3. License is MIT.

#=
#R = [1; 1; 1; 1; 1; 1]
#R = [1; 1; 1; 1; 2; 2]
#R = [1; 1; 2; 2; 3; 3]
#R = [1; 3; 2; 2; 1; 4]
k = length(unique(R))

priorR = Kpax3.EwensPitman(settings.α, settings.θ)
priorC = Kpax3.AminoAcidPriorCol(data, settings.γ, settings.r)

state = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)

C = zeros(UInt8, n, m)

cs = ((UInt8[1], UInt8[2], UInt8[3], UInt8[4]),
      (UInt8[1; 1], UInt8[2; 2], UInt8[3; 3], UInt8[3; 4], UInt8[4; 3],
       UInt8[4; 4]),
      (UInt8[1; 1; 1], UInt8[2; 2; 2], UInt8[3; 3; 3], UInt8[3; 3; 4],
       UInt8[3; 4; 3], UInt8[4; 3; 3], UInt8[3; 4; 4], UInt8[4; 3; 4],
       UInt8[4; 4; 3], UInt8[4; 4; 4]),
      (UInt8[1; 1; 1; 1], UInt8[2; 2; 2; 2], UInt8[3; 3; 3; 3],
       UInt8[3; 3; 3; 4], UInt8[3; 3; 4; 3], UInt8[3; 4; 3; 3],
       UInt8[4; 3; 3; 3], UInt8[3; 3; 4; 4], UInt8[3; 4; 3; 4],
       UInt8[3; 4; 4; 3], UInt8[4; 3; 3; 4], UInt8[4; 3; 4; 3],
       UInt8[4; 4; 3; 3], UInt8[3; 4; 4; 4], UInt8[4; 3; 4; 4],
       UInt8[4; 4; 3; 4], UInt8[4; 4; 4; 3], UInt8[4; 4; 4; 4]),
      (UInt8[1; 1; 1; 1; 1], UInt8[2; 2; 2; 2; 2], UInt8[3; 3; 3; 3; 3],
       UInt8[3; 3; 3; 3; 4], UInt8[3; 3; 3; 4; 3], UInt8[3; 3; 4; 3; 3],
       UInt8[3; 4; 3; 3; 3], UInt8[4; 3; 3; 3; 3], UInt8[3; 3; 3; 4; 4],
       UInt8[3; 3; 4; 3; 4], UInt8[3; 3; 4; 4; 3], UInt8[3; 4; 3; 3; 4],
       UInt8[3; 4; 3; 4; 3], UInt8[3; 4; 4; 3; 3], UInt8[4; 3; 3; 3; 4],
       UInt8[4; 3; 3; 4; 3], UInt8[4; 3; 4; 3; 3], UInt8[4; 4; 3; 3; 3],
       UInt8[3; 3; 4; 4; 4], UInt8[3; 4; 3; 4; 4], UInt8[3; 4; 4; 3; 4],
       UInt8[3; 4; 4; 4; 3], UInt8[4; 3; 3; 4; 4], UInt8[4; 3; 4; 3; 4],
       UInt8[4; 4; 3; 3; 4], UInt8[4; 3; 4; 4; 3], UInt8[4; 4; 3; 4; 3],
       UInt8[4; 4; 4; 3; 3], UInt8[3; 4; 4; 4; 4], UInt8[4; 3; 4; 4; 4],
       UInt8[4; 4; 3; 4; 4], UInt8[4; 4; 4; 3; 4], UInt8[4; 4; 4; 4; 3],
       UInt8[4; 4; 4; 4; 4]),
      (UInt8[1; 1; 1; 1; 1; 1], UInt8[2; 2; 2; 2; 2; 2],
       UInt8[3; 3; 3; 3; 3; 3], UInt8[3; 3; 3; 3; 3; 4],
       UInt8[3; 3; 3; 3; 4; 3], UInt8[3; 3; 3; 4; 3; 3],
       UInt8[3; 3; 4; 3; 3; 3], UInt8[3; 4; 3; 3; 3; 3],
       UInt8[4; 3; 3; 3; 3; 3], UInt8[3; 3; 3; 3; 4; 4],
       UInt8[3; 3; 3; 4; 3; 4], UInt8[3; 3; 3; 4; 4; 3],
       UInt8[3; 3; 4; 3; 3; 4], UInt8[3; 3; 4; 3; 4; 3],
       UInt8[3; 3; 4; 4; 3; 3], UInt8[3; 4; 3; 3; 3; 4],
       UInt8[3; 4; 3; 3; 4; 3], UInt8[3; 4; 3; 4; 3; 3],
       UInt8[3; 4; 4; 3; 3; 3], UInt8[4; 3; 3; 3; 3; 4],
       UInt8[4; 3; 3; 3; 4; 3], UInt8[4; 3; 3; 4; 3; 3],
       UInt8[4; 3; 4; 3; 3; 3], UInt8[4; 4; 3; 3; 3; 3],
       UInt8[3; 3; 3; 4; 4; 4], UInt8[3; 3; 4; 3; 4; 4],
       UInt8[3; 3; 4; 4; 3; 4], UInt8[3; 3; 4; 4; 4; 3],
       UInt8[3; 4; 3; 3; 4; 4], UInt8[3; 4; 3; 4; 3; 4],
       UInt8[3; 4; 4; 3; 3; 4], UInt8[3; 4; 3; 4; 4; 3],
       UInt8[3; 4; 4; 3; 4; 3], UInt8[3; 4; 4; 4; 3; 3],
       UInt8[4; 3; 3; 3; 4; 4], UInt8[4; 3; 3; 4; 3; 4],
       UInt8[4; 3; 4; 3; 3; 4], UInt8[4; 4; 3; 3; 3; 4],
       UInt8[4; 3; 3; 4; 4; 3], UInt8[4; 3; 4; 3; 4; 3],
       UInt8[4; 4; 3; 3; 4; 3], UInt8[4; 3; 4; 4; 3; 3],
       UInt8[4; 4; 3; 4; 3; 3], UInt8[4; 4; 4; 3; 3; 3],
       UInt8[3; 3; 4; 4; 4; 4], UInt8[3; 4; 3; 4; 4; 4],
       UInt8[3; 4; 4; 3; 4; 4], UInt8[3; 4; 4; 4; 3; 4],
       UInt8[3; 4; 4; 4; 4; 3], UInt8[4; 3; 3; 4; 4; 4],
       UInt8[4; 3; 4; 3; 4; 4], UInt8[4; 3; 4; 4; 3; 4],
       UInt8[4; 3; 4; 4; 4; 3], UInt8[4; 4; 3; 3; 4; 4],
       UInt8[4; 4; 3; 4; 3; 4], UInt8[4; 4; 3; 4; 4; 3],
       UInt8[4; 4; 4; 3; 3; 4], UInt8[4; 4; 4; 3; 4; 3],
       UInt8[4; 4; 4; 4; 3; 3], UInt8[3; 4; 4; 4; 4; 4],
       UInt8[4; 3; 4; 4; 4; 4], UInt8[4; 4; 3; 4; 4; 4],
       UInt8[4; 4; 4; 3; 4; 4], UInt8[4; 4; 4; 4; 3; 4],
       UInt8[4; 4; 4; 4; 4; 3], UInt8[4; 4; 4; 4; 4; 4]))

logpnew = -Inf
logpold = -Inf
l = 0
for c1 in cs[state.k], c2 in cs[state.k], c3 in cs[state.k], c4 in cs[state.k]
  l += 1

  tmp = hcat(c1, c2, c3, c4)

  for l in 1:state.k
    state.C[state.cl[l], :] = tmp[l, :]
  end

  logpnew = Kpax3.logcondpostC(state.C, state.cl, state.k, state.v, state.n1s, priorC)

  if logpnew > logpold
    logpold = logpnew
    C[1:state.k, :] = copy(state.C[state.cl[1:state.k], :])
  end
end

C
=#

function test_local_mode()
  ifile = "data/read_proper_aa.fasta"
  ofile = "../build/test"

  settings = Kpax3.KSettings(ifile, ofile, gamma=[0.3; 0.35; 0.35])

  data = UInt8[1 1 1 1 0 0;
               0 0 1 1 0 0;
               1 0 0 0 1 1;
               1 0 1 0 1 0]

  (m, n) = size(data)

  priorR = Kpax3.EwensPitman(settings.α, settings.θ)
  priorC = Kpax3.AminoAcidPriorCol(data, settings.γ, settings.r)

  # k = 1
  R = [1; 1; 1; 1; 1; 1]
  k = 1
  C = zeros(UInt8, 6, 4)
  C[1, :] .= 0x01
  cl = [1; 0; 0; 0; 0; 0]
  v = [6; 0; 0; 0; 0; 0]
  n1s = zeros(Float64, 6, 4)
  n1s[1, :] .= vec(sum(float(data), dims=2))

  s = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(s.v, s.n1s, s.C, s.cl, s.k, s.logpC, priorC)

  @test s.C == C
  @test isapprox(s.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(s.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  t = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(t, priorC)

  @test t.C == C
  @test isapprox(t.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(t.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  # k = 2
  R = [1; 1; 1; 1; 2; 2]
  k = 2
  C = zeros(UInt8, 6, 4)
  C[1:2, :] = UInt8[4 1 2 1; 3 1 2 1]
  cl = [1; 2; 0; 0; 0; 0]
  v = [4; 2; 0; 0; 0; 0]
  n1s = zeros(Float64, 6, 4)
  n1s[1:2, :] .= permutedims(hcat(sum(float(data[:, R .== 1]), dims=2), sum(float(data[:, R .== 2]), dims=2)))

  s = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(s.v, s.n1s, s.C, s.cl, s.k, s.logpC, priorC)

  @test s.C == C
  @test isapprox(s.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(s.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  t = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(t, priorC)

  @test t.C == C
  @test isapprox(t.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(t.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  # k = 3
  R = [1; 1; 2; 2; 3; 3]
  k = 3
  C = zeros(UInt8, 6, 4)
  C[1:3, :] = UInt8[4 3 2 1; 4 4 2 1; 3 3 2 1]
  cl = [1; 2; 3; 0; 0; 0]
  v = [2; 2; 2; 0; 0; 0]
  n1s = zeros(Float64, 6, 4)
  n1s[1:3, :] .= permutedims(hcat(sum(float(data[:, R .== 1]), dims=2), sum(float(data[:, R .== 2]), dims=2), sum(float(data[:, R .== 3]), dims=2)))

  s = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(s.v, s.n1s, s.C, s.cl, s.k, s.logpC, priorC)

  @test s.C == C
  @test isapprox(s.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(s.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  t = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(t, priorC)

  @test t.C == C
  @test isapprox(t.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(t.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  # k = 4
  R = [1; 3; 2; 2; 1; 4]
  k = 4
  C = zeros(UInt8, 6, 4)
  C[1:4, :] = UInt8[1 3 4 2; 1 4 3 2; 1 3 3 2; 1 3 4 2]
  cl = [1; 2; 3; 4; 0; 0]
  v = [2; 2; 1; 1; 0; 0]
  n1s = zeros(Float64, 6, 4)
  n1s[1:4, :] .= permutedims(hcat(sum(float(data[:, R .== 1]), dims=2), sum(float(data[:, R .== 2]), dims=2), sum(float(data[:, R .== 3]), dims=2), sum(float(data[:, R .== 4]), dims=2)))

  s = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(s.v, s.n1s, s.C, s.cl, s.k, s.logpC, priorC)

  @test s.C == C
  @test isapprox(s.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(s.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  t = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(t, priorC)

  @test t.C == C
  @test isapprox(t.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(t.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  # k = 5
  R = [1; 2; 3; 3; 4; 5]
  k = 5
  C = zeros(UInt8, 6, 4)
  C[1:5, :] = UInt8[2 3 2 1; 2 3 2 1; 2 4 2 1; 2 3 2 1; 2 3 2 1]
  cl = [1; 2; 3; 4; 5; 0]
  v = [1; 1; 2; 1; 1; 0]
  n1s = zeros(Float64, 6, 4)
  n1s[1:5, :] .= permutedims(hcat(sum(float(data[:, R .== 1]), dims=2), sum(float(data[:, R .== 2]), dims=2), sum(float(data[:, R .== 3]), dims=2), sum(float(data[:, R .== 4]), dims=2), sum(float(data[:, R .== 5]), dims=2)))

  s = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(s.v, s.n1s, s.C, s.cl, s.k, s.logpC, priorC)

  @test s.C == C
  @test isapprox(s.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(s.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  t = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(t, priorC)

  @test t.C == C
  @test isapprox(t.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(t.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  # k = 6
  R = [1; 2; 3; 4; 5; 6]
  k = 6
  C = UInt8[1 1 2 2; 1 1 2 2 ; 1 1 2 2; 1 1 2 2; 1 1 2 2; 1 1 2 2]
  cl = [1; 2; 3; 4; 5; 6]
  v = [1; 1; 1; 1; 1; 1]
  n1s = permutedims(float(data))

  s = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(s.v, s.n1s, s.C, s.cl, s.k, s.logpC, priorC)

  @test s.C == C
  @test isapprox(s.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(s.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  t = Kpax3.AminoAcidState(data, R, priorR, priorC, settings)
  Kpax3.computelocalmode!(t, priorC)

  @test t.C == C
  @test isapprox(t.logpC[1], Kpax3.logpriorC(C, cl, k, priorC), atol=ε)
  @test isapprox(t.logpC[2], Kpax3.logcondpostC(C, cl, k, v, n1s, priorC), atol=ε)

  nothing
end

test_local_mode()


using Revise
using ComplexMixtures, PDBTools
const CM = ComplexMixtures

nthreads = Threads.nthreads()
if nthreads == 1
  error(" These tests have to be run with multiple threads as well. ")
end

dir="$(@__DIR__ )/data/NAMD"
atoms = readPDB("$dir/structure.pdb")
protein = Selection(select(atoms,"protein"),nmols=1)
tmao = Selection(select(atoms,"resname TMAO"),natomspermol=14)
water = Selection(select(atoms,"water"),natomspermol=3)

options_single = Options(stride=1,seed=321,nthreads=1,silent=true,lcell=2)
options_multi = Options(stride=1,seed=321,silent=true,lcell=2)

println(" --------------------------------------------------------------")
println(" Compiling - single thread ")
traj = Trajectory("$dir/trajectory.dcd",protein,tmao)
@time R = mddf(traj,options_single);
println(" Compiling - multi-threaded ")
traj = Trajectory("$dir/trajectory.dcd",protein,tmao)
@time R = mddf(traj,options_multi);
println(" Compiling - self single thread ")
traj = Trajectory("$dir/trajectory.dcd",tmao)
@time R = mddf(traj,options_single);
println(" Compiling - self multi-threaded ")
traj = Trajectory("$dir/trajectory.dcd",tmao)
@time R = mddf(traj,options_multi);

println(" --------------------------------------------------------------")
println(" Protein-TMAO - Single threaded ")
traj = Trajectory("$dir/trajectory.dcd",protein,tmao)
@time R = mddf(traj,options_single);

println(" --------------------------------------------------------------")
println(" Protein-TMAO - nthreads = $nthreads ")
traj = Trajectory("$dir/trajectory.dcd",protein,tmao)
@time R = mddf(traj,options_multi);

println(" --------------------------------------------------------------")
println(" Protein-Water - Single threaded ")
traj = Trajectory("$dir/trajectory.dcd",protein,water)
@time R = mddf(traj,options_single);

println(" --------------------------------------------------------------")
println(" Protein-Water - nthreads = $nthreads ")
traj = Trajectory("$dir/trajectory.dcd",protein,water)
@time R = mddf(traj,options_multi);

println(" --------------------------------------------------------------")
println(" Water-TMAO - Single threaded ")
traj = Trajectory("$dir/trajectory.dcd",water,tmao)
@time R = mddf(traj,options_single);

println(" --------------------------------------------------------------")
println(" Water-TMAO - nthreads = $nthreads ")
traj = Trajectory("$dir/trajectory.dcd",water,tmao)
@time R = mddf(traj,options_multi);

println(" --------------------------------------------------------------")
println(" Water-Water - Single threaded ")
traj = Trajectory("$dir/trajectory.dcd",water)
@time R = mddf(traj,options_single);

println(" --------------------------------------------------------------")
println(" Water-Water - nthreads = $nthreads ")
traj = Trajectory("$dir/trajectory.dcd",water)
@time R = mddf(traj,options_multi);
println(" --------------------------------------------------------------")


using Compose, Colors, CSV
using DataFrames

"""
    add_acquisition_points(imgpath::AbstractString, mime::AbstractString, x::Real, y::Real, fovx::Real, fovy::Real, pts::DataFrame)

A generic function for adding marker points to an image.  The image at `imgpath` of MIME-type `mime` centered at stage position `x`,`y`
with X and Y field-of-views `fovx`, `fovy` is annotated.  The point markers are placed at the coordinates pts[:,:X], pts[:,:Y].
"""
function add_acquisition_points(
    img::AbstractString,
    mime::AbstractString,
    x::Real,
    y::Real,
    fovx::Real,
    fovy::Real,
    pts::DataFrame,
)
    col = RGBA(colorant"yellow", 0.4) # Transparent yellow
    x0, y0 = x + 0.5 * fovx, y - 0.5 * fovy
    xx, yy = ((x0 .- pts[:, :X]) ./ fovx), (pts[:, :Y] .- y0) ./ fovy
    onimg =
        filter(i -> xx[i] > 0.0 && xx[i] < 1.0 && yy[i] > 0.0 && yy[i] < 1.0, eachindex(xx))
    compose(
        context(units = UnitBox(0.0, 0.0, 1.0, 1.0)),
        (
            context(),
            circle(xx[onimg], yy[onimg], [0.005]),
            Compose.stroke(col),
            linewidth(0.003w),
            fill("transparent"),
        ),
        (context(), bitmap(mime, read(img), 0.0, 0.0, 1.0, 1.0)),
    )
end

"""
    add_annotations(imgpath::AbstractString, modes::Symbol...; color = RGBA(colorant"yellow", 0.4), mime::AbstractString = "image/png")

Adds annotations to a SEMAntics image from the `images.txt` and `spectra.txt` meta-data files.

  Modes
  * `:ScaleBar`           Add a scale bar
  * `:AcquisitionPoints`  Mark all spectrum acquisition points.
"""
function add_annotations(
    imgpath::AbstractString,
    modes::Symbol...;
    color = RGBA(colorant"yellow", 0.4),
    mime::AbstractString = "image/png",
)
    imgdir, imgfn = splitdir(imgpath)
    df = CSV.read(joinpath(imgdir, "images.txt")) |> DataFrame
    img = splitext(imgfn)[1][1:end-3]
    rws = filter(r -> r[:Name] == img, df)
    if nrow(rws) > 0
        r = last(rws)
        aspect, fov = r[:YDim] / r[:XDim], r[:FOV]
        contexts = Any[context(units = UnitBox(0.0, 0.0, 1.0, aspect))]
        if :ScaleBar in modes
            sc = 10.0^floor(log10(fov))
            scale =
                fov / sc > 8.0 ? 5.0 * sc :
                (fov / sc > 4.0 ? 2.0 * sc : (fov / sc > 2.0 ? 1.0 * sc : 0.5 * sc))
            backcol = RGBA(0.8, 0.8, 0.8, 0.1)
            unit = (sc < 0.001 ? "nm" : (sc < 1.0 ? "μm" : "mm"))
            val = sc < 0.001 ? scale * 1.0e6 : (sc < 1.0 ? scale * 1.0e3 : scale)
            push!( # Scale bar
                contexts,
                (
                    context(),
                    rectangle(0.96 - scale / fov, 0.036, scale / fov, 0.006),
                    fill(color),
                ),
            )
            push!( # Text for scale bar
                contexts,
                (
                    context(),
                    text(0.96 - 0.5 * scale / fov, 0.06, "$val $unit", hcenter, vtop),
                    fontsize(0.04h),
                    font("Helvetica"),
                    fill(color),
                ),
            )
            push!( # Transparent gray background
                contexts,
                (
                    context(),
                    rectangle(0.954 - scale / fov, 0.03, scale / fov + 0.012, 0.07),
                    fill(backcol),
                ),
            )
        end
        if :AcquisitionPoints in modes
            x0, y0 = r[:X] + 0.5 * fov, r[:Y] - 0.5 * fov * aspect
            fovx, fovy = r[:FOV], aspect * r[:FOV]
            pts = CSV.read(joinpath(imgdir, "spectra.txt")) |> DataFrame
            xx, yy = ((x0 .- pts[:, :X]) ./ fovx), (pts[:, :Y] .- y0) ./ fovy
            onimg = filter(
                i -> xx[i] > 0.0 && xx[i] < 1.0 && yy[i] > 0.0 && yy[i] < 1.0,
                eachindex(xx),
            )
            push!( # X markers
                contexts,
                (
                    context(),
                    xgon(xx[onimg], yy[onimg], [0.02 * w], [4]),
                    stroke(color),
                    linewidth(0.003 * w),
                    fill("transparent"),
                ),
            )
        end
        # Image
        push!(contexts, (context(), bitmap(mime, read(imgpath), 0.0, 0.0, 1.0, aspect)))
        return compose(contexts...)
    end
end