# dirty hacks
get_number_jumps(model::ReactionSystem) = length(model.reactions)
get_number_jumps(model::InteractingParticleSystem) = length(model.reactions)
get_number_jumps(model::Vector{T}) where T = length(model)
get_number_species(state::Lattice) = length(state.types)
get_number_species(state::Vector{T}) where T = length(state)

abstract type SimulationAlgorithm end

"""
```
Direct()
```

Gillespie's Direct Method.

#### References

- Gillespie, D.T. (1976) A general method for numerically simulating the stochastic time evolution of coupled chemical reactions. *Journal of Computational Physics*. [https://doi.org/10.1016/0021-9991(76)90041-3](https://doi.org/10.1016/0021-9991(76)90041-3)

- Gillespie, D.T. (1977) Exact stochastic simulation of coupled chemical reactions. *The Journal of Physical Chemistry*. [https://doi.org/10.1021/j100540a008](https://doi.org/10.1021/j100540a008)
"""
struct Direct <: SimulationAlgorithm end

function build_simulator(::Direct, state, model, rates_cache)
  number_jumps = get_number_jumps(model)
  algorithm = DirectMethod{rates_cache}(zeros(number_jumps), 0.0)

  return ExactSimulator(algorithm)
end

"""
```
EnhancedDirect()
```

A Direct method that uses a dependency graph.

#### References

- Marchetti L., Priami C., Thanh V.H. (2017) Simulation Algorithms for Computational Systems Biology. *Texts in Theoretical Computer Science*.
"""
struct EnhancedDirect <: SimulationAlgorithm end

function build_simulator(::EnhancedDirect, state, model, rates_cache)
  number_jumps = get_number_jumps(model)
  algorithm = EnhancedDirectMethod{HasRates}(zeros(number_jumps), 0.0)

  return ExactSimulator(algorithm)
end

"""
```
SortingDirect()
```

The sorting direct method reorders reactions to identify frequently occurring events.
This effectively reduces the search depth in selecting the next reaction.

#### References

- McCollum, J.M., Peterson, G.D., Cox, C.D., Simpson, M.L., Samatova, N.F. (2006). The sorting direct method for stochastic simulation of biochemical systems with varying reaction execution behavior. *Computational Biology and Chemistry*. [https://doi.org/10.1016/j.compbiolchem.2005.10.007](https://doi.org/10.1016/j.compbiolchem.2005.10.007).
"""
struct SortingDirect <: SimulationAlgorithm end

function build_simulator(::SortingDirect, state, model, rates_cache)
  number_jumps = get_number_jumps(model)
  algorithm = SortingDirectMethod{HasRates}(zeros(number_jumps), 0.0, collect(1:number_jumps), 1)

  return ExactSimulator(algorithm)
end

"""
```
FirstReaction()
```

A first reaction method that samples reaction events and times jointly.

#### References

- Gillespie, D.T. (1976) A general method for numerically simulating the stochastic time evolution of coupled chemical reactions. *Journal of Computational Physics*. [https://doi.org/10.1016/0021-9991(76)90041-3](https://doi.org/10.1016/0021-9991(76)90041-3)
"""
struct FirstReaction <: SimulationAlgorithm end

function build_simulator(::FirstReaction, state, model, rates_cache)
  number_jumps = get_number_jumps(model)
  algorithm = FirstReactionMethod(zeros(number_jumps), 0.0)

  return ExactSimulator(algorithm)
end

"""
```
NextReaction()
```

Gibson and Bruck's next reaction method.
Uses a priority queue to select the next reaction event and time.

#### References

- Gibson, M.A., Bruck, J. (1999) Efficient exact stochastic simulation of chemical systems with many species and many channels. *Journal of Physical Chemistry*. [https://doi.org/10.1021/jp993732q](https://doi.org/10.1021/jp993732q)
"""
struct NextReaction <: SimulationAlgorithm end

function build_simulator(::NextReaction, state, model, rates_cache)
  number_jumps = get_number_jumps(model)
  priority_queue = PQBinaryHeap{Int,Float64,ForwardOrdering}(collect(1:number_jumps), zeros(number_jumps))
  algorithm = NextReactionMethod(zeros(number_jumps), 0.0, priority_queue)

  return ExactSimulator(algorithm)
end

"""
```
RejectionSSA()
```

A rejection-based algorithm that operates on "virtual states" to accelerate simulation.

WARNING: This *implementation* is a work in progress.

#### References

- Thahn, V.H., Priami, C., Zunino, R. (2014) Efficient rejection-based simulation of biochemical reactions with stochastic noise and delays. *Journal of Chemical Physics*. [https://doi.org/10.1063/1.4896985](https://doi.org/10.1063/1.4896985)
"""
struct RejectionSSA <: SimulationAlgorithm end

function build_simulator(::RejectionSSA, state, model, rates_cache)
  number_species = length(state)
  number_jumps = get_number_jumps(model)

  interval_lo = Vector{Int}(undef, number_species)
  interval_hi = Vector{Int}(undef, number_species)
  rates = [Vector{Float64}(undef, 2) for j in 1:number_jumps]
  total_rate = Vector{Float64}(undef, 2)
  spc_dg = model.spc_graph
  rxn_dg = model.rxn_graph

  algorithm = RejectionMethod(state, model, interval_lo, interval_hi, rates, total_rate, spc_dg, rxn_dg)

  return ExactSimulator(algorithm)
end

##### tau-leaping

"""
```
TauLeapingDG2001()
```

A pure tau-leaping method using the Equation (26a) to select tau.

#### Optional Arguments

Work in progress.

#### References

- Gillespie, D.T. (2001) Approximate accelerated stochastic simulation of chemically reacting systems. *Journal of Chemical Physics*. [https://doi.org/10.1063/1.1378322](https://doi.org/10.1063/1.1378322)
"""
struct TauLeapingDG2001 <: SimulationAlgorithm end

function build_simulator(::TauLeapingDG2001, state, model, rates_cache)
  number_species = length(state)
  number_jumps = get_number_jumps(model)

  rates = zeros(number_jumps)
  total_rate = zero(eltype(rates))
  leap_formula = DG2001Eq26a(number_species, number_jumps, 0.125)

  # build closure to apply leap updates
  V = extract_net_stoichiometry(model)
  execute_leap! = ApplyLeapUpdate(forward_leap!, V)  # apply a leap update
  reverse_leap! = ApplyLeapUpdate(backward_leap!, V) # reverse a leap update

  # build closure that ensures leaps are valid
  rejection_threshold = 0.75
  proposal = copy(state)
  validate_leap! = RejectionThinning(rejection_threshold, proposal, execute_leap!, reverse_leap!)

  algorithm = PoissonLeapMethod(rates, total_rate, V, leap_formula, validate_leap!)

  return TauLeapSimulator(algorithm, number_jumps, execute_leap!)
end

"""
```
TauLeapingDGLP2003()
```

A pure tau-leaping method using the Equation (6) to select tau.

#### Optional Arguments

Work in progress.

#### References

- Gillespie, D.T., Petzold, L.R. (2003) Improved leap-size selection for accelerated stochastic simulation. *Journal of Chemical Physics*. [https://doi.org/10.1063/1.1613254](https://doi.org/10.1063/1.1613254)
"""
struct TauLeapingDGLP2003 <: SimulationAlgorithm end

function build_simulator(::TauLeapingDGLP2003, state, model, rates_cache)
  number_species = length(state)
  number_jumps = get_number_jumps(model)

  rates = zeros(number_jumps)
  total_rate = zero(eltype(rates))

  # extract stoichiometry
  V = extract_net_stoichiometry(model)

  # build leap formula
  leap_formula = DGLP2003Eq6(number_species, number_jumps, V, 0.125)

  # build closure to apply leap updates
  execute_leap! = ApplyLeapUpdate(forward_leap!, V)  # apply a leap update
  reverse_leap! = ApplyLeapUpdate(backward_leap!, V) # reverse a leap update

  # build closure that ensures leaps are valid
  rejection_threshold = 0.75
  proposal = copy(state)
  validate_leap! = RejectionThinning(rejection_threshold, proposal, execute_leap!, reverse_leap!)

  algorithm = PoissonLeapMethod(rates, total_rate, V, leap_formula, validate_leap!)

  return TauLeapSimulator(algorithm, number_jumps, execute_leap!)
end

"""
```
StepAnticipation()
```

A pure tau-leaping method using the Equation (15) to select tau.

#### Optional Arguments

Work in progress.

#### References

- Sehl, M.E., Alekseyenko, A.L., Lange, K.L. (2009) Accurate stochastic simulation via the step anticipation tau-leaping (SAL) algorithm. *Journal of Computational Biology*. [https://dx.doi.org/10.1089/cmb.2008.0249](https://dx.doi.org/10.1089/cmb.2008.0249)
"""
struct StepAnticipation <: SimulationAlgorithm end

function build_simulator(::StepAnticipation, state, model, rates_cache)
  number_species = length(state)
  number_jumps = get_number_jumps(model)

  rates = zeros(number_jumps)
  total_rate = zero(eltype(rates))

  # extract stoichiometry
  U = extract_coefficients(model)
  V = extract_net_stoichiometry(model)

  # allocate memory for derivatives
  dxdt = zeros(number_species)
  drdt = zeros(number_jumps)

  # build leap formula
  k = model.rxn_rates # note: this needs to change in the future!
  leap_formula = GenericLeapFormula(k, U, V, dxdt, drdt, 0.125)

  # build closure to apply leap updates
  execute_leap! = ApplyLeapUpdate(forward_leap!, V)  # apply a leap update
  reverse_leap! = ApplyLeapUpdate(backward_leap!, V) # reverse a leap update

  # build closure that ensures leaps are valid
  rejection_threshold = 0.75
  proposal = copy(state)
  validate_leap! = RejectionThinning(rejection_threshold, proposal, execute_leap!, reverse_leap!)

  algorithm = StepAnticipationMethod(rates, total_rate, leap_formula, validate_leap!, U, V, dxdt, drdt)

  return TauLeapSimulator(algorithm, number_jumps, execute_leap!)
end

"""
```
HybridSAL()
```

Same as `StepAnticipation()`, but defaults to `Direct()` depending on the cumulative intensity.

#### Optional Arguments

Work in progress.

#### References

- Sehl, M.E., Alekseyenko, A.L., Lange, K.L. (2009) Accurate stochastic simulation via the step anticipation tau-leaping (SAL) algorithm. *Journal of Computational Biology*. [https://dx.doi.org/10.1089/cmb.2008.0249](https://dx.doi.org/10.1089/cmb.2008.0249)
"""
struct HybridSAL <: SimulationAlgorithm end

function build_simulator(::HybridSAL, state, model, rates_cache)
  # shared information
  number_species = length(state)
  number_jumps = get_number_jumps(model)

  rates = zeros(number_jumps)
  total_rate = zero(eltype(rates))

  # exact simulator...
  algorithm = DirectMethod{rates_cache}(rates, 0.0)
  exact = ExactSimulator(algorithm)

  # tau-leaping simulator...

  # extract stoichiometry
  U = extract_coefficients(model)
  V = extract_net_stoichiometry(model)

  # allocate memory for derivatives
  dxdt = zeros(number_species)
  drdt = zeros(number_jumps)

  # build leap formula
  k = model.rxn_rates # note: this needs to change in the future!
  leap_formula = GenericLeapFormula(k, U, V, dxdt, drdt, 0.125)

  # build closure to apply leap updates
  execute_leap! = ApplyLeapUpdate(forward_leap!, V)  # apply a leap update
  reverse_leap! = ApplyLeapUpdate(backward_leap!, V) # reverse a leap update

  # build closure that ensures leaps are valid
  rejection_threshold = 0.75
  proposal = copy(state)
  validate_leap! = RejectionThinning(rejection_threshold, proposal, execute_leap!, reverse_leap!)

  algorithm = StepAnticipationMethod(rates, total_rate, leap_formula, validate_leap!, U, V, dxdt, drdt)

  tauleap = TauLeapSimulator(algorithm, number_jumps, execute_leap!)

  return HybridTauLeapSimulator(exact, tauleap)
end


# EXPERIMENTAL - Threaded version

import Base.Threads.@spawn

# Threading not possible in this case
corkendallthreads_v4(x::Union{RealVector,RealOrMissingVector}, y::Union{RealVector,RealOrMissingVector}) = corkendall(x, y)

function corkendallthreads_v4(X::Union{RealMatrix,RealOrMissingMatrix}, Y::Union{RealMatrix,RealOrMissingMatrix})
    nr = size(X, 2)
    nc = size(Y, 2)

    #speedup provided by threading is greater when the second argument has more columns, so switch.
    #TODO test that hypothesis more thoroughly!
    if nr > nc
        return(convert(RealMatrix, transpose(corkendallthreads_v4(Y, X))))
    end

    C = zeros(Float64, nr, nc)
    chunks = partitioncols(nc, false)

    tasks = Array{Task,1}(undef, length(chunks))

    for j = 1:length(chunks)
        tasks[j] = @spawn corkendall(X, Y[:,chunks[j]])
    end

    for (c,t) in zip(chunks,tasks)
        C[:,c] = fetch(t)
    end

    return C
end

function corkendallthreads_v4(X::Union{RealMatrix,RealOrMissingMatrix})
    nr = nc = size(X, 2)
    C = zeros(Float64, nr, nc)

    chunks = partitioncols(nc, true,max(Threads.nthreads(),nc/100))

    tasks = Array{Task,1}(undef, length(chunks))
    for j = 1:length(chunks)
        tasks[j] = @spawn ck_belowdiagonal(X, chunks[j])
    end

    for (c,t) in zip(chunks,tasks)
        C[:,c] = fetch(t)
    end

    for j = 1:nr
        C[j,j] = 1.0
        for i = (1 + j):nr
            C[j,i] = C[i,j]
        end
    end

    return C
end

function corkendallthreads_v4(x::Union{RealVector,RealOrMissingVector}, Y::Union{RealMatrix,RealOrMissingMatrix})
    nr = 1
    nc = size(Y, 2)

    C = zeros(Float64, nr, nc)

    chunks = partitioncols(nc, false,max(8,nc/20))

    tasks = Array{Task,1}(undef, length(chunks))
    for j = 1:length(chunks)
        tasks[j] = @spawn corkendall(x, view(Y,:,chunks[j]))
    end

    for (c,t) in zip(chunks,tasks)
        C[:,c] = fetch(t)
    end

    return C
end

function corkendallthreads_v4(X::Union{RealMatrix,RealOrMissingMatrix}, y::Union{RealVector,RealOrMissingVector})
    l = size(X, 2)
    #it is idiosyncratic that this method returns a vector, not a matrix.
    C = zeros(Float64, l)

    chunks = partitioncols(l, false)

    tasks = Array{Task,1}(undef, length(chunks))
    for j = 1:length(chunks)
        tasks[j] = @spawn corkendall(X[:,chunks[j]], y)
    end

    for (c,t) in zip(chunks,tasks)
        C[c] = fetch(t)
    end

    return C
end

"""
    partitioncols(nc::Int64, triangular::Bool)
Auxiliary function for task load balancing. Returns a vector of `UnitRange`s, which partition the columns of the
correlation matrix to be calculated, one "chunk" per task.
# Arguments
- `nc::Int64`: the number of columns in the correlation matrix.
- `triangular::Bool`: should be `true` when calculating below-the-diagonal elements of a correlation matrix. In this
case the partitions have (approximately) equal numbers of elements below the diagonal, so early partitions are narrower
than later partitions.
- `numtasks`: the number of tasks (or chunks) to partition to. The length of the return from the function
is `min(nc,numtasks)`.

# Examples
```
julia> length.(KendallTau.partitioncols(10,false,4))
4-element Vector{Int64}:
 3
 3
 2
 2

julia> length.(KendallTau.partitioncols(100,true,4))
4-element Vector{Int64}:
 14
 16
 21
 48
```
"""
function partitioncols(nc::Int64, triangular::Bool, numtasks=Threads.nthreads())

    chunks = Vector{UnitRange{Int64}}(undef, 0)
    if triangular
        lastcol = nc - 1
        numcorrels = nc * (nc - 1) ÷ 2
    else
        lastcol = nc
        numcorrels = nc * nc
    end

    correlsdone = tasksdone = 0
    starttask = true
    chunkfirstcol = 0
    target = 0.0
    for i = 1:lastcol
        if starttask
            chunkfirstcol = i
            target = correlsdone + (numcorrels - correlsdone) / (numtasks - tasksdone)
        end
        correlsdone += triangular ? (nc - i) : nc
        if correlsdone >= target || i == lastcol
            push!(chunks, chunkfirstcol:i)
            starttask = true
            tasksdone += 1
        else
            starttask = false
        end
    end

    return(chunks)
end

"""
    ck_belowdiagonal(X::Union{RealMatrix,RealOrMissingMatrix}, colnos::UnitRange{Int64})
For use from multi-threading code to avoid double calculation of elements. Returns corkendall(X)[:,colnos] but with NaNs
on and above the diagonal of corkendall(X).
"""
function ck_belowdiagonal(X::Union{RealMatrix,RealOrMissingMatrix}, colnos::UnitRange{Int64})
    nr = size(X, 2)
    nc = length(colnos)
    C = Matrix{Float64}(undef, nr, nc)
    for j = 1:nr
        permx = sortperm(X[:,j])
        sortedx = X[:,j][permx]
        for i = 1:nc
            if j > i + colnos[1] - 1
                C[j,i] = ck_sorted!(sortedx, X[:,colnos[i]], permx)
            else
                C[j,i] = NaN
            end
        end
    end
    return C
end


test_image_results = [0, 1, 1, 4, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 2, 2, 2]

# Black pixels
test_image_1 = [
0 0 0 0 0 ;
0 0 0 0 0 ;
0 0 0 0 0 ;
0 0 0 0 0 ;
0 0 0 0 0]

# White pixels
test_image_2 = [
1 1 1 1 1 ;
1 1 1 1 1 ;
1 1 1 1 1 ;
1 1 1 1 1 ;
1 1 1 1 1]

# Single pixel
test_image_3 = [
0 0 0 0 0 ;
0 0 0 0 0 ;
0 0 1 0 0 ;
0 0 0 0 0 ;
0 0 0 0 0]

# Multiple single pixels
test_image_4 = [
0 0 0 0 0 ;
0 1 0 1 0 ;
0 0 0 0 0 ;
0 1 0 1 0 ;
0 0 0 0 0]

# Checkerboard 1
test_image_5 = [
1 0 1 0 1 ;
0 1 0 1 0 ;
1 0 1 0 1 ;
0 1 0 1 0 ;
1 0 1 0 1]

# Checkerboard 2
test_image_6 = [
0 1 0 1 0 ;
1 0 1 0 1 ;
0 1 0 1 0 ;
1 0 1 0 1 ;
0 1 0 1 0]

# External contour
test_image_7 = [
0 0 0 0 0 ;
0 1 1 1 0 ;
0 1 0 1 0 ;
0 1 1 1 0 ;
0 0 0 0 0]

# Cut-off external contour
test_image_8 = [
0 0 0 0 0 ;
0 1 1 1 0 ;
0 1 0 1 0 ;
0 1 0 1 0 ;
0 1 0 1 0]

# Multiple external contour
test_image_9 = [
0 0 0 0 0 ;
1 1 1 1 0 ;
0 0 0 0 0 ;
0 1 1 1 1 ;
0 0 0 0 0]

# Single pixel internal contour
test_image_10 = [
1 0 1 0 1 ;
1 1 1 1 1 ;
1 1 0 1 1 ;
1 1 1 1 1 ;
0 1 1 1 0]

# Internal contour
test_image_11 = [
1 0 1 0 1 ;
1 1 1 1 1 ;
1 0 0 0 1 ;
1 1 1 1 1 ;
0 1 1 1 0]

# Multiple internal contours
test_image_12 = [
1 0 1 0 1 ;
1 1 1 1 1 ;
1 0 1 0 1 ;
1 1 1 1 1 ;
0 1 0 1 0]

# Labelled internal contours
test_image_13 = [
1 1 1 1 1 ;
1 0 0 1 1 ;
1 0 0 0 1 ;
1 1 0 1 1 ;
1 1 1 1 1]

# Unlabelled internal contours
test_image_14 = [
1 1 1 1 1 ;
1 1 1 1 1 ;
1 0 0 1 1 ;
1 0 0 1 1 ;
1 1 1 1 1]

# No dummy row
test_image_15 = [
0 0 0 0 0 ;
1 1 1 1 1 ;
1 0 1 0 1 ;
1 0 1 0 1 ;
1 1 1 1 1]

# Multiple external and internal contours 1
test_image_16 = [
0 0 0 0 0 0 0 0 0 0;
0 1 1 1 1 1 1 1 1 0;
0 1 0 0 0 0 0 0 1 0;
0 1 0 1 1 1 1 1 1 0;
0 1 1 1 0 0 0 0 0 0;
0 0 0 0 0 1 1 1 1 0;
0 1 1 1 0 1 0 0 1 0;
0 1 0 1 0 1 0 0 1 0;
0 1 1 1 0 1 1 1 1 0;
0 0 0 0 0 0 0 0 0 0]

# Multiple external and internal contours 2
test_image_17 = [
0 0 0 0 0 0 0 0 0 0;
0 1 1 1 1 1 1 1 1 0;
0 1 0 0 1 1 0 0 1 0;
0 1 0 0 0 0 1 0 1 0;
0 1 1 0 0 1 0 0 1 0;
0 1 1 1 1 0 0 1 0 0;
0 0 0 0 0 0 0 0 1 0;
0 1 0 0 0 0 1 1 1 0;
0 1 1 1 1 1 1 1 0 0;
0 0 0 0 0 0 0 0 0 0]

# Multiple external and internal contours 3
test_image_18 = [
0 0 0 0 0 0 0 0 0 0;
0 0 1 1 1 1 1 0 0 0;
0 1 0 1 0 1 1 1 0 0;
0 1 0 0 1 0 0 0 0 0;
0 1 0 0 0 0 0 0 0 0;
0 1 1 1 1 0 0 0 0 0;
0 0 1 0 0 0 1 1 0 0;
0 0 0 0 0 1 0 0 1 0;
0 0 0 0 0 1 0 0 1 0;
0 0 0 0 0 0 1 0 1 0]

# Multiple external and internal contours 4
test_image_19 = [
0 0 0 0 1 1 0 0 0 0;
0 0 0 1 0 0 1 0 0 0;
0 0 1 0 0 0 0 1 0 0;
0 1 0 0 1 1 0 0 1 0;
1 0 0 1 0 0 1 0 0 1;
1 0 0 1 0 0 1 0 0 1;
0 1 0 1 0 0 1 0 1 0;
0 1 0 0 1 1 0 0 1 0;
0 0 1 0 0 0 0 1 0 0;
0 0 0 1 1 1 1 0 0 0]

# Multiple external and internal contours 5
test_image_20 = [
0 1 0 0 0 0 0 0 1 0;
0 1 1 1 1 1 1 1 1 0;
0 1 0 0 1 0 0 0 1 0;
0 1 0 0 0 1 0 0 1 0;
0 1 0 1 0 0 1 1 1 0;
0 1 0 0 1 0 0 0 1 0;
0 1 0 1 0 1 0 0 1 0;
0 1 0 1 0 0 1 0 1 0;
0 1 0 0 1 1 1 0 1 0;
0 1 0 0 1 0 0 0 1 0]


using Underscores
using Utils

const Mappings = Dict{String,String}

function parse_file(f)
    @_ split_blocks(f) |> map(Dict(split(item, ':') for item=split(_)), __)
end

function load()
    open(joinpath(@__DIR__, "04_passports.txt"), "r") do f
        parse_file(f)
    end
end

function isvalid(d)
    isempty(setdiff(Set(["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]), keys(d)))
end

colors = Set(["amb", "blu", "brn", "gry", "grn", "hzl", "oth"])

function fd(s, fromto)
    parsed = tryparse(UInt, s)
    if isnothing(parsed)
        false
    else
        parsed in fromto
    end
end

function isvalid_strict(d)
    try
        fd(d["byr"], 1920:2020) &&
            fd(d["iyr"], 2010:2020) &&
            fd(d["eyr"], 2020:2030) &&
            d["ecl"] in colors &&
            occursin(r"^#[0-9a-f]{6}$", d["hcl"]) &&
            occursin(r"^\d{9}$", d["pid"]) &&
            occursin(r"^(1([5-8]\d|9[0-3])cm|(59|6\d|7[0-6])in)$", d["hgt"])
    catch KeyError
        false
    end
end

function count_valid(isvalid, passports)
    @_ sum(isvalid(_) ? 1 : 0, passports)
end

passports = load()

println("Number of          valid passports: ", count_valid(isvalid, passports))
println("Number of striclty valid passports: ", count_valid(isvalid_strict, passports))

using Test

@testset "Passport Processing" begin
    @testset "example 1" begin
        example_input = """
            ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
            byr:1937 iyr:2017 cid:147 hgt:183cm

            iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
            hcl:#cfa07d byr:1929

            hcl:#ae17e1 iyr:2013
            eyr:2024
            ecl:brn pid:760753108 byr:1931
            hgt:179cm

            hcl:#cfa07d eyr:2025 pid:166559648
            iyr:2011 ecl:brn hgt:59in
            """
        expected = [
            Dict(
                "ecl" => "gry",
                "pid" => "860033327",
                "eyr" => "2020",
                "hcl" => "#fffffd",
                "byr" => "1937",
                "iyr" => "2017",
                "cid" => "147",
                "hgt" => "183cm",
            ),
            Dict(
                "iyr" => "2013",
                "ecl" => "amb",
                "cid" => "350",
                "eyr" => "2023",
                "pid" => "028048884",
                "hcl" => "#cfa07d",
                "byr" => "1929",
            ),
            Dict(
                "hcl" => "#ae17e1",
                "iyr" => "2013",
                "eyr" => "2024",
                "ecl" => "brn",
                "pid" => "760753108",
                "byr" => "1931",
                "hgt" => "179cm",
            ),
            Dict(
                "hcl" => "#cfa07d",
                "eyr" => "2025",
                "pid" => "166559648",
                "iyr" => "2011",
                "ecl" => "brn",
                "hgt" => "59in",
            ),
        ]

        example = parse_file(IOBuffer(example_input))

        @test example == expected

        @test isvalid(example[1])
        @test !isvalid(example[2])
        @test isvalid(example[3])
        @test !isvalid(example[4])
    end

    @testset "example 2 valid" begin
        passports = parse_file(IOBuffer("""
            pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980
            hcl:#623a2f

            eyr:2029 ecl:blu cid:129 byr:1989
            iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm

            hcl:#888785
            hgt:164cm byr:2001 iyr:2015 cid:88
            pid:545766238 ecl:hzl
            eyr:2022

            iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719
            """))

        @test (@_ all(isvalid_strict(_), passports))
    end

    @testset "example 2 invalid" begin
        passports = parse_file(IOBuffer("""
            eyr:1972 cid:100
            hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926

            iyr:2019
            hcl:#602927 eyr:1967 hgt:170cm
            ecl:grn pid:012533040 byr:1946

            hcl:dab227 iyr:2012
            ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277

            hgt:59cm ecl:zzz
            eyr:2038 hcl:74454a iyr:2023
            pid:3556412378 byr:2007
            """))

        @test !(@_ any(isvalid_strict(_), passports))
    end

    @testset "TDD" begin
        first = passports[1]
        @test length(first) == 6
    end

    @testset "results" begin
        @test count_valid(isvalid, passports) == 222
        @test count_valid(isvalid_strict, passports) == 140
    end
end


function make_grids_for_chunklist_timeseries(clt::ACLT; oversample_factor::Real = 1 ) where { ACLT<:AbstractChunkListTimeseries }
    map(i->RvSpectMLBase.make_grid_for_chunk(clt,i,oversample_factor=oversample_factor), 1:num_chunks(clt))
end

# Wrapper code to deal with weird data structures
function pack_chunk_list_timeseries_to_matrix(timeseries::ACLT, chunk_grids::Union{AR,AAV}; alg::Symbol = :TemporalGP,
    oversample_factor::Real = 1, smooth_factor::Real=1, verbose::Bool = false ) where {
        ACLT<:AbstractChunkListTimeseries, RT<:AbstractRange, AR<:AbstractArray{RT,1}, T<:Real, AV<:AbstractVector{T}, AAV<:AbstractArray{AV,1} }
    @assert alg == :Linear || alg == :GP  || alg == :Sinc || alg == :TemporalGP # TODO: Eventually move to traits-based system?
    num_obs = length(timeseries)
    num_λ = sum(length.(chunk_grids))
    flux_matrix = Array{Float64,2}(undef,num_λ,num_obs)
    var_matrix = Array{Float64,2}(undef,num_λ,num_obs)
    λ_vec = Array{Float64,1}(undef,num_λ)
    chunk_map = Array{UnitRange{Int64}}(undef,length(chunk_grids))
    mean_flux = zeros(num_λ)
    mean_var = zeros(num_λ)
    dfluxdlnλ = zeros(num_λ)
    d2fluxdlnλ2 = zeros(num_λ)
    if (alg == :GP) && (maximum(length.(chunk_grids))>1024)
        @error "To use a GP with more than 1024 points in a chunk, use a more efficient GP package, e.g., :TemporalGP."
    end
    if alg == :Sinc    # Setup workspace for Sync.  TODO: Put into functor
        filter_size=23
        kaiserB=13
        sinc_filter = SincInterpolation.create_filter_curve(filter_size*21; filter_size=filter_size, kaiserB=kaiserB)
    end

    for t in 1:num_obs
       idx_start = 0
       for c in 1:length(chunk_grids)
           idx = (idx_start+1):(idx_start+length(chunk_grids[c]))
           if verbose && !issorted(timeseries.chunk_list[t].data[c].λ)
               flush(stdout);  println("t= ",t, " c= ",c," idx= ", idx, " size(flux)= ",size(flux_matrix), " issorted(λ)=", issorted(timeseries.chunk_list[t].data[c].λ))
           end
           if t == 1
               λ_vec[idx] .= chunk_grids[c]
               chunk_map[c] = idx
           end
           mean_flux_in_chunk = mean(timeseries.chunk_list[t].data[c].flux)
           if mean_flux_in_chunk > 0
               if alg == :Linear
                   LinearInterpolation.interp_chunk_to_grid_linear!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c])
               elseif alg == :Sinc
                   SincInterpolation.interp_chunk_to_grid_sinc!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c], Filter=sinc_filter)
               elseif alg == :GP
                   GPInterpolation.interp_chunk_to_grid_gp!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c])
               elseif alg == :TemporalGP
                   RvSpectML.TemporalGPInterpolation.interp_chunk_to_grid_gp_temporal!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c] , use_logx=true, use_logy=false, smooth_factor=smooth_factor)
               end
               mean_flux[idx] .+= flux_matrix[idx,t]
               mean_var[idx]  .+= var_matrix[idx,t]
               dfluxdlnλ[idx] .+= calc_dfluxdlnlambda(view(flux_matrix,idx,t),view(λ_vec,idx))
               d2fluxdlnλ2[idx] .+= calc_d2fluxdlnlambda2(view(flux_matrix,idx,t),view(λ_vec,idx))

               flux_matrix[idx,t] ./= mean_flux_in_chunk
               var_matrix[idx,t]  ./= mean_flux_in_chunk^2
           else  # no flux in chunk?!?
               @warn "No flux in chunk " * string(c) * " at time " * string(t) * "."
           end
           idx_start += length(chunk_grids[c])
       end # for c
   end # for t

    # TODO Think if can propagate uncertainties better
    var_matrix .*= sqrt(oversample_factor)

    #flush(stdout)
    #println("# mean flux pre-normalize = ", mean(mean_flux))
    idx_start = 0
    for c in 1:length(chunk_grids)
        idx = (idx_start+1):(idx_start+length(chunk_grids[c]))
        mean_flux_in_chunk = mean(mean_flux[idx])
        #println("Normalizing chunk ", c, " (",idx,") by ", mean_flux_in_chunk)
        #dmeanfluxdlnλ[idx] .= calc_dfluxdlnlambda(vec(sum(view(flux_matrix,idx,:),dims=2)),vec(sum(view(var_matrix,idx,:), dims=2)) ) ./mean_flux_in_chunk
        mean_flux[idx] ./= mean_flux_in_chunk
        mean_var[idx] ./= mean_flux_in_chunk^2
        #dfluxdlnλ[idx] ./= mean_flux_in_chunk
        #d2fluxdlnλ2[idx] ./= mean_flux_in_chunk
        dfluxdlnλ[idx] .= calc_dfluxdlnlambda(mean_flux[idx],view(λ_vec,idx))
        d2fluxdlnλ2[idx] .= calc_d2fluxdlnlambda2(mean_flux[idx],view(λ_vec,idx))

        #dmeanfluxdlnλ[idx] .= calc_dfluxdlnlambda(vec(sum(view(flux_matrix,idx,:),dims=2)),view(λ_vec,idx))  ./mean_flux_in_chunk
        idx_start += length(chunk_grids[c])
    end
    #println("# mean flux post-normalize = ", mean(mean_flux))
    #flush(stdout)
    #mean_flux /= num_obs
    mean_var .*= sqrt(oversample_factor)
    #dmeanfluxdlnλ ./= num_obs
    return ( matrix=SpectralTimeSeriesCommonWavelengths(λ_vec,flux_matrix,var_matrix,chunk_map, TheoreticalInstrument1D() ), mean_flux=mean_flux, mean_var=mean_var, deriv=dfluxdlnλ, deriv2=d2fluxdlnλ2 )
end

# Wrapper code to deal with weird data structures
function pack_shifted_chunk_list_timeseries_to_matrix(timeseries::ACLT, chunk_grids::Union{AR,AAV}; alg::Symbol = :TemporalGP,
    oversample_factor::Real = 1, smooth_factor::Real=1, remove_rv_est::Bool = true, verbose::Bool = false ) where {
        ACLT<:AbstractChunkListTimeseries, RT<:AbstractRange, AR<:AbstractArray{RT,1}, T<:Real, AV<:AbstractVector{T}, AAV<:AbstractArray{AV,1} }
    @assert alg == :Linear || alg == :GP  || alg == :Sinc || alg == :TemporalGP # TODO: Eventually move to traits-based system?
    num_obs = length(timeseries)
    num_λ = sum(length.(chunk_grids))
    flux_matrix = Array{Float64,2}(undef,num_λ,num_obs)
    var_matrix = Array{Float64,2}(undef,num_λ,num_obs)
    λ_vec = Array{Float64,1}(undef,num_λ)
    chunk_map = Array{UnitRange{Int64}}(undef,length(chunk_grids))
    mean_flux = zeros(num_λ)
    mean_var = zeros(num_λ)
    dfluxdlnλ = zeros(num_λ)
    d2fluxdlnλ2 = zeros(num_λ)
    if remove_rv_est
        @assert haskey(first(timeseries.metadata),:rv_est)
    end
    if (alg == :GP) && (maximum(length.(chunk_grids))>1024)
        @error "To use a GP with more than 1024 points in a chunk, use a more efficient GP package, e.g., :TemporalGP."
    end
    if alg == :Sinc    # Setup workspace for Sync.  TODO: Put into functor
        filter_size=23
        kaiserB=13
        sinc_filter = SincInterpolation.create_filter_curve(filter_size*21; filter_size=filter_size, kaiserB=kaiserB)
    end
    for t in 1:num_obs
       idx_start = 0
       boost_factor = remove_rv_est ? calc_doppler_factor(timeseries.metadata[t][:rv_est]) : 1.0
       for c in 1:length(chunk_grids)
           idx = (idx_start+1):(idx_start+length(chunk_grids[c]))
           if verbose
               flush(stdout);  println("t= ",t, " c= ",c," idx= ", idx, " size(flux)= ",size(flux_matrix))
           end
           if t == 1
               λ_vec[idx] .= chunk_grids[c]
               chunk_map[c] = idx
           end
           mean_flux_in_chunk = mean(timeseries.chunk_list[t].data[c].flux)
           if mean_flux_in_chunk > 0
               #println(" mean flux in chunk ", c, " at time ", t, " = ", mean_flux_in_chunk)
               if alg == :Linear
                   LinearInterpolation.interp_chunk_to_shifted_grid_linear!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c], boost_factor )
                   view(flux_matrix,idx,t) .= calc_running_mean_weighted(view(flux_matrix,idx,t))
                   view(var_matrix,idx,t) .= calc_running_mean_weighted(view(var_matrix,idx,t))
               elseif alg == :Sinc
                   SincInterpolation.interp_chunk_to_shifted_grid_sinc!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c], boost_factor, Filter=sinc_filter )
                   view(flux_matrix,idx,t) .= calc_running_mean_weighted(view(flux_matrix,idx,t))
                   view(var_matrix,idx,t) .= calc_running_mean_weighted(view(var_matrix,idx,t))
               elseif alg == :GP
                   GPInterpolation.interp_chunk_to_shifted_grid_gp!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c], boost_factor)
               elseif alg == :TemporalGP
                   #TemporalGPsInterpolation.
                   interp_chunk_to_shifted_grid_gp_temporal!(view(flux_matrix,idx,t), view(var_matrix,idx,t), timeseries.chunk_list[t].data[c], chunk_grids[c], boost_factor, use_logx=true, use_logy=false, smooth_factor=smooth_factor)
               end
               if t == 1
                  λ_vec[idx] .= chunk_grids[c]
                  chunk_map[c] = idx
               end
            else  # no flux in chunk?!?
                @warn "No flux in chunk " * string(c) * " at time " * string(t) * "."
            end

           mean_flux[idx] .+= view(flux_matrix,idx,t)
           mean_var[idx]  .+= view(var_matrix,idx,t)
           #dfluxdlnλ[idx] .+= calc_dfluxdlnlambda(view(flux_matrix,idx,t),view(λ_vec,idx))
           #d2fluxdlnλ2[idx] .+= calc_d2fluxdlnlambda2(view(flux_matrix,idx,t),view(λ_vec,idx))

           flux_matrix[idx,t] ./= mean_flux_in_chunk
           var_matrix[idx,t]  ./= mean_flux_in_chunk^2

           idx_start += length(chunk_grids[c])
       end
    end

    # TODO Think if can propagate uncertainties better
    var_matrix .*= sqrt(oversample_factor)

    #flush(stdout)
    #println("# mean flux pre-normalize = ", mean(mean_flux))
    idx_start = 0
    for c in 1:length(chunk_grids)
        idx = (idx_start+1):(idx_start+length(chunk_grids[c]))
        mean_flux_in_chunk = mean(mean_flux[idx])
        #println("Normalizing chunk ", c, " (",idx,") by ", mean_flux_in_chunk)
        #dmeanfluxdlnλ[idx] .= calc_dfluxdlnlambda(vec(sum(view(flux_matrix,idx,:),dims=2)),vec(sum(view(var_matrix,idx,:), dims=2)) ) ./mean_flux_in_chunk
        #sum_in_chunk = vec(sum(view(flux_matrix,idx,:),dims=2))
        mean_flux[idx] ./= mean_flux_in_chunk
        mean_var[idx] ./= mean_flux_in_chunk^2
        #dfluxdlnλ[idx] ./= mean_flux_in_chunk
        #d2fluxdlnλ2[idx] ./= mean_flux_in_chunk
        if length(idx) >= 2
            dfluxdlnλ[idx] .= calc_dfluxdlnlambda(mean_flux[idx],view(λ_vec,idx))
        else
            dfluxdlnλ[idx] .= 0.0
        end
        if length(idx) >= 3
            d2fluxdlnλ2[idx] .= calc_d2fluxdlnlambda2(mean_flux[idx],view(λ_vec,idx))
        else
            d2fluxdlnλ2[idx] .= 0.0
        end
        idx_start += length(chunk_grids[c])
    end

    #println("# mean flux post-normalize = ", mean(mean_flux))
    #flush(stdout)
    #mean_flux /= num_obs
    mean_var .*= sqrt(oversample_factor)
    #dmeanfluxdlnλ ./= num_obs
    return ( matrix=SpectralTimeSeriesCommonWavelengths(λ_vec,flux_matrix,var_matrix,chunk_map, TheoreticalInstrument1D() ), mean_flux=mean_flux, mean_var=mean_var, deriv=dfluxdlnλ, deriv2=d2fluxdlnλ2 )
end

"""   repack_flux_vector_to_chunk_matrix(λ, flux, var, chunk_map, λc; alg )
Warning:  This doesn't work yet
"""
function repack_flux_vector_to_chunk_matrix(λ::AA1, flux::AA2, var::AA3, chunk_map::CMT, λc::AA4; alg::Symbol = :TemporalGP,
    oversample_factor::Real = 1, smooth_factor::Real=1, verbose::Bool = false ) where {
        T1<:Real, AA1<:AbstractVector{T1}, T2<:Real, AA2<:AbstractVector{T2}, T3<:Real, AA3<:AbstractVector{T3}, T4<:Real, AA4<:AbstractVector{T4},
        CMT<:AbstractArray{UnitRange{Int64}} } #RT<:AbstractRange, AR<:AbstractArray{RT,1}, T<:Real, AV<:AbstractVector{T}, AAV<:AbstractArray{AV,1} }
    @assert alg == :Linear || alg == :GP  || alg == :Sinc || alg == :TemporalGP # TODO: Eventually move to traits-based system?
	@warn "This doesn't work yet"
    num_chunks = length(chunk_map)
    num_λ_per_chunk = minimum(length.(chunk_map))
    flux_matrix = zeros(num_λ_per_chunk,num_chunks)
    var_matrix = zeros(num_λ_per_chunk,num_chunks)
    if (alg == :GP) && (num_λ_per_chunk>1024)
        @error "To use a GP with more than 1024 points in a chunk, use a more efficient GP package, e.g., :TemporalGP."
    end
    if alg == :Sinc    # Setup workspace for Sync.  TODO: Put into functor
        filter_size=23
        kaiserB=13
        sinc_filter = SincInterpolation.create_filter_curve(filter_size*21; filter_size=filter_size, kaiserB=kaiserB)
    end

   idx_start = 0
   for c in 1:length(chunk_map)
       idx_in = (idx_start+1):(idx_start+length(chunk_map[c]))
       idx_out_begin = floor(Int,(idx_in[1]+idx_in[end])//2)-floor(Int,num_λ_per_chunk//2)
       idx_out_end = idx_out_begin+num_λ_per_chunk-1
       idx_out = idx_out_begin:idx_out_end
       Δλ_in_chunk = λ[idx_out].-λc[c]
       mean_flux_in_chunk = mean(flux[idx_out])
       chunk_of_spectrum = ChunkOfSpectrum{eltype(λ),eltype(flux),eltype(var)}(view(λ,idx_out), view(flux,idx_out), view(var,idx_out))
       Δv = 400  # TODO: Figure out good choice for Δv
       Δλ = Δv /RvSpectML.speed_of_light_mps*λc[c]
       if mean_flux_in_chunk > 0
           if alg == :Linear
               LinearInterpolation.interp_chunk_to_grid_linear!(view(flux_matrix,:,c), view(var_matrix,:,c), chunk_of_spectrum, chunk_grid)
               view(flux_matrix,idx,:,c) .= calc_running_mean_weighted(view(flux_matrix,idx,:,c))
               view(var_matrix,idx,:,c) .= calc_running_mean_weighted(view(var_matrix,idx,:,c))
           elseif alg == :Sinc
               chunk_grid = range(λc[c]-(num_λ_per_chunk-1)//2*Δλ, stop=λc[c]+(num_λ_per_chunk-1)//2*Δλ, length=num_λ_per_chunk )
               SincInterpolation.interp_chunk_to_grid_sinc!(view(flux_matrix,:,c), view(var_matrix,:,c), chunk_of_spectrum, chunk_grid, Filter=sinc_filter)
               view(flux_matrix,idx,:,c) .= calc_running_mean_weighted(view(flux_matrix,idx,:,c))
               view(var_matrix,idx,:,c) .= calc_running_mean_weighted(view(var_matrix,idx,:,c))
           elseif alg == :GP
               GPInterpolation.interp_chunk_to_grid_gp!(view(flux_matrix,:,c), view(var_matrix,:,c), chunk_of_spectrum, chunk_grid)
           elseif alg == :TemporalGP
               TemporalGPsInterpolation.interp_chunk_to_grid_gp_temporal!(view(flux_matrix,:,c), view(var_matrix,:,c), chunk_of_spectrum, chunk_grid , use_logx=true, use_logy=false, smooth_factor=smooth_factor)
           end
       else  # no flux in chunk?!?
           @warn "No flux in chunk " * string(c) * " at time " * string(t) * "."
       end
       idx_start += length(chunk_map[c])
   end # for c
   return flux_matrix
    #flush(stdout)
    #println("# mean flux pre-normalize = ", mean(mean_flux))
    #=
    idx_start = 0
    for c in 1:length(chunk_grids)
        idx = (idx_start+1):(idx_start+length(chunk_grids[c]))
        mean_flux_in_chunk = mean(mean_flux[idx])
        #println("Normalizing chunk ", c, " (",idx,") by ", mean_flux_in_chunk)
        #dmeanfluxdlnλ[idx] .= calc_dfluxdlnlambda(vec(sum(view(flux_matrix,idx,:),dims=2)),vec(sum(view(var_matrix,idx,:), dims=2)) ) ./mean_flux_in_chunk
        mean_flux[idx] ./= mean_flux_in_chunk
        mean_var[idx] ./= mean_flux_in_chunk^2
        #dfluxdlnλ[idx] ./= mean_flux_in_chunk
        #d2fluxdlnλ2[idx] ./= mean_flux_in_chunk
        dfluxdlnλ[idx] .= calc_dfluxdlnlambda(mean_flux[idx],view(λ_vec,idx))
        d2fluxdlnλ2[idx] .= calc_d2fluxdlnlambda2(mean_flux[idx],view(λ_vec,idx))

        #dmeanfluxdlnλ[idx] .= calc_dfluxdlnlambda(vec(sum(view(flux_matrix,idx,:),dims=2)),view(λ_vec,idx))  ./mean_flux_in_chunk
        idx_start += length(chunk_grids[c])
    end
    =#
    #println("# mean flux post-normalize = ", mean(mean_flux))
    #flush(stdout)
    #mean_flux /= num_obs
    #mean_var .*= sqrt(oversample_factor)
    #dmeanfluxdlnλ ./= num_obs
    #return flux ( matrix=SpectralTimeSeriesCommonWavelengths(λ_vec,flux_matrix,var_matrix,chunk_map, TheoreticalInstrument1D() ), mean_flux=mean_flux, mean_var=mean_var, deriv=dfluxdlnλ, deriv2=d2fluxdlnλ2 )
end


using .Microclimate

"""
    apply_environment!(plant::Plant, env::AbstractMicroclimate, organs, u, t)

Apply a microclimate from Microclimates.jl to a `Plant`.
"""
apply_environment!(env::AbstractMicroclimate, plant::Plant, organs, u, t) = begin
    envind = calc_envind(plant, t)
    shoot, root = organs
    # Get the climate at a particular height and time
    # Use half the height for the median temperature/windspeed/humidity etc.
    # relating temperature to the distribution of biomass may be more accurate.
    shootenv = MicroclimInstant(env, envind, height(shoot)/2)
    rootenv = MicroclimInstant(env, envind, depth(root)/2)
    # Update any environment dependent variables
    apply_environment!(assimilation_pars(shoot), shoot, u, shootenv, rootenv)
    apply_environment!(assimilation_pars(root), root, u, rootenv, shootenv)
    nothing
end

# We need an environment data point for t = 0 even if we
# never use it - we may need to interpolate at t = 0.5
calc_envind(o, t) = ustrip(o.environment_start[] + t) + 1

"""
    apply_environment!(plant::Plant, env::MicroclimControl, organs, u, t)

Apply an environment to a Plant using live user input for variables,
instead of data.
"""
apply_environment!(env::MicroclimControl, plant::Plant, organs, u, t) = begin
    shoot, root = organs
    apply_environment!(assimilation_pars(shoot), shoot, u, env, env)
    apply_environment!(assimilation_pars(root), root, u, env, env)
    nothing
end

"""
    apply_environment!(a::AbstractKooijmanPhoto, o, u, shootenv, rootenv)

Apply environment to shoot with `AbstractKooijmanPhoto` assimilation model.
"""
apply_environment!(a::AbstractKooijmanPhoto, o, u, shootenv, rootenv) = begin
    v = a.vars
    update_temp!(o, airtemperature(shootenv))
    v.J_L_F = radiation(shootenv) * parconv
    # Use the mean soil water potential between zero and the full root depth
    # swp = mean_soilwaterpotential(rootenv.microclimate, depth(o), rootenv.t)
    swp = if typeof(rootenv) <: MicroclimControl
        soilwaterpotential(rootenv)
    else
        layermax(soilwaterpotential(rootenv.microclimate), rootenv)
    end
    v.soilwaterpotential = swp
    set_swp!(o, swp)
end
"""

Apply environment to shoot with any `AbstractAssim` assimilation model.
This simply adds the air temperature.
"""
apply_environment!(a::AbstractAssim, o, u, shootenv, rootenv) =
    update_temp!(o, airtemperature(shootenv))
"""
    apply_environment!(a::AbstractNAssim, o, u, rootenv, shootenv)

Apply environment to root with `AbstractNAssim` assimilation model.
This simply adds the soil temperature.
"""
apply_environment!(a::AbstractNAssim, o, u, rootenv, shootenv) =
    update_temp!(o, soiltemperature(rootenv))


"""
    Before calling this function one should check that `startpos` is inside `det`: `in(startpos, det`

"""
function drift_charge!(
                            drift_path::Vector{CartesianPoint{T}},
                            det::SolidStateDetector{T, :cartesian},
                            point_types::PointTypes{T, 3, :cartesian},
                            grid::Grid{T, 3, :cartesian},
                            startpos::CartesianPoint{T},
                            Δt::T,
                            velocity_field::Interpolations.Extrapolation{<:StaticVector{3}, 3};
                            verbose::Bool = true
                        )::Nothing where {T <: SSDFloat}
    done::Bool = false
    drift_path[1] = startpos
    null_step::CartesianVector{T} = CartesianVector{T}(0, 0, 0)
    @inbounds for istep in eachindex(drift_path)[2:end] #end]
        if done == false
            current_pos::CartesianPoint{T} = drift_path[istep - 1]
            stepvector::CartesianVector{T} = get_velocity_vector(velocity_field, current_pos) * Δt
            if stepvector == null_step done = true end
            next_pos::CartesianPoint{T} = current_pos + stepvector
            if next_pos in point_types
                drift_path[istep] = next_pos
            elseif next_pos in det
                drift_path[istep] = next_pos
            else
                crossing_pos::CartesianPoint{T}, cd_point_type::UInt8, boundary_index::Int, surface_normal::CartesianVector{T} = get_crossing_pos(det, grid, current_pos, next_pos)
                if cd_point_type == CD_ELECTRODE
                    done = true
                    drift_path[istep] = crossing_pos
                elseif cd_point_type == CD_FLOATING_BOUNDARY
                    projected_vector = project_to_plane(stepvector, surface_normal )
                    next_pos = current_pos + projected_vector
                    # ToDo: We actually need a time array as well to do this properly...
                    small_projected_vector = projected_vector * T(0.001)
                    i::Int = 0
                    while i < 1000 && !(next_pos in det)
                        next_pos -= small_projected_vector
                        i += 1
                    end
                    if i == 1000 && verbose @warn("Handling of charge at floating boundary did not work as intended. Start Position (Cart): $startpos") end
                    drift_path[istep] = next_pos
                elseif cd_point_type == CD_BULK
                    if verbose @warn ("Internal error for charge starting at $startpos") end
                    drift_path[istep] = current_pos
                    done = true
                else # elseif cd_point_type == CD_OUTSIDE
                    if verbose @warn ("Internal error for charge starting at $startpos") end
                    drift_path[istep] = current_pos
                    done = true
                end
            end
        elseif done == true
            drift_path[istep] = drift_path[istep-1]
        end
    end
    return nothing
end

# Point types for charge drift: Defined in DetectorGeometries/DetectorGeometries.jl
# const CD_ELECTRODE = 0x00
# const CD_OUTSIDE = 0x01
# const CD_BULK = 0x02
# const CD_FLOATING_BOUNDARY = 0x04 # not 0x03, so that one could use bit operations here...

function get_crossing_pos(  detector::SolidStateDetector{T, :cartesian}, grid::Grid{T, 3}, point_in::CartesianPoint{T}, point_out::CartesianPoint{T};
                            max_n_iter::Int = 2000)::Tuple{CartesianPoint{T}, UInt8, Int, CartesianVector{T}} where {T <: SSDFloat}
    point_mid::CartesianPoint{T} = T(0.5) * (point_in + point_out)
    cd_point_type::UInt8, contact_idx::Int, surface_normal::CartesianVector{T} = point_type(detector, grid, point_mid)
    for i in 1:max_n_iter
        if cd_point_type == CD_BULK
            point_in = point_mid
        elseif cd_point_type == CD_OUTSIDE
            point_out = point_mid
        elseif cd_point_type == CD_ELECTRODE
            break
        else #elseif cd_point_type == CD_FLOATING_BOUNDARY
            break
        end
        point_mid = T(0.5) * (point_in + point_out)
        cd_point_type, contact_idx, surface_normal = point_type(detector, grid, point_mid)
    end
    return point_mid, cd_point_type, contact_idx, surface_normal
end


# *********************************************************************************
# REopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this list
# of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
# *********************************************************************************
function add_export_constraints(m, p; _n="")

    ##Constraint (8e): Production export no greater than production
    @constraint(m, [t in p.elec_techs, ts in p.time_steps_with_grid],
        p.production_factor[t,ts] * p.levelization_factor[t] * m[Symbol("dvRatedProduction"*_n)][t,ts]
        >= sum(m[Symbol("dvProductionToGrid"*_n)][t, u, ts] for u in p.export_bins_by_tech[t])
        + m[Symbol("dvCurtail"*_n)][t, ts]
    )

    ##Constraint (8f): Total sales to grid no greater than annual allocation - storage tiers
    @constraint(m,
        p.hours_per_timestep * sum( m[Symbol("dvProductionToGrid"*_n)][t, u, ts]
            for t in p.elec_techs, u in p.export_bins_by_tech[t], ts in p.time_steps_with_grid
        ) <= p.max_grid_export_kwh
    )

    ### Constraint set (9): Net Meter Module
    ##Constraint (9c): Net metering only -- can't sell more than you purchase
    # note that hours_per_timestep is cancelled on both sides, but used for unit consistency (convert power to energy)
    NEM_techs = String[t for t in p.elec_techs if :NEM in p.export_bins_by_tech[t]]
    if !isempty(NEM_techs)
        @constraint(m,
            p.hours_per_timestep * sum( m[Symbol("dvProductionToGrid"*_n)][t, :NEM, ts]
            for t in NEM_techs, ts in p.time_steps)
            <= p.hours_per_timestep * sum( m[Symbol("dvGridPurchase"*_n)][ts] for ts in p.time_steps)
        )
    end
end

function add_monthly_peak_constraint(m, p; _n="")
	## Constraint (11d): Monthly peak demand is >= demand at each hour in the month
	@constraint(m, [mth in p.months, ts in p.etariff.time_steps_monthly[mth]],
    m[Symbol("dvPeakDemandMonth"*_n)][mth] >= m[Symbol("dvGridPurchase"*_n)][ts]
    )
end

function add_tou_peak_constraint(m, p; _n="")
    ## Constraint (12d): Ratchet peak demand is >= demand at each hour in the ratchet`
    @constraint(m, [r in p.ratchets, ts in p.etariff.tou_demand_ratchet_timesteps[r]],
        m[Symbol("dvPeakDemandTOU"*_n)][r] >= m[Symbol("dvGridPurchase"*_n)][ts]
    )
end

function add_mincharge_constraint(m, p; _n="")
    @constraint(m,
        m[Symbol("MinChargeAdder"*_n)] >= m[Symbol("TotalMinCharge"*_n)] - ( m[Symbol("TotalEnergyChargesUtil"*_n)] +
        m[Symbol("TotalDemandCharges"*_n)] + m[Symbol("TotalExportBenefit"*_n)] + m[Symbol("TotalFixedCharges"*_n)] )
    )
end

function add_simultaneous_export_import_constraint(m, p; _n="")
    @constraint(m, NoGridPurchasesBinary[ts in p.time_steps],
          m[Symbol("dvGridPurchase"*_n)][ts]
        + sum(m[Symbol("dvGridToStorage"*_n)][b, ts] for b in p.storage.types)
        - (1 - m[Symbol("binNoGridPurchases"*_n)][ts]) * 1.0E9 <= 0
    )
    @constraint(m, ExportOnlyAfterSiteLoadMetCon[ts in p.time_steps],
        sum(m[Symbol("dvProductionToGrid"*_n)][t,u,ts] for t in p.elec_techs, u in p.export_bins_by_tech[t])
        - m[Symbol("binNoGridPurchases"*_n)][ts] * 1.0E9 <= 0
    )
end

function add_elec_utility_expressions(m, p; _n="")

    if !isempty(p.etariff.export_bins) & !isempty(p.techs)
        # NOTE: levelization_factor is baked into dvProductionToGrid
        m[Symbol("TotalExportBenefit"*_n)] = @expression(m, p.pwf_e * p.hours_per_timestep *
            sum( sum(p.etariff.export_rates[u][ts] * m[Symbol("dvProductionToGrid"*_n)][t, u, ts]
                 for u in p.etariff.export_bins, t in p.techs_by_exportbin[u])
            for ts in p.time_steps)
        )
    else
        m[Symbol("TotalExportBenefit"*_n)] = 0
    end

    m[Symbol("TotalEnergyChargesUtil"*_n)] = @expression(m, p.pwf_e * p.hours_per_timestep *
        sum( p.etariff.energy_rates[ts] * m[Symbol("dvGridPurchase"*_n)][ts] for ts in p.time_steps)
    )

    if !isempty(p.etariff.tou_demand_rates)
        m[Symbol("DemandTOUCharges"*_n)] = @expression(m,
            p.pwf_e * sum( p.etariff.tou_demand_rates[r] * m[Symbol("dvPeakDemandTOU"*_n)][r] for r in p.ratchets)
        )
    else
        m[Symbol("DemandTOUCharges"*_n)] = 0
    end

    if !isempty(p.etariff.monthly_demand_rates)
        m[Symbol("DemandFlatCharges"*_n)] = @expression(m,
            p.pwf_e * sum( p.etariff.monthly_demand_rates[mth] * m[Symbol("dvPeakDemandMonth"*_n)][mth] for mth in p.months)
        )
    else
        m[Symbol("DemandFlatCharges"*_n)] = 0
    end

    m[Symbol("TotalDemandCharges"*_n)] = m[Symbol("DemandTOUCharges"*_n)] + m[Symbol("DemandFlatCharges"*_n)]

    m[Symbol("TotalFixedCharges"*_n)] = p.pwf_e * p.etariff.fixed_monthly_charge * 12

    if p.etariff.annual_min_charge > 12 * p.etariff.min_monthly_charge
        m[Symbol("TotalMinCharge"*_n)] = p.etariff.annual_min_charge
    else
        m[Symbol("TotalMinCharge"*_n)] = 12 * p.etariff.min_monthly_charge
    end

	if m[Symbol("TotalMinCharge"*_n)] >= 1e-2
		add_mincharge_constraint(m, p)
	else
		@constraint(m, m[Symbol("MinChargeAdder"*_n)] == 0)
	end

    m[Symbol("TotalElecBill"*_n)] = (
        m[Symbol("TotalEnergyChargesUtil"*_n)]
        + m[Symbol("TotalDemandCharges"*_n)]
        + m[Symbol("TotalExportBenefit"*_n)]
        + m[Symbol("TotalFixedCharges"*_n)]
        + 0.999 * m[Symbol("MinChargeAdder"*_n)]
    )
    #= Note: 0.999 * MinChargeAdder in Objective b/c when
        TotalMinCharge > (TotalEnergyCharges + TotalDemandCharges + TotalExportBenefit + TotalFixedCharges)
		it is arbitrary where the min charge ends up (eg. could be in TotalDemandCharges or MinChargeAdder).
		0.001 * MinChargeAdder is added back into LCC when writing to results.
    =#
    nothing
end


module ITensors

using Random,
      Printf,
      LinearAlgebra,
      StaticArrays,
      TimerOutputs

# TODO: move imports to individual files
import Base.adjoint,
       Base.conj,
       Base.convert,
       Base.copy,
       Base.deepcopy,
       Base.copyto!,
       Base.eltype,
       Base.fill!,
       Base.getindex,
       Base.in,
       Base.isapprox,
       Base.isless,
       Base.iterate,
       Base.length,
       Base.push!,
       Base.setindex!,
       Base.eachindex,
       Base.show,
       Base.sum,
       Base.summary,
       Base.similar,
       Base.size,
       Base.ndims,
       Base.!=,
       Base.+,
       Base.-,
       Base.*,
       Base./,
       Base.^,
       Base.setdiff,  # Since setdiff doesn't
                      # work with IndexSet, overload it
       Base.lastindex,
       LinearAlgebra.axpby!,
       LinearAlgebra.axpy!,
       LinearAlgebra.dot,
       LinearAlgebra.norm,
       LinearAlgebra.mul!,
       LinearAlgebra.rmul!,
       LinearAlgebra.normalize!,
       Random.randn!

#####################################
# Global Variables
#
const warnTensorOrder = 10
const GLOBAL_TIMER = TimerOutput()

#####################################
# Index and IndexSet
#
# TODO: load these after Tensor functions
include("smallstring.jl")
include("readwrite.jl")
include("tagset.jl")
include("index.jl")
include("indexset.jl")

#####################################
# Tensor
#
include("tensor/tensor.jl")
include("tensor/tensorstorage.jl")
include("tensor/contraction_logic.jl")
include("tensor/dense.jl")
include("tensor/linearalgebra.jl")
include("tensor/diag.jl")
include("tensor/combiner.jl")
include("tensor/truncate.jl")
include("tensor/svd.jl")

#####################################
# ITensor
#
include("itensor.jl")
include("decomp.jl")
include("iterativesolvers.jl")

###########################################################
# MPS/MPO
#
include("mps/mps.jl")
include("mps/mpo.jl")
include("mps/sweeps.jl")
include("mps/projmpo.jl")
include("mps/observer.jl")
include("mps/dmrg.jl")

###########################################################
# Physics
#
include("physics/tag_types.jl")
include("physics/lattices.jl")
include("physics/site_types/spinhalf.jl")
include("physics/site_types/spinone.jl")
include("physics/site_types/electron.jl")
include("physics/site_types/tj.jl")
include("physics/autompo.jl")

end # module ITensors


# This file contains code pertaining to "measures", which are some unit of
# length. Compose supports specifying measures in absolute terms (e.g., 15mm),
# but also in terms of the width/height of the parent canvas (e.g, 0.5w, 0.25h),
# or in "canvas coordinates" which is an arbitrary coordinate system given to
# the canvas (e.g, 10cx, -5cy).

export Measure, BoundingBox, text_extents,
       mm, cm, inch, furlong, pt, w, h, px, cx, cy

# A unit gives some semantic meaning to a bare number.
abstract Unit
show(io::IO, ::Type{Unit}) = print(io, "??")

# Units defined in terms of the screen.
abstract PixelUnit <: Unit
show(io::IO, ::Type{PixelUnit}) = print(io, "px")

# Units of absolute length.
abstract MillimeterUnit <: Unit
show(io::IO, ::Type{MillimeterUnit}) = print(io, "mm")

# Units proportional to the width/height of the containing canvas.
abstract WidthUnit <: Unit
show(io::IO, ::Type{WidthUnit}) = print(io, "width")

abstract HeightUnit <: Unit
show(io::IO, ::Type{HeightUnit}) = print(io, "height")

# Canvas specific units defined in terms of width/height.
abstract CanvasXUnit <: Unit
abstract CanvasYUnit <: Unit

# A measure is a number annotated with a type.
abstract Measure

# A measure used by a backend
abstract NativeMeasure <: Measure

# A single number with a single associated unit.
type SimpleMeasure{U <: Unit} <: Measure
    value::Float64
end

function copy{U}(a::SimpleMeasure{U})
    SimpleMeasure{U}(a.value)
end

function show{U}(io::IO, a::SimpleMeasure{U})
    print(io, a.value, U)
end

function json(a::SimpleMeasure)
    repr(string(a))
end

function +{U}(a::SimpleMeasure{U}, b::SimpleMeasure{U})
    SimpleMeasure{U}(a.value + b.value)
end

function -{U}(a::SimpleMeasure{U}, b::SimpleMeasure{U})
    SimpleMeasure{U}(a.value - b.value)
end

function *{U}(a::Number, b::SimpleMeasure{U})
    SimpleMeasure{U}(convert(Float64, a) * b.value)
end

*{U}(a::SimpleMeasure{U}, b::Number) = b * a

-{U}(a::SimpleMeasure{U}) = SimpleMeasure{U}(-a.value)

function /{U}(a::SimpleMeasure{U}, b::SimpleMeasure{U})
    a.value / b.value
end

function /{U}(a::SimpleMeasure{U}, b::Number)
    SimpleMeasure{U}(a.value / convert(Float64, b))
end

=={U}(a::SimpleMeasure{U}, b::SimpleMeasure{U}) = a.value == b.value
<{U}(a::SimpleMeasure{U}, b::SimpleMeasure{U}) = a.value < b.value
<={U}(a::SimpleMeasure{U}, b::SimpleMeasure{U}) = a.value <= b.value
>={U}(a::SimpleMeasure{U}, b::SimpleMeasure{U}) = a.value >= b.value

# Unit of a simple measure.
measure_unit{U}(a::SimpleMeasure{U}) = U

# A sum of one or more measures of different units.
type CompoundMeasure <: Measure
    values::Dict{Type, Float64}

    function CompoundMeasure()
        new(Dict{Type, Float64}())
    end

    function CompoundMeasure(values::Dict{Type, Float64})
        new(values)
    end
end

function copy(a::CompoundMeasure)
    CompoundMeasure(copy(a.values))
end

function show(io::IO, a::CompoundMeasure)
    print(io, join([sprint(print, v, k) for (k, v) in a.values], " + "))
end

function +{U, V}(a::SimpleMeasure{U}, b::SimpleMeasure{V})
    c = CompoundMeasure()
    c.values[U] = a.value
    c.values[V] = b.value
    c
end

function -{U, V}(a::SimpleMeasure{U}, b::SimpleMeasure{V})
    c = CompoundMeasure()
    c.values[U] =  a.value
    c.values[V] = -b.value
    c
end

function +{U}(a::CompoundMeasure, b::SimpleMeasure{U})
    c = CompoundMeasure(copy(a.values))
    if haskey(c.values, U)
        c.values[U] += b.value
    else
        c.values[U] = b.value
    end
    c
end

+{U}(a::SimpleMeasure{U}, b::CompoundMeasure) = b + a

function -{U}(a::CompoundMeasure, b::SimpleMeasure{U})
    c = CompoundMeasure(copy(a.values))
    if haskey(c.values, U)
        c.values[U] -= b.value
    else
        c.values[U] = -b.value
    end
    c
end

function -{U}(a::SimpleMeasure{U}, b::CompoundMeasure)
    c = CompoundMeasure()
    c.values[U] = a.value
    for (V, value) in b.values
        if haskey(c.values, V)
            c.values[V] -= value
        else
            c.values[V] = -value
        end
    end
    c
end

function +(a::CompoundMeasure, b::CompoundMeasure)
    c = CompoundMeasure(copy(a.values))
    for (k, v) in b.values
        if haskey(c.values, k)
            c.values[k] += v
        else
            c.values[k] = v
        end
    end
    c
end

function -(a::CompoundMeasure, b::CompoundMeasure)
    c = CompoundMeasure(copy(a.values))
    for (k, v) in b.values
        if haskey(c.values, k)
            c.values[k] -= v
        else
            c.values[k] = -v
        end
    end
    c
end

function *(a::Number, b::CompoundMeasure)
    a = convert(Float64, a)
    c = CompoundMeasure(copy(b.values))
    for (k,v) in c.values
        c.values[k] *= a
    end
    c
end

*(a::CompoundMeasure, b::Number) = b * a

function /(a::CompoundMeasure, b::Number)
    b = convert(Float64, b)
    c = CompoundMeasure(copy(a.values))
    for (k,v) in c.values
        c.values[k] /= b
    end
    c
end

# Rather than explicitly intialize instances of Measure, one should use these
# constants to form coordinates by multiplication. E.g., "2inch", "35px", and
# so on.
const mm      = SimpleMeasure{MillimeterUnit}(1.0)
const cm      = SimpleMeasure{MillimeterUnit}(10.0)
const inch    = SimpleMeasure{MillimeterUnit}(25.4)
const furlong = 7920inch
const pt      = inch/72
const w       = SimpleMeasure{WidthUnit}(1.0)
const h       = SimpleMeasure{HeightUnit}(1.0)
const px      = SimpleMeasure{PixelUnit}(1.0)
const cx      = SimpleMeasure{CanvasXUnit}(1.0)
const cy      = SimpleMeasure{CanvasYUnit}(1.0)

# Conversion between pixels and millimeters. This is tricky business, since we
# have no way of knowing the pixel density yet want to do something other than
# display an error message. Thus we are going to choose a reasonable default of
# 4.5 pixels per mm, which is in typical laptop screen territory.
const assumed_ppmm = 4.5

function convert(::Type{SimpleMeasure{PixelUnit}},
                 u::SimpleMeasure{MillimeterUnit})
    SimpleMeasure{PixelUnit}(u.value * assumed_ppmm)
end

function convert(::Type{SimpleMeasure{MillimeterUnit}},
                 u::SimpleMeasure{PixelUnit})
    SimpleMeasure{MillimeterUnit}(u.value / assumed_ppmm)
end

# Compute a measure at least as large as any of the given measures.
#
# Arbitrary comparisons between measures are not necessarily well defined since
# the scale of relative units are not known until the graphic is drawn. Yet we
# sometimes need to to produce a measure that is at least as long as several
# others. This max function finds such an upper bound, but it differs from the
# normal max function in that the upper bound may not be tight.
#
# TODO: Possibly a new type should be implemented to handle these sorts of
# situations. Essentially a lazy maximum measure that gets evaluated once
# relative units are resolved.
#
function max(measures::Measure...)
    maximums = Dict{Type, Float64}()
    for measure in measures
        if isa(measure, CompoundMeasure)
            for (t, value) in measure.values
                if haskey(maximums, t)
                    maximums[t] = max(maximums[t], value)
                else
                    maximums[t] = value
                end
            end
        else
            t = measure_unit(measure)
            if haskey(maximums, t)
                maximums[t] = max(maximums[t], measure.value)
            else
                maximums[t] = measure.value
            end
        end
    end

    if length(maximums) == 1
        for (t, value) in maximums
            return SimpleMeasure{t}(value)
        end
    else
        CompoundMeasure(maximums)
    end
end

max(measures::Vector{Measure}) = max(measures...)

# For user-facing functions, there is often an obvious default unit.
typealias MeasureOrNumber Union(Measure, Number)

# Points
type Point
    x::Measure
    y::Measure

    function Point(x::MeasureOrNumber, y::MeasureOrNumber)
        new(x_measure(x), y_measure(y))
    end

    function Point(point::Point)
        new(copy(point.x), copy(point.y))
    end
end

copy(point::Point) = Point(point)

# Return array of paths to draw with printpath
# array is formed by splitting by NaN values
function make_paths(points::Vector{Point})
    paths = Any[]
    nans = filter(i->isnan(points[i].y.value), [1:length(points)])

    if length(nans) == 0
        push!(paths, points)
    else
        nans = [0, nans, length(points) + 1]
        i, n = 1, length(nans)

        while i <= n-1
            if nans[i] + 1 < nans[i + 1]
                push!(paths, points[(nans[i]+1):(nans[i+1] - 1)])
            end
            i += 1
        end
    end
    paths
end

# Support specifying points as tuples.
typealias XYTuple NTuple{2, MeasureOrNumber}

function Point(xy::XYTuple)
    Point(x_measure(xy[1]),
          y_measure(xy[2]))
end

function convert(::Type{Point}, xy::XYTuple)
    Point(xy)
end

# A point in broadest terms.
typealias XYTupleOrPoint Union(XYTuple, Point)

# Rotation about a point.
type Rotation
    theta::Float64
    offset::Point

    function Rotation()
        new(0.0, Point(0., 0.))
    end

    function Rotation(theta::Number)
        Rotation(theta, 0.0, 0.0)
    end

    function Rotation(theta::Number, offset::XYTupleOrPoint)
        new(convert(Float64, theta), convert(Point, offset))
    end

    function Rotation(theta::Number,
        offset_x::MeasureOrNumber,
        offset_y::MeasureOrNumber)
        new(convert(Float64, theta), Point(offset_x, offset_y))
    end

    # copy constructor
    function Rotation(rot::Rotation)
        new(copy(rot.theta),
            copy(rot.offset))
    end
end

copy(rot::Rotation) = Rotation(rot)

type NativeTransform
    M::Matrix{Float64}

    function NativeTransform()
        new([1.0 0.0 0.0
        0.0 1.0 0.0
        0.0 0.0 1.0])
    end

    function NativeTransform(M::Matrix{Float64})
        new(M)
    end
end

const identity_native_transform = NativeTransform()

function isidentity(a::NativeTransform)
    a.M == identity_native_transform.M
end

function combine(a::NativeTransform, b::NativeTransform)
    NativeTransform(a.M * b.M)
end

# A rectangle defining a coordinate system
type BoundingBox
    x0::Measure
    y0::Measure
    width::Measure
    height::Measure

    function BoundingBox(x0::MeasureOrNumber,
        y0::MeasureOrNumber,
        width::MeasureOrNumber,
        height::MeasureOrNumber)
        new(x_measure(x0),
        y_measure(y0),
        x_measure(width),
        y_measure(height))
    end

    # copy-constructor
    function BoundingBox(box::BoundingBox)
        new(copy(box.x0), copy(box.y0),
            copy(box.width), copy(box.height))
    end
end

BoundingBox() = BoundingBox(0, 0, 1, 1)

copy(box::BoundingBox) = BoundingBox(box)

type NativeBoundingBox{M <: NativeMeasure}
    x0::M
    y0::M
    width::M
    height::M
end

# Context-dependent interpretation of bare numbers.

x_measure(u::Measure) = u
x_measure(u::Number) = SimpleMeasure{CanvasXUnit}(convert(Float64, u))

y_measure(u::Measure) = u
y_measure(u::Number) = SimpleMeasure{CanvasYUnit}(convert(Float64, u))

# TODO: defaulting to pixels is not reasonable. Maybe pt?
size_measure(u::Measure) = u
size_measure(u::Number) = SimpleMeasure{PixelUnit}(convert(Float64, u))


# TODO currently the program is dynamically allocated

_mode(instr::Symbol) = (:Implicit, instr, nothing)

function _mode(instr::Expr, line::Integer=-1)
    if @capture(instr, (op_,))
        return (:Implicit, op, nothing)
    elseif @capture(instr, (op_, arg_UInt8))
        return (:Immediate, op, arg)
    elseif @capture(instr, (op_, Π(arg_UInt8)))
        return (:ZeroPage, op, arg)
    elseif @capture(instr, (op_, Π(arg_UInt16)))
        return (:Absolute, op, arg)
    elseif @capture(instr, (op_, Π(arg_UInt8), X))
        return (:ZeroPageX, op, arg)
    elseif @capture(instr, (op_, Π(arg_UInt8), Y))
        return (:ZeroPageY, op, arg)
    elseif @capture(instr, (op_, Π(arg_UInt16), X))
        return (:AbsoluteX, op, arg)
    elseif @capture(instr, (op_, Π(arg_UInt16), Y))
        return (:AbsoluteY, op, arg)
    elseif @capture(instr, (op_, (Π(arg_UInt16),)))
        return (:Indirect, op, arg)
    elseif @capture(instr, (op_, (Π(arg_UInt8),X)))
        return (:IndirectX, op, arg)
    elseif @capture(instr, (op_, (Π(arg_UInt8),Y)))
        return (:IndirectY, op, arg)
    end
    throw(ArgumentError("Assembly Error: Unparsable instruction at line $line: `$instr`."))
end

_assembly_lookup(op::String, mode::Symbol) = ASSEMBLY_DICT[(op,mode)]

_assembly_args(::Nothing) = nothing
_assembly_args(x::UInt8) = x
_assembly_args(x::UInt16) = reinterpret(UInt8, [x])

function assemble!(program::Vector{UInt8}, instr::Union{Expr,Symbol})
    mode, op, arg = _mode(instr)
    op = lowercase(string(op))
    opcode = _assembly_lookup(op, mode)
    args = _assembly_args(arg)
    if args == nothing
        push!(program, opcode)
    else
        append!(program, UInt8[opcode; args])
    end
end

macro assemble(block::Expr)
    program = Vector{UInt8}(undef)  # for now we'll dynamically allocate
    if @capture(block, begin instrs__ end)
        for instr ∈ instrs
            assemble!(program, instr)
        end
    else
        throw(ArgumentError("Assembly Error: Malformed instruction block."))
    end
    :($program)
end


# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder

name = "Arpack"
version = v"3.5.0-3"

# Collection of sources required to build ArpackMKLBuilder
sources = [
    "https://github.com/opencollab/arpack-ng.git" =>
    "b095052372aa95d4281a645ee1e367c28255c947",

]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir
cd arpack*
mkdir build
cd build
# cmake -DINTERFACE64=1 -DBUILD_SHARED_LIBS=ON -DLAPACK_FOUND=true -DLAPACK_INCLUDE_DIRS=${MKLROOT}/include -DLAPACK_LIBRARIES=mkl_rt -DBLAS_FOUND=true -DBLAS_INCLUDE_DIRS=${MKLROOT}/include -DBLAS_LIBRARIES=mkl_rt ..
CMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath -Wl,$MKLROOT/lib"
# Symbols that have float32, float64, complexf32, and complexf64 support
SDCZ_SYMBOLS="axpy copy gemv geqr2 lacpy lahqr lanhs larnv lartg \
              lascl laset scal trevc trmm trsen gbmv gbtrf gbtrs \
              gttrf gttrs pttrf pttrs"
# All symbols that have float32/float64 support (including the SDCZ_SYMBOLS above)
SD_SYMBOLS="${SDCZ_SYMBOLS} dot ger labad laev2 lamch lanst lanv2 \
            lapy2 larf larfg lasr nrm2 orm2r rot steqr swap"
# All symbols that have complexf32/complexf64 support (including the SDCZ_SYMBOLS above)
CZ_SYMBOLS="${SDCZ_SYMBOLS} dotc geru unm2r"
# Add in (s|d)*_64 symbol remappings:
for sym in ${SD_SYMBOLS}; do     SYMBOL_DEFS="${SYMBOL_DEFS} -Ds${sym}=s${sym}_64 -Dd${sym}=d${sym}_64"; done
# Add in (c|z)*_64 symbol remappings:
for sym in ${CZ_SYMBOLS}; do     SYMBOL_DEFS="${SYMBOL_DEFS} -Dc${sym}=c${sym}_64 -Dz${sym}=z${sym}_64"; done
# Add one-off symbol mappings; things that don't fit into any other bucket:
for sym in scnrm2 dznrm2 csscal zdscal dgetrf dgetrs; do     SYMBOL_DEFS="${SYMBOL_DEFS} -D${sym}=${sym}_64"; done
# Set up not only lowercase symbol remappings, but uppercase as well:
SYMBOL_DEFS="${SYMBOL_DEFS} ${SYMBOL_DEFS^^}"
FFLAGS="${FFLAGS} -fdefault-integer-8 ${SYMBOL_DEFS} -ff2c"
cmake ..  -DINTERFACE64=1 -DBUILD_SHARED_LIBS=ON -DBLAS_LIBRARIES="-L$MKLROOT/lib -lmkl_rt" -DLAPACK_LIBRARIES="-L$MKLROOT/lib -lmkl_rt" -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" -DCMAKE_Fortran_FLAGS="${FFLAGS}"
make clean all
make install

# Install into ${prefix}/lib
mkdir -p ${prefix}/lib
cp lib/libarpack.so.2.0.0 ${prefix}/lib
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = [
    Linux(:x86_64, libc=:glibc),
    Linux(:i686, libc=:glibc)
]
platforms = expand_gcc_versions(platforms)

# The products that we will ensure are always built
products(prefix) = [
    LibraryProduct(prefix, "libarpack", :libarpack)
]

# Dependencies that must be installed before this package can be built
dependencies = [
    "https://raw.githubusercontent.com/JuliaComputing/MKL.jl/76ab0ac2b671eb4e199c1b4a31872ccb22898e4f/deps/build_MKL.jl"
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies)


module BonbonRectangle
export GameEnv, GameSpec, Board
include("game.jl")
  module Training
    using AlphaZero
    import ..GameSpec
    include("../../scripts/test_game.jl")
  end
end


###
# Main
###

using MAT
using Datetime
using Distributions
import NumericExtensions.sumabs
import NumericExtensions
const ne = NumericExtensions

include("config.jl")
include("drawcircle.jl")
include("gaussian_blur.jl")
include("utils.jl")

###
# set up initial configuration
###
function main(config=Dict(), disturbances=Dict(), cluster=false; enableVis :: Bool = false, fileName = "", loadTime = nothing, allow32Bit = false, forceJuliaImpl = false, resultFolder="results")
    useVis = enableVis && ((length(procs()) == 1) || (!(myid() in workers()))) && !cluster
    ###
    # Prepare GPU
    ###
    const P64BIT, USECL, ctx, queue = determineCapabilities(cluster, allow32Bit, forceJuliaImpl)

    ###
    # Prepare GUI
    ###

    guiproc = if useVis && (length(procs()) <= 1)
        first(addprocs(1))
    elseif useVis
        last(procs())
    else
        -1
    end

    rref = if useVis
       @at_proc guiproc using PyPlot
    end

    value = simulation(cluster, useVis, fileName, loadTime, USECL, P64BIT ? Float64 : Float32, config, disturbances, guiproc, ctx, queue, rref, resultFolder)

    ctx = nothing
    queue = nothing
    gc()
    return value
end

function simulation{T <: FloatingPoint}(cluster, enableVis, fileName, loadTime, USECL, :: Type{T}, config :: Dict, disturbances :: Dict, guiproc :: Int, ctx, queue, gui_rref, resultFolder)
worker = (length(procs()) > 1) && (myid() in workers())

# initialize membrane fields
Afield = nothing
Mfield = nothing
Ffield = nothing
Wfield = nothing
directionfield = nothing

# Prepare the configuration values and load the default settings
loadConfig(baseConfig)

# If we have a non empty fileName and we don't run on a cluster
# Load configuration values from data directory.
fileConfig = if fileName != "" && !cluster
    matread("data/$(fileName).mat")
else
    Dict()
end

# Load file config
loadConfig(fileConfig, dataVars)

# Overwrite configuration values from a
loadConfig(config, dataVars)
updateDependentValues() # Update the values that depend on the configuration

# Load initial state.
if loadTime != nothing
    history_W, history_A, history_M, history_F, history_dir =
        if cluster && checkVars(historyVars, collect(keys(config)))
            (config["history_W"], config["history_A"], config["history_M"], config["history_F"], config["history_dir"])
        elseif checkVars(historyVars, collect(keys(fileConfig)))
            (fileConfig["history_W"], fileConfig["history_A"], fileConfig["history_M"], fileConfig["history_F"], fileConfig["history_dir"])
        else
            error("Could not find historyVars despite being given a loadTime of $loadTime")
        end

    if loadTime == -1
        Wfield = history_W[:,:,end]
        Afield = history_A[:,:,end]
        Mfield = history_M[:,:,end]
        Ffield = history_F[:,:,end]
        directionfield = history_dir[:,:,end]
    else
        Wfield = history_W[:,:,loadTime]
        Afield = history_A[:,:,loadTime]
        Mfield = history_M[:,:,loadTime]
        Ffield = history_F[:,:,loadTime]
        directionfield = history_dir[:,:,loadTime]
    end
else
    Afield = zeros(T, fieldResY, fieldResX)
    Mfield = zeros(T, fieldResY, fieldResX)
    Ffield = avgF * ones(T, fieldResY, fieldResX)
    Wfield = ones(T, fieldResY, fieldResX)
    directionfield = pi/4 * ones(T, fieldResY, fieldResX)

    # draw membrane circle
    M_circ = zeros(T, fieldResY, fieldResX)

    for r in mR:0.01:(mR+mT-1)
        drawcircle!(M_circ, xc, yc, r)
    end
    Mfield += avgM * M_circ + 0.00001* rand(fieldResY, fieldResX)

    # fill in with autocatalyst
    A_circ = zeros(T, fieldResY, fieldResX)

    for r in 0:0.01:mR-1
        drawcircle!(A_circ, xc, yc, r)
    end

    Afield += avgA * A_circ

    Wfield -= (Mfield + Afield)

    ###
    # directionality initialization
    ###

    # make directionality in a ring
    for q1 in 1:fieldResX
        for q2 in 1:fieldResY
            directionfield[q2,q1] = atan((q2-yc)/(q1-xc))-pi/2

            # correct for 180 degrees
            while directionfield[q2, q1] > pi
                 directionfield[q2, q1] -= pi
            end
            while directionfield[q2, q1] <= 0 # ??? < 0
                 directionfield[q2, q1] += pi
            end
        end
    end

    directionfield[isnan(directionfield)] = 0
end

###
# Data storing
###

# create 3d matrices to store field activities
history_A = Array{T, 2}[]
history_F = Array{T, 2}[]
history_T = Array{T, 2}[]
history_M = Array{T, 2}[]
history_W = Array{T, 2}[]
history_dir = Array{T, 2}[]

#vectors to save global concentrations across time

Avec = T[]
Fvec = T[]
Mvec = T[]
Wvec = T[]
DAvec = T[]
DMvec = T[]

###
# Decide which compute context should be used
# Either OpenCL or Julia
###

if USECL
    include("cl/functions.jl")
else
    include("jl/functions.jl")
end

createComputeContext(T, ctx, queue)

###
# Prepare simulation
###

#define scaled diffusion
diffA = diffusionA*fieldRes/fieldSize
diffF = diffusionF*fieldRes/fieldSize
diffM = diffusionM*fieldRes/fieldSize
diffW = diffusionW*fieldRes/fieldSize

dF = zeros(T, fieldResY, fieldResX)

# create temp arrays
mpot = create(T)
mpot1 = create(T)
mpot2 = create(T)
wpot = create(T)
apot = create(T)
apot1 = create(T)

buff_Mfield = create(T)
buff_Wfield = create(T)
buff_Afield = create(T)
buff_Dfield = create(T)
buff_NewDfield = create(T)
buff_Ffield = create(T)

wlap = create(T)
alap = create(T)
mlap = create(T)
flap = create(T)

row1 = create(T)
row2 = create(T)
row3 = create(T)
row4 = create(T)
row5 = create(T)
row6 = create(T)

dW = create(T)
dA = create(T)
dM = create(T)
buff_dF = create(T)

temp = create(T)
area = create_n4(T)
const_area = create_const_n4(convert(T, 1.0/8.0))

# Make sure that the fields are in the correct DataFormat
Mfield = convert(Array{T}, Mfield)
Afield = convert(Array{T}, Afield)
Wfield = convert(Array{T}, Wfield)
Ffield = convert(Array{T}, Ffield)
directionfield = convert(Array{T}, directionfield)

# Stability and structure
stable = false
timeToStable = -1

old_Mfield = copy(Mfield)
old_Ffield = copy(Ffield)
old_Wfield = copy(Wfield)
old_Afield = copy(Afield)
old_directionfield = copy(directionfield)

###
# Simulation
###

t = 1

meanMField = mean(Mfield)
meanAField = mean(Afield)

###
# Disturbance steps
###
tT = timeTotal
outFileName = ""

###
# Set up dynamic precision
###

precision = 1.0
skipFlag = false
precisionCounter = 0
bufferStep = 2 # Minimum 1.0 increase to allow for higher precision over a second timeStep

previous_Mfield = copy(Mfield)
previous_Ffield = copy(Ffield)
previous_Wfield = copy(Wfield)
previous_Afield = copy(Afield)
previous_directionfield = copy(directionfield)

tx = Float64[]

while (t <= tT) && (meanAField < 1.0) !isnan(meanMField) && !isnan(meanAField)
    previous_Mfield = copy(Mfield)
    previous_Ffield = copy(Ffield)
    previous_Wfield = copy(Wfield)
    previous_Afield = copy(Afield)
    previous_directionfield = copy(directionfield)

	if t in keys(disturbances)
		val = disturbances[t]
		method = first(val)
		args = val[2:end]
		if method == :global && length(args) == 2
			(mu, sig) = args
            mask = !FrefillBinMask
			Mfield += mask .* rand(Normal(mu, sig), (fieldResY, fieldResX))
			Afield += mask .* rand(Normal(mu, sig), (fieldResY, fieldResX))
			Ffield += mask .* rand(Normal(mu, sig), (fieldResY, fieldResX))
			Wfield += mask .* rand(Normal(mu, sig), (fieldResY, fieldResX))
			directionfield += mask .* rand(Normal(mu, sig), (fieldResY, fieldResX))

			# Normalize
			Mfield[Mfield .< 0] = 0
			Afield[Afield .< 0] = 0
			Ffield[Ffield .< 0] = 0
			Wfield[Wfield .< 0] = 0

			directionfield = map(x -> mod(x, pi), directionfield)
		elseif method == :punch_local
			(x, y, alpha, beta) = args
			apply_punch_down!(Mfield, x, y, alpha, beta)
			apply_punch_down!(Afield, x, y, alpha, beta)
			apply_punch_down!(Ffield, x, y, alpha, beta)
			apply_punch_down!(Wfield, x, y, alpha, beta)
            # apply_punch_down!(directionfield, x, y, alpha, beta)
        elseif method == :punch_random
            (alpha, beta) = args
            lin_idx = sample(find((s) -> s > 0.5, Mfield))
            x, y = ind2sub(size(Mfield), lin_idx)
            apply_punch_down!(Mfield, x, y, alpha, beta)
            apply_punch_down!(Afield, x, y, alpha, beta)
            apply_punch_down!(Ffield, x, y, alpha, beta)
            apply_punch_down!(Wfield, x, y, alpha, beta)
		elseif method == :gaussian_blur && length(args) == 1
			sigma = args[1]
			imfilter_gaussian_no_nans!(Mfield, [sigma,sigma])
			imfilter_gaussian_no_nans!(Afield, [sigma,sigma])
			imfilter_gaussian_no_nans!(Ffield, [sigma,sigma])
			imfilter_gaussian_no_nans!(Wfield, [sigma,sigma])
			# directionfield = imfilter_gaussian(directionfield, [sigma,sigma])
		elseif method == :tear_membrane && length(args) == 1
			tearsize = args[1]

		    tS = iround(fieldRes/2+tearSize*fieldRes/fieldSize)
		    t0 = iround(fieldRes/2)
		    Mfield[fieldRes/2:fieldRes,t0:tS] = 0
		elseif method == :tear_dfield && length(args) == 1
			tearsize = args[1]

		    tS = iround(fieldRes/2+tearSize*fieldRes/fieldSize)
		    t0 = iround(fieldRes/2)
		    directionfield[fieldRes/2:fieldRes,t0:tS] = pi .* rand(iround(fieldRes-fieldRes/2+1),tS-t0+1)
		elseif method == :tear && length(args) == 1
			tearsize = args[1]

		    tS = iround(fieldRes/2+tearSize*fieldRes/fieldSize)
		    t0 = iround(fieldRes/2)

		    Mfield[fieldRes/2:fieldRes,t0:tS] = 0
		    directionfield[fieldRes/2:fieldRes,t0:tS] = pi .* rand(iround(fieldRes-fieldRes/2+1),tS-t0+1)
		else
			warn("Don't know how to handle $method with arguments $args, carry on.")
		end
	end

    ###
    # Pushing the fields on the GPU
    ###

    c_copy!(buff_Mfield, Mfield)
    c_copy!(buff_Wfield, Wfield)
    c_copy!(buff_Afield, Afield)
    c_copy!(buff_Ffield, Ffield)
    c_copy!(buff_Dfield, directionfield)

    ###
    # calculate potential based on repulsion
    ###
    area!(buff_Dfield, area)

    potential!(buff_Mfield, buff_Wfield, area, mpot, wpot, MW_repulsion)
    potential!(buff_Mfield, buff_Afield, area, mpot1, apot, MA_repulsion)
    potential!(buff_Mfield, buff_Mfield, area, mpot2, temp, MM_repulsion)

    potential!(buff_Afield, buff_Afield, const_area, apot1, temp, AA_repulsion)

    c_add!(apot, apot1)
    c_add!(mpot, mpot1)
    c_add!(mpot, mpot2)

    ###
    # move molecules and update directionality
    ###

    diffusion!(buff_Mfield, mpot, mlap)
    smul!(mlap, diffM)

    diffusion!(buff_Wfield, wpot, wlap)
    smul!(wlap, diffW)

    diffusion!(buff_Afield, apot, alap)
    smul!(alap, diffA)

    # Laplacian for diffusion
    # Todo calculate in on GPU to be coherent and to minimize data transfers.
    laplacian!(buff_Ffield, flap)
    smul!(flap, diffF)

    # update direction field based on alignment
    align!(buff_Mfield, buff_Dfield, buff_NewDfield)
    c_copy!(directionfield, buff_NewDfield)

    ###
    # reactions
    ##

    calcRow!(buff_Mfield, buff_Afield, buff_Ffield, buff_Wfield, row1, m11, a11, f11, w11)
    calcRow!(buff_Mfield, buff_Afield, buff_Ffield, buff_Wfield, row2, m12, a12, f12, w12)
    calcRow!(buff_Mfield, buff_Afield, buff_Ffield, buff_Wfield, row3, m21, a21, f21, w21)
    calcRow!(buff_Mfield, buff_Afield, buff_Ffield, buff_Wfield, row4, m22, a22, f22, w22)
    calcRow!(buff_Mfield, buff_Afield, buff_Ffield, buff_Wfield, row5, m31, a31, f31, w31)
    calcRow!(buff_Mfield, buff_Afield, buff_Ffield, buff_Wfield, row6, m32, a32, f32, w32)

    ##
    # Calculate dA
    ##

    delta!(row1, row2, row3, row4, row5, row6, dA,
            fsm(a12, a11, kf1), fsm(a11, a12, kb1), fsm(a22, a21, kf2), fsm(a21, a22, kb2), fsm(a32, a31, kf3), fsm(a31, a32, kb3))
    delta2!(dA, buff_Mfield, buff_Afield, alap, dA, decayA)

    ##
    # Calculate dM
    ##

    delta!(row1, row2, row3, row4, row5, row6, dM,
            fsm(m12, m11, kf1), fsm(m11, m12, kb1), fsm(m22, m21, kf2), fsm(m21, m22, kb2), fsm(m32, m31, kf3), fsm(m31, m32, kb3))
    delta2!(dM, buff_Afield, buff_Mfield, mlap, dM, decayM)

    ##
    # Calculate dW
    ##

    delta!(row1, row2, row3, row4, row5, row6, dW,
            fsm(w12, w11, kf1), fsm(w11, w12, kb1), fsm(w22, w21, kf2), fsm(w21, w22, kb2), fsm(w32, w31, kf3), fsm(w31, w32, kb3))

    c_add!(dW, wlap)

    ##
    # Calculate dF
    ##

    delta!(row1, row2, row3, row4, row5, row6, buff_dF,
            fsm(f12, f11, kf1), fsm(f11, f12, kb1), fsm(f22, f21, kf2), fsm(f21, f22, kb2), fsm(f32, f31, kf3), fsm(f31, f32, kb3))

    c_add!(buff_dF, flap)

    c_copy!(dF, buff_dF)

    dF[FrefillBinMask] += flowRateF * (saturationF .- Ffield[FrefillBinMask])

    dT = 0

    # update values
    smul!(dA, (stepIntegration / precision))
    c_add!(buff_Afield, dA)
    c_copy!(Afield, buff_Afield)

    Ffield += dF * (stepIntegration / precision)

    smul!(dM, (stepIntegration / precision))
    c_add!(buff_Mfield, dM)
    c_copy!(Mfield, buff_Mfield)

    smul!(dW, (stepIntegration / precision))
    c_add!(buff_Wfield, dW)
    c_copy!(Wfield, buff_Wfield)

    # calulate stability criteria

    sumabs_dA = sumabs(c_read(dA))
    sumabs_dM = sumabs(c_read(dM))
    sumabs_dF = sumabs(dF)
    sumabs_dW = sumabs(c_read(dW))

    negativeValues = any(Mfield .< 0) || any(Afield .< 0) || any(Ffield .< 0) || any(Wfield .< 0)

    if negativeValues
        warn("Integration error at t=$t increase precision from $precision to $(2 * precision)")
        precision = 2 * precision

        if precision > 1024
            error("Precision just exceeded 1024 clearly something is wrong here.")
        end

        Mfield = copy(previous_Mfield)
        Ffield = copy(previous_Ffield)
        Wfield = copy(previous_Wfield)
        Afield = copy(previous_Afield)
        directionfield = copy(previous_directionfield)
        skipFlag = true

        # If we increase it for the first time
        if precisionCounter == 0
            precisionCounter = bufferStep*precision
        else
            precisionCounter *= 2
        end
    end

    #save values for visualization
    meanAField = mean(Afield)
    meanMField = mean(Mfield)

    if !skipFlag
        stableCondition = (sumabs_dA < epsilon / precision) && (sumabs_dM < epsilon / precision) #&& (sumabs_dF < epsilon) && (sumabs_dW < epsilon)

        if !stable && stableCondition
            println("reached a stable config after $t")
            stable = true
            tT = t + stableTime
            timeToStable = t
        elseif stable && !stableCondition
            stable = false
            tT = t + timeTotal
            println("Lost stability")
        end

        push!(Avec, meanAField)
        push!(Mvec, meanMField)
        push!(Fvec, mean(Ffield))
        push!(Wvec, mean(Wfield))
        push!(DAvec, sumabs_dA)
        push!(DMvec, sumabs_dM)

        if (t % storeStep == 0)
          push!(history_A, Afield)
          push!(history_F, Ffield)
          push!(history_M, Mfield)
          push!(history_W, Wfield)
          push!(history_dir, directionfield)
        end

        push!(tx, t)
    end

    if enableVis && (t % visInterval == 0)
        if !isready(gui_rref)
            println()
            println("Waiting for GUI to be initialized.")
            @time wait(gui_rref)
        end

        @spawnat guiproc begin
          hold(false)
          # Timeseries plot

          subplot(241)
          plot(tx, Avec, "-", linewidth=2)
          title("Avec")

          subplot(242)
          plot(tx, Mvec, "-", linewidth=2)
          title("Mvec")

          subplot(243)
          plot(tx, DAvec, "-", linewidth=2)
          title("DAvec")
          axis([0, t, 0, 0.4])

          subplot(244)
          plot(tx, DMvec, "-", linewidth=2)
          title("DMvec")
          axis([0, t, 0, 0.4])

          subplot(245)
          pcolormesh(Mfield, vmin=0, vmax=0.6)
          title("Mfield")

          subplot(246)
          pcolormesh(Afield, vmin=0, vmax=0.6)
          title("Afield")

          subplot(247)
          pcolormesh(Ffield, vmin=0, vmax=1)
          title("Ffield")

            subplot(248)
            title("DirectionField")

            U = cos(directionfield)
            V = sin(directionfield)
            d1, d2 = size(directionfield)
            plt.quiver([1:d1], [1:d2], U, V, linewidth=1.5, headwidth = 0.5)
        end
    end

    yield() # to be able to answer question about the status
    if !skipFlag
        if precisionCounter > 0
            precisionCounter -= 1
            t += (stepIntegration / precision)
        else
            t += stepIntegration
        end
    else
        skipFlag = false
    end

    if precisionCounter == 0 && precision > 1.0
        if t % stepIntegration != 0
            warn("t=$t and integration=$stepIntegration t % integration should be zero but it is. $(t%stepIntegration)")
        end
        warn("Resetting precision to 1.0 at t=$t")
        precision = 1.0
    end
end # While

###
# Find the reason we terminated
###

print("Finished because after $t: ")
if t > tT
  println("Time done")
elseif isnan(meanMField) || isnan(meanAField)
   println("Found NaNs")
elseif meanAField >= 1.0
    println("Mean of Afield is $meanAField >= 1.0")
end

###
# Export
###
try
    dt=now()
    stack(X) = reshape(hcat(X...), fieldResY, fieldResX, length(X))
    result = {
        "history_W" => stack(history_W),
        "history_A" => stack(history_A),
        "history_M" => stack(history_M),
        "history_F" => stack(history_F),
        "history_dir" => stack(history_dir),
        "Avec" => Avec,
        "Fvec" => Fvec,
        "Mvec" => Mvec,
        "Wvec" => Wvec,
        "DAvec" => DAvec,
        "DMvec" => DMvec,
        "tx" => tx
    }
    merge!(result, saveConfig(collect(keys(baseConfig))))
    prefix = if cluster
        "$resultFolder/$(myid())-"
    else
        "$resultFolder/"
    end
    outFileName = "$prefix$(year(dt))-$(month(dt))-$(day(dt))_$(hour(dt)):$(minute(dt)):$(second(dt)).mat"
    matwrite(outFileName, result)
catch e
    warn("Write failed because of $e")
end

if !(worker || cluster)
println("Press any key to exit program.")
readline(STDIN)
end

structM = sumabs(old_Mfield .- Mfield)
structA = sumabs(old_Afield .- Afield)
structF = sumabs(old_Ffield .- Ffield)
structW = sumabs(old_Wfield .- Wfield)
structd = sumabs(old_directionfield .- directionfield)

valueOfInterest =   if stable
                        if (meanMField >= deathEpsilon) || (meanAField >= deathEpsilon)
                            timeToStable # stable and living
                        else
                            -timeToStable # death through diffusion and decay
                        end
                    elseif (meanAField >= 1.0) || isnan(meanMField) || isnan(meanAField) # explosion
                        - t
                    end

destroyComputeContext()

return (t, timeToStable, valueOfInterest, stable, meanMField, meanAField, structM, structA, structF, structW, structd, outFileName)
end #Function

function fsm(x, y, k)
  if (k == zero(k)) || (x == y)
    return zero(k)
  else
    return ((x-y) * k )
  end
end