text:
### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

# ╔═╡ c8590734-c738-4225-9e4e-372f5ef7a63c
using Plots

# ╔═╡ bae4a3e4-2224-4e5d-ab15-bbdf86310ba8
begin
	using PlutoUI
	PlutoUI.TableOfContents(title = "Contents")
end

# ╔═╡ 133172e0-ee0f-11eb-3d88-efecd8115ca7
md"""
# Modelling infectious diseases
"""

# ╔═╡ 0f33ac97-eb86-40cf-aa65-8ffb51cdf37c
md"""
## Objectives
- Create and compare two different ``SIR`` models for infectious disease
  - Mathematically, the classical ``SIR`` model given in terms of differential equations
  - Computationally, an ``SIR`` model considering proximity of individuals
- _**Consider methods of increasing efficiency in Julia code**_
"""

# ╔═╡ 70d33a7a-edac-421c-a6e0-7acfd523871e
md"""
## What is an SIR model?

An ``SIR`` model models an infectious disease by splitting the population into three categories
- **Susceptible** (``S``) - Those who are susceptible to the disease, having not come into contact with it yet
- **Infectious** (``I``) - Those who have the disease and are able to pass it on to the susceptible population
- **Recovered** (``R``) - Those who have had the disease in the past but can no longer pass it on. This category is sometimes called **Removed** to be more inclusive to those who died from the disease

The model then has rules for how the size of the three categories changes with time. In the models in this case study, the total population `` N = S + I + R `` is fixed, but this doesn't have to be the case.

The two models that I will create in the following studies are:
- A simple yet effective model based on an intuitive system of differential equations, which I will refer to as the [mathematical model](#ed6cab5f-4fdb-4680-9978-6024aa57595c)
- A more complicated model attempting to simulate the disease passing between individuals, which I will refer to as the [computational model](#99d6153f-38ae-4ba4-a96e-509e9094bdb1)
"""

# ╔═╡ ed6cab5f-4fdb-4680-9978-6024aa57595c
md"""
## Mathematical model

The most simple and elegant ``SIR`` model is given by three differential equations:

```math
\frac{dS}{dt} = - \beta I S
```
```math
\frac{dI}{dt} = \beta I S - \nu I
```
```math
\frac{dR}{dt} = \nu I
```

although since we know that `` S + I + R `` is fixed, and the equations do not depend directly on ``R``, the first two will suffice. Here, ``\beta`` is the rate of infection, and ``\nu`` is the rate of recovery, which are parameters that I will be able to change later.

To implement this in Julia, I will use the Forward Euler algorithm where

```math
\frac{dF}{dt} = f(t), \qquad F(0) = F_0
```

can be approximated for `` t > 0 `` by iteration of the form

```math
F(0) = F_0, \qquad F((n+1)\delta{}t) \approx F(n\delta{}t) + f(n\delta{}t) \delta{}t
```

where ``\delta{}t`` is some chosen step size (smaller is more accurate but requires more computation).

I start by choosing `δt` and a final time `T` up to which I will run the simulation.
"""

# ╔═╡ 728e28a5-d807-4751-858c-4a94873463be
δt = 0.001

# ╔═╡ 2598752b-f99f-457b-8192-b9e256c72e1e
T = 1

# ╔═╡ 828ec2df-aa76-44ff-b951-880e159cb82f
times = 0:δt:T

# ╔═╡ 262d57fa-8ab8-4a9e-a826-3c8550692811
ntimes = length(times)

# ╔═╡ fb7bb03b-bcf3-4d2c-a066-294256e2d92e
md"""
I also choose a population size `N`. The subscript `₁` denotes that this is the first model of the two.
"""

# ╔═╡ dc26b16a-25ba-4b16-9b52-16b912c4b8bb
N₁ = 10000

# ╔═╡ b75775ab-a852-4571-8e59-63b5083db3c2
md"""
I initialise vectors the same size as the list of times to track ``S`` and ``I`` (as mentioned earlier there is no need to track ``R``), with the initial values being one infectious individual and the rest susceptible.
"""

# ╔═╡ 5363d193-fb8a-469d-9e87-09ece19a01fe
S₁ = zeros(size(times));

# ╔═╡ 1751daa5-78e7-471c-8576-1c9acc2dd68c
S₁[1] = N₁ - 1;

# ╔═╡ 0533a017-889b-4f72-ad21-58f762f4fae2
I₁ = zeros(size(times));

# ╔═╡ 547bc8b9-22fb-4143-9eb6-8f2b4090ed65
I₁[1] = 1;

# ╔═╡ 64f43f00-eacb-4039-81b0-d09b94af8923
md"""
Finally, I choose values for the two parameters `β` and `ν`.
"""

# ╔═╡ 3fa04a1c-25eb-4fd4-aa77-f0296c83dd31
β = 0.005

# ╔═╡ 688bebd2-2d88-441c-90f6-9597de92a176
ν = 4

# ╔═╡ 310433cf-f3a6-4e4a-8f2c-c5d5967b5ad4
md"""
Forward Euler iteration can now begin. I am careful to make sure that `S`, `I`, and `S + I` remain between `0` and `N` at all times, although the model has clearly failed if this ever becomes a problem.
"""

# ╔═╡ 907983f7-0950-49ae-ba41-85da7ac577ed
for i ∈ 2:ntimes
    δS₁ = - β * I₁[i-1] * S₁[i-1] * δt
    δI₁ = - δS₁ - ν * I₁[i-1] * δt
    S₁[i] = min(max(S₁[i-1] + δS₁, 0),N₁)
    I₁[i] = min(max(I₁[i-1] + δI₁, 0),N₁ - S₁[i])
end

# ╔═╡ 16ff8546-92f0-4578-9310-28d8db1cb6e2
md"""
I can now calculate `R` from the values obtained for `S` and `I`.
"""

# ╔═╡ e20a2623-e0bc-4421-8726-8f81554d63b1
R₁ = N₁ .- (S₁ + I₁);

# ╔═╡ 1753a908-a344-40f6-b25d-6dbc124f5272
md"""
The `Plots` package allows me to visualise the results, which I will do in two forms, a line graph, and a stacked area graph.
"""

# ╔═╡ 5bb45fc2-1361-4555-952d-d50f9e6aea77
lineplot₁ = plot(
	times,
	[S₁ I₁ N₁ .- (S₁ + I₁)],
	label = ["Susceptible" "Infectious" "Recovered"],
	color = [:gold :red :blue]
)

# ╔═╡ 7fe01089-0b35-417e-b857-1b0ba3653ccd
areaplot₁ = areaplot(
	times,
	[S₁ I₁ N₁ .- (S₁ + I₁)],
	label = ["Susceptible" "Infectious" "Recovered"],
	color = [:gold :red :blue]
)

# ╔═╡ e794f4a0-fcad-427e-af49-7fd20ed11a10
md"""
An interesting alteration to make to this model is to introduce some randomness, for which I will need to introduce a vector to track ``R``. The randomness I will create by multiplying `δS`, `δI`, and `δR` by log-normally distributed random variables of parameters `μ = 0`, and `σ` a parameter I can set (note that `σ = 0` is equivalent to no randomness), and then normalising to keep `S + I + R` fixed.

It is now particularly essential that `S`, `I`, and `R` are kept between `0` and `N` since the randomness increases the chance for them to end up outside of that range.
"""

# ╔═╡ b3e54a43-a997-43bf-994c-19100d49f765
S₁′ = zeros(size(times));

# ╔═╡ f33ac3be-782d-4183-9c84-ee9c161d33ee
S₁′[1] = N₁ - 1;

# ╔═╡ 3b63e289-dddf-4460-bde9-ce6acd7607ab
I₁′ = zeros(size(times));

# ╔═╡ 6b7a449f-3fcc-457d-b268-39fddda134a2
I₁′[1] = 1;

# ╔═╡ 8c207e61-241c-4af1-a1dd-0f5580157de8
R₁′ = zeros(size(times));

# ╔═╡ 61c09dcb-b03a-485d-a954-9ebbc9501d35
σ = 1

# ╔═╡ 1641868a-a25b-499d-bc72-e6fff52036bc
for i ∈ 2:ntimes
    δS₁′ = - β * I₁′[i-1] * S₁′[i-1] * δt
	δR₁′ = ν * I₁′[i-1] * δt
	δI₁′ = - δS₁′ - δR₁′
    S₁′[i] = max(S₁′[i-1] + δS₁′ * exp(σ * randn()), 0)
    I₁′[i] = max(I₁′[i-1] + δI₁′ * exp(σ * randn()), 0)
	R₁′[i] = max(R₁′[i-1] + δR₁′ * exp(σ * randn()), 0)
	scale = (S₁′[i] + I₁′[i] + R₁′[i])/N₁
	S₁′[i] /= scale; I₁′[i] /= scale; R₁′[i] /= scale
end

# ╔═╡ 4670197e-4c4e-49ec-ad7d-ffdd0701631e
md"""
Plotting `S`, `I` and `R` now gives:
"""

# ╔═╡ d2821018-da14-45c2-a98e-47edf882a867
plot(
	times,
	[S₁′ I₁′ R₁′],
	label = ["Susceptible" "Infectious" "Recovered"],
	color = [:gold :red :blue]
)

# ╔═╡ a52bbb3c-3f25-46a0-819c-dce56bd7c75c
areaplot(
	times,
	[S₁′ I₁′ R₁′],
	label = ["Susceptible" "Infectious" "Recovered"],
	color = [:gold :red :blue]
)

# ╔═╡ 7a039123-2afd-4f2d-b47b-d05f3cb4f13c
md"""
There is only a small effect of randomness on making the curves less smooth. What it does do, however, is dramatically quicken both processes of infection and recovery. This can be explained as the exponential nature of the processes causing a feedback loop which amplifies the effects of random increases in rate while dulling the effects of random decreases in rate.
"""

# ╔═╡ 99d6153f-38ae-4ba4-a96e-509e9094bdb1
md"""
## Computational model
The second model which I will use simulates a population of ``N`` people with the disease propagating only between neighbours:
- ``N`` people are arranged in a grid, with one starting out as infectious and the rest susceptible
- At each step:
  - Any infectious individual passes on the disease to any of their four neighbours independently with probability ``p``
  - Any infectious individual recovers with probability ``q`` (can happen on the same step as passing on the disease, but not in the same step as getting passed the disease)
- This is run for a predetermined number of steps, with the numbers ``S``, ``I``, and ``R`` kept track of at all times

This model has some features that the first does not which may make it more realistic, such as:
- The population is discrete, allowing the disease to die out more easily
- The disease is localised so cannot infect those who are not in contact with it

However, due to the simulation of individuals rather than the simulation of the population as a whole, this is inevitably more computationally intensive. Hence, efficiency will be essential to make this model usable.

### Setting up the model
To begin with, I will set up the parameters for the model.
"""

# ╔═╡ df4d71fd-a752-46fd-bb18-908a23ee0cd0
sqrtN₂ = 100

# ╔═╡ 1086d559-ef39-405a-a113-a060b391ad9e
N₂ = sqrtN₂^2

# ╔═╡ 25922ea7-4054-4054-ba4a-f93dbee85503
p = 0.4

# ╔═╡ b39fb04a-1074-4441-9d6d-3612674475d9
q = 0.01

# ╔═╡ a87ed703-f514-41cf-8262-b5ace0a28e45
maxsteps = 500

# ╔═╡ b24a796c-4908-4ff9-b392-0781574829e4
md"""
The population of size `N₂` will be stored as a matrix of numbers (`sqrtN₂` by `sqrtN₂`), where `1` represents susceptible, `2` represents infectious, and `3` represents recovered.
"""

# ╔═╡ 7709c287-c7b8-4ffe-a257-4ada27454db3
population = fill(1, (sqrtN₂, sqrtN₂));

# ╔═╡ fdc5dd62-50e7-4423-880b-1dbde1aebea3
population[rand(1:N₂)] = 2;

# ╔═╡ a565879c-2938-4f5b-8de8-9807cdff3948
md"""
As before, vectors `S₂` and `I₂` will keep track of the number of susceptible and infectious individuals respectively after each step.
"""

# ╔═╡ d74fe014-47a1-4636-9360-83fc311307d2
S₂ = zeros(maxsteps+1);

# ╔═╡ b8903866-81e9-42b0-8bd7-263d5cbaf2b4
S₂[1] = N₂ - 1;

# ╔═╡ 94272156-621b-4ed5-8582-c079625fa977
I₂ = zeros(maxsteps+1);

# ╔═╡ ef4051e0-38f2-4dc6-a765-b0c76a4b0705
I₂[1] = 1;

# ╔═╡ 8fbf94ce-50f5-48a6-91ce-d2f67fa505e4
md"""
As an additional form of output, I will create an animation of the population at each stage, using the `Plots` package. I use the function `populationplot` to convert the matrix `population` into a heatmap where `1` is yellow, `2` is red, and `3` is blue (which is why I use numbers in the matrix). In order to maintain these colours, three additional pixels of each of these values are added, as otherwise `Plots` will change the scale if the range of values of the plot is not 1 to 3.
"""

# ╔═╡ dfc867ac-d8d7-4f90-9410-03746e134443
function populationplot(population::Matrix{Int64})
    return heatmap(
        hcat(population, fill(missing, sqrtN₂),
			[1,2,3, fill(missing, sqrtN₂ - 3)...]),
        legend = false,
        color = [:gold, :red, :blue],
        size = (4*sqrtN₂,4*sqrtN₂),
        showaxis = false,
        ticks = false
    )
end

# ╔═╡ f4e7d095-13c3-40dc-b248-61d8a065d61c
begin
	anim₂ = Animation()
	frame(anim₂, populationplot(population))
end;

# ╔═╡ 559aeb30-0d2e-4077-9af1-b4ed4702f9e5
md"""
I am now ready to carry out the iteration. For each step, I would like to do the following:

- Initialise empty lists of indices for points which will become red and points that will become blue
```julia
reds = CartesianIndex{2}[]
blues = CartesianIndex{2}[]
```

- Iterating over each member of the population, check whether they are infectious
```julia
for j ∈ 1:sqrtN₂, i ∈ 1:sqrtN₂
    if population[i,j] == 2
		# loop
	end
end
```

- For those which are infectious, infect each susceptible neighbour with independent probability `p`, and recover with probability `q`, adding the appropriate indices to the lists `reds` and `blues`
```julia
i > 1 && population[i-1,j] == 1 && rand() < p && push!(reds,CartesianIndex(i-1,j))
j > 1 && population[i,j-1] == 1 && rand() < p && push!(reds,CartesianIndex(i,j-1))
i < sqrtN₂ && population[i+1,j] == 1 && rand() < p &&
	push!(reds,CartesianIndex(i+1,j))
j < sqrtN₂ && population[i,j+1] == 1 && rand() < p &&
	push!(reds,CartesianIndex(i,j+1))
rand() < q && push!(blues,CartesianIndex(i,j))
```

- Once this is complete, update the status of the population with the new `reds` and `blues`
```julia
population[reds]  .= 2
population[blues] .= 3
```

- Create the next frame of the animation
```julia
frame(anim₂, populationplot(population))
```

- Calculate the new number of susceptible and infectious
```julia
nnewreds, nnewblues = (length ∘ unique! ∘ sort!)(reds), length(blues)
S₂[n] = S₂[n-1] - nnewreds
I₂[n] = I₂[n-1] + nnewreds - nnewblues
```

- If the disease has died out (i.e. no more infectious), stop the loop, since there is no point in continuing
```julia
I₂[n] == 0 && (S₂ = S₂[1:n]; I₂ = I₂[1:n]; break)
```

This I combine into one large loop.
"""

# ╔═╡ dc815f4e-a731-4e6d-987d-9a9db7ee5270
for n ∈ 2:(maxsteps+1)
    reds = CartesianIndex{2}[]
    blues = CartesianIndex{2}[]
    for j ∈ 1:sqrtN₂, i ∈ 1:sqrtN₂
        if population[i,j] == 2
            i > 1      && population[i-1,j] == 1 && rand() < p &&
				push!(reds,CartesianIndex(i-1,j))
            j > 1      && population[i,j-1] == 1 && rand() < p &&
				push!(reds,CartesianIndex(i,j-1))
            i < sqrtN₂ && population[i+1,j] == 1 && rand() < p &&
				push!(reds,CartesianIndex(i+1,j))
            j < sqrtN₂ && population[i,j+1] == 1 && rand() < p &&
				push!(reds,CartesianIndex(i,j+1))
            rand() < q && push!(blues,CartesianIndex(i,j))
        end
    end

    population[reds]  .= 2
    population[blues] .= 3
    frame(anim₂, populationplot(population))

    nnewreds, nnewblues = (length ∘ unique! ∘ sort!)(reds), length(blues)
    S₂[n] = S₂[n-1] - nnewreds
    I₂[n] = I₂[n-1] + nnewreds - nnewblues
    I₂[n] == 0 && (S₂ = S₂[1:n]; I₂ = I₂[1:n]; break)
end

# ╔═╡ 3e2119c2-9482-482b-8caf-f9d386ae0522
md"""
From `S` and `I`, I calculate `R` as before.
"""

# ╔═╡ 466ab0b6-fb5e-4354-ae91-20c7bc20a583
R₂ = N₂ .- (S₂ + I₂);

# ╔═╡ 8ef0ac34-d033-4515-af2d-9f44c6749977
md"""
Finally, the visualisations can be created. First, I plot the animation of the spread of the infection, and then the same two graphs as were plotted for the first model.
"""

# ╔═╡ ba404ab9-794d-499f-b41a-7601a994e42e
 gif₂ = Plots.gif(anim₂)

# ╔═╡ fa9bcbb8-927f-45cd-96c1-14ff760b2af8
lineplot₂ = plot(
	0:length(S₂)-1,
	[S₂ I₂ R₂],
	label = ["Susceptible" "Infectious" "Recovered"],
	color = [:gold :red :blue]
)

# ╔═╡ fff03d6e-741b-48a6-b90f-89a52f4d707e
areaplot₂ = areaplot(
	0:length(S₂)-1,
	[S₂ I₂ R₂],
	label = ["Susceptible" "Infectious" "Recovered"],
	color = [:gold :red :blue]
)

# ╔═╡ df111c85-33bf-4ca1-846e-a20e3491dcbd
md"""
### Efficiency considerations

The second model took noticeably more effort for me to program than the first. This is mainly because it is more complicated, and in this instance, with complexity comes slowness. For this reason, I had to make many changes to my initial attempt in order to optimise it.

Here is a snippet of an early draft of the program for the second model:
```julia
population = fill(:gold, (sqrtN₂, sqrtN₂))
population[rand(1:N₂)] = :red

anim₂ = Animation()
function populationplot(population::Matrix{Symbol})
	return scatter(
		[(i,j) for j ∈ 1:sqrtN₂, i ∈ 1:sqrtN₂][:],
		markersize = 3,
		markercolor = population[:],
		markerstrokewidth = 0,
		size = (4*sqrtN₂, 4*sqrtN₂),
		legend = false,
		showaxis = false,
		ticks = false
	)
end
frame(anim₂,populationplot(population))

for n ∈ 2:(maxsteps+1)
	newpopulation = copy(population)
	for j ∈ 1:sqrtN₂, i ∈ 1:sqrtN₂
        if population[i,j] == :red
            i > 1 && population[i-1,j] == :gold && rand() < p &&
				newpopulation[i-1,j] = :red
            j > 1 && population[i,j-1] == :gold && rand() < p &&
				newpopulation[i,j-1] = :red
            i < sqrtN₂ && population[i+1,j] == :gold && rand() < p &&
				newpopulation[i+1,j] = :red
            j < sqrtN₂ && population[i,j+1] == :gold && rand() < p &&
				newpopulation[i,j+1] = :red
            rand() < q && newpopulation[i,j] = :blue
        end
    end

	population = newpopulation
	frame(anim₂,populationplot(population))

	S₂[n] = count(==(:gold), population)
	I₂[n] = count(==(:red), population)
	I₂[n] == 0 && (S₂ = S₂[1:n]; I₂ = I₂[1:n]; break)
end
```
"""

# ╔═╡ 9756b3f3-e826-4b6d-ac4f-54ea7a5d43d1
md"""
With experience coding in Julia (and in general), you can pick up tips and tricks for efficiency that become almost automatic for you to include. Some examples of this in this instance are already included in my early draft, and some could be added in:

- Julia orders the elements of matrices by columns then rows, i.e the next element of a matrix `A` after `A[i,j]` is `A[i+1,j]` (the element below it). Note that this is opposite to some other languages, such as Python. A consqeuence of this is that when looping over matrices, the outer loops should loop over the columns, and the inner loop over the rows, since then the entries are being accessed in exactly the order that they lie in memory. In the example above, this is the reason why I have written:
```julia
for j ∈ 1:sqrtN₂, i ∈ 1:sqrtN₂
```
- Another way of fixing this is to use `eachindex`, which gives an efficient way of iterating over an array (with syntax `for i ∈ eachindex(A)`). This is better when the row and column indices are irrelevant to the operation inside the loop, but I needed the indices, it isn't the right choice here.

- If multiple conditions need to be checked, it makes sense to check the fastest and/or most likely to fail first, since then the loop can move on quicker. This is seen in the draft, where the probability `p` checks are only made after checking that the target individual is susceptible, since that is a quick operation and is quite likely to not be true

- There is no need to work with large amounts of data when a small amount will do, in particular the matrix `newpopulation`, which is inefficiently copied from `population` only for most of the entries to remain the same since no infection or recovery happened there. This I fixed by introducing the lists `reds` and `blues` of points which have become red/blue, and then changing the values in the matrix `population` once finished, as can be seen in the final program. The `CartesianIndex` type is used to store these indices since it allows the assignment of values to a list of indices with `.=` as shown below, while storing indices as a vector of tuples or as a matrix would not give as neat a solution.
```julia
reds = CartesianIndex{2}[]
blues = CartesianIndex{2}[]
⋮
population[reds]  .= :red
population[blues] .= :blue
```

- Since I now have the list of `reds` and `blues` that are changing at this step, there is no need to count `S[n]` and `I[n]` so inefficiently. Instead, I need only count how many are changing and add to / subtract from the previous value. For `blues`, this is simple, but for `reds` there could be duplicates (if two infectious people infect the same susceptible person that they both neighbour). `unique!` would do this, but the documentation notes that if the order of the original points isn't needed (which it isn't), then `unique! ∘ sort!` is better.
```julia
nnewreds, nnewblues = (length ∘ unique! ∘ sort!)(reds), length(blues)
S₂[n] = S₂[n-1] - nnewreds
I₂[n] = I₂[n-1] + nnewreds - nnewblues
```

- More examples of this sort of optimisation can be found at <https://docs.julialang.org/en/v1/manual/performance-tips/>
"""

# ╔═╡ 6733d7f2-c434-4719-9ae8-e15fa58779ce
md"""
The next place to look for improvements in efficiency is in profiling, which helps to find which places in the code are taking the longest, and where improvements to be made. This is implemented by the inbuilt package `Profile`, although for better visualisation, the package `ProfileView` is needed.
```julia
using ProfileView
```
Profiling works best for testing individual functions, although it is important to make sure that the functions are compiled first before running the test (this can be achieved by running the function, or by profiling twice and taking the second result as true). However, in this instance, I don't have a single function to test, but an entire program, so I run the program once to compile it, and then run
```julia
ProfileView.@profview include("<file-path>")
```
This produces a flame graph, which is a graph made up of stacked horizontal bars. Each bar represents a function called within the process of running the program, with the length denoting the length of time spent in that function, and each bar lying below all the bars representing the functions that it calls within that time. Usually, the best place to look for potential improvements are the longest bars, constituting the functions that are the slowest / called the most times within the program. Once you have identified these places, you can consider how you can rewrite these sections of code to speed them up. Flame graphs can also be saved to compare different iterations of the program, which can also help this process.

An advantage of using editors is that they can also have their own inbuilt tools for profiling. One example of this is in VSCode, where the Julia extension comes with an inbuilt package `VSCodeServer` with its own version of `@profview`. Again, run the function or program once, before running
```julia
VSCodeServer.@profview include("<file-path>")
```
Instead of a flame graph, this produces a list of all of the functions called in the process of running ordered by "Self Time", that is the time for the functions themselves to run, ignoring those functions that they call. It also annotates the code with times, showing the lines of code which take the most. The flame graph from `ProfileView` can be obtained from the same run with
```julia
ProfileView.view()
```
Both methods of visualisation of the profile are valuable to understand where to look for gains in efficiency.

In the case of my program, I found that the majority of the time came from calls to `Plots` functions (and related packages, such as `GR` which is the backend that I am using for generating the graphs). My initial thought was that this may be because drawing the entire graph after each step may be inefficient, but amending this to instead add red and blue points to the graph at each step made little difference. This seems to be because with so many points on the graph, drawing it in order to capture a frame gets slower and slower with each step.

The solution that I settled on was to shift from manually drawing a scatter graph from a matrix of colours to drawing a heatmap from a matrix of numbers with custom colours. The heatmap is plotted similarly to the scatter graph, however it is already optimised to be efficient, unlike my own code, so it is a better choice.
"""

# ╔═╡ 53984f21-33d9-46b8-b08c-d15e044f2752
md"""
If the program still isn't fast enough, it is possible that more radical changes need to be made. The algorithm itself may be at fault, in which case there is nothing more that you can do then attempt to alter it or look elsewhere for a better one (if the constraints of the project allow you to do so). An important (but by no means only) consideration to make is the complexity of the algorithm, that is the rate at which the speed of the algorithm scales with increasing the size of the inputs or relevant parameters. Lower complexity algorithms tend to be faster, although if a large amount of overhead is required to lower the complexity it may not be worthwhile for realistically sized inputs.

For the mathematical model, the most relevant parameter is `ntimes` (or `δt` assuming that `T` is fixed). Each loop contains the exact same calculations, so the total complexity of the algorithm is ``O(n)`` (where ``n = `` `ntimes`).

For the computational model, (ignoring early termination, since complexity calculations always assume the worst case) the length of the loop is `maxsteps`, but the time that each iteration takes depends on the size of `I`. The worst case is that all neighbours are infected and none ever recover, with the assumption that `N` is large enough that this doesn't hit the border within `maxsteps` iterations, giving a sequence
```math
I_n = 1, 5, 13, 25,...
```
which are the [centered square numbers](http://oeis.org/A001844), increasing at a rate of ``I_n = O(n^2)``. Hence, the complexity of the second algorithm is (in terms of ``m = `` `maxsteps`) is
```math
\sum_{n = 2}^{m+1} O(n^2) = O(m^3)
```

This, however, demonstrates the limitations of considering complexity, since I already know that this part of the algorithm isn't the slow bit (even if theoretically it may be for larger `maxsteps`). In this instance, the higher complexity is pretty irrelevant for explaining the discrepancy in speed between the two algorithms, instead it is the formation of the animated visualisation which takes up most of the time.
"""

# ╔═╡ 23608a2c-77cb-4401-b598-9a7eb3d8d469
md"""
## Comparison of the two models

Since we have line graphs and stacked area graphs for both models, we can examine them side-by-side to compare the two models. On the left are the results of the first model (not including randomness), and on the right are the results of the second:

*Note for Pluto users: the analysis below is performed using the default parameters. If you have experimented with different values, it may be best to reload the notebook to see the intended graphs*
"""

# ╔═╡ 3df2b0f4-a7ae-4ad6-aad5-255330edf54b
plot(lineplot₁, areaplot₁, lineplot₂, areaplot₂, layout = (2,2), size = (800,600))

# ╔═╡ 87cac64d-90a8-431c-8179-7f7462e5c11a
md"""
Obviously, the two models are parameterised completely differently, so there is no reason to expect them to match perfectly. However, there is one clear difference between the two, which is the rate of propagation of the infection through the population, which is markedly quicker in the mathematical model.

To explain the difference, we need only look at the two algorithms. From the differential equations defining the first, when ``I`` is small and ``S`` is large,
`` \frac{dI}{dt} \approx \beta I S ``, giving (near) exponential growth. Meanwhile, as already seen when considering the complexity of the second algorithm, the growth in ``I`` is at most quadratic, and indeed with this parametrisation, is almost linear. Hence, even though the computational model is intended to be more realistic, it cannot model the phenomenon of exponential growth of infections which is seen in the real world. In my view, this is probably because although some level of contact is simulated, it does not come close to the sort of contact between individuals which actually occurs, since individuals only come into contact with four people all of whom are already in close contact anyway (perhaps it is more accurate as a model of maths students than the population as a whole).

In contrast, the recovery curves for the two models are almost identical. Again, this can be predicted by studying the algorithms. In the first, with `` S \approx 0 ``, `` \frac{dI}{dt} \approx - \nu I ``, which results in exponential decay. For the second model, each infectious person recovers with probability `q`, so ``I_n \approx q I_{n-1} ``, again giving exponential decay. The parameters that I have chosen make this even more obvious since they cause the blue Recovered curves to resemble each other very well, indeed:
"""

# ╔═╡ ba1205e6-dcde-4c44-a246-5bc180085ec7
begin
	recoverycomparison = plot(
		times,
		R₁,
		color = :deepskyblue,
		label = "Mathematical model",
		legend = :topleft,
		showaxis = false,
		ticks = false
	)
	plot!(
		recoverycomparison,
		(0:maxsteps) / maxsteps,
		R₂,
		color = :navy,
		label = "Computational model"
	)
end

# ╔═╡ 89c2bc30-220c-4add-8bc8-21b5b610d45b
md"""
Overall, I think that the mathematical model is a better model for infectious disease, although with some alterations to increase contact, the computational model may be able to equal it, with the added bonus of a visualisation of the disease spreading among the population rather than just the macroscopic graphs that the its competitor provides.
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
Plots = "~1.22.1"
PlutoUI = "~0.7.10"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"

[[Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "f2202b55d816427cd385a9a4f3ffb226bee80f99"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+0"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "9995eb3977fbf67b86d0a0a0508e83017ded03f2"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.14.0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "4866e381721b30fac8dda4c8cb1d9db45c8d2994"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.37.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

[[DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.2.10+0"

[[FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"

[[FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
git-tree-sha1 = "d8a578692e3077ac998b50c0217dfd67f21d1e5f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.4.0+0"

[[FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "21efd19106a55620a188615da6d3d06cd7f6ee03"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.93+0"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "87eb71354d8ec1a96d4a7636bd57a7347dde3ef9"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.4+0"

[[FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"

[[GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "dba1e8614e98949abfa60480b13653813d8f0157"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.5+0"

[[GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "c2178cfbc0a5a552e16d097fae508f2024de61a3"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.59.0"

[[GR_jll]]
deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "ef49a187604f865f4708c90e3f431890724e9012"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.59.0+0"

[[GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[Gettext_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
version = "0.21.0+0"

[[Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "7bf67e9a481712b3dbe9cb3dac852dc4b1162e02"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.3+0"

[[Graphite2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
version = "1.3.14+0"

[[Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "60ed5f1643927479f845b0135bb369b031b541fa"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.14"

[[HarfBuzz_jll]]
deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"]
git-tree-sha1 = "8a954fed8ac097d5be04921d595f741115c1b2ad"
uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
version = "2.8.1+0"

[[HypertextLiteral]]
git-tree-sha1 = "72053798e1be56026b81d4e2682dbe58922e5ec9"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.0"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.0+0"

[[LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"

[[LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"

[[LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "a4b12a1bd2ebade87891ab7e36fdbce582301a92"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.6"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "761a393aeccd6aa92ec3515e428c26bf99575b3b"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+0"

[[Libgcrypt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"]
git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae"
uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
version = "1.8.7+0"

[[Libglvnd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf"
uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
version = "1.3.0+3"

[[Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"

[[Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+0"

[[Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "5a5bc6bf062f0f95e62d0fe0a2d99699fed82dd9"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.8"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+0"

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.10+0"

[[Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "438d35d2d95ae2c5e8780b330592b6de8494e779"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.3"

[[Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"

[[PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "2537ed3c0ed5e03896927187f5f2ee6a4ab342db"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.14"

[[Plots]]
deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"]
git-tree-sha1 = "4c2637482176b1c2fb99af4d83cb2ff0328fc33c"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.22.1"

[[PlutoUI]]
deps = ["Base64", "Dates", "HypertextLiteral", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "26b4d16873562469a0a1e6ae41d90dec9e51286d"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.10"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Qt5Base_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"]
git-tree-sha1 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+0"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "7ad0dfa8d03b7bcf8c597f59f5292801730c55b8"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.4.1"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3240808c6d463ac46f1c1cd7638375cd22abbccb"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.12"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StatsAPI]]
git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.0.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "8cbbc098554648c84f79a463c9ff0fd277144b6c"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.10"

[[StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "1162ce4a6c4b7e31e0e6b14486a6986951c73be9"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.5.2"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[Wayland_jll]]
deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23"
uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89"
version = "1.19.0+0"

[[Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll"]
git-tree-sha1 = "2839f1c1296940218e35df0bbb220f2a79686670"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.18.0+4"

[[XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[XSLT_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
version = "1.1.34+0"

[[Xorg_libX11_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527"
uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
version = "1.6.9+4"

[[Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"

[[Xorg_libXcursor_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd"
uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724"
version = "1.2.0+4"

[[Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"

[[Xorg_libXext_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3"
uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
version = "1.3.4+4"

[[Xorg_libXfixes_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4"
uuid = "d091e8ba-531a-589c-9de9-94069b037ed8"
version = "5.0.3+4"

[[Xorg_libXi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"]
git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246"
uuid = "a51aa0fd-4e3c-5386-b890-e753decda492"
version = "1.7.10+4"

[[Xorg_libXinerama_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"]
git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123"
uuid = "d1454406-59df-5ea1-beac-c340f2130bc3"
version = "1.1.4+4"

[[Xorg_libXrandr_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631"
uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484"
version = "1.5.2+4"

[[Xorg_libXrender_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96"
uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
version = "0.9.10+4"

[[Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"

[[Xorg_libxcb_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6"
uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
version = "1.13.0+3"

[[Xorg_libxkbfile_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2"
uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a"
version = "1.1.0+4"

[[Xorg_xcb_util_image_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97"
uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b"
version = "0.4.0+1"

[[Xorg_xcb_util_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"]
git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1"
uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5"
version = "0.4.0+1"

[[Xorg_xcb_util_keysyms_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00"
uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7"
version = "0.4.0+1"

[[Xorg_xcb_util_renderutil_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e"
uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e"
version = "0.3.9+1"

[[Xorg_xcb_util_wm_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67"
uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361"
version = "0.4.1+1"

[[Xorg_xkbcomp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"]
git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b"
uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4"
version = "1.4.2+4"

[[Xorg_xkeyboard_config_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"]
git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d"
uuid = "33bec58e-1273-512f-9401-5d533626f822"
version = "2.27.0+4"

[[Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.15.1+0"

[[libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "2.0.2+0"

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+0"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2021.5.5+0"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.5.0+0"

[[xkbcommon_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"]
git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6"
uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd"
version = "0.9.1+5"
"""

# ╔═╡ Cell order:
# ╟─133172e0-ee0f-11eb-3d88-efecd8115ca7
# ╟─0f33ac97-eb86-40cf-aa65-8ffb51cdf37c
# ╟─70d33a7a-edac-421c-a6e0-7acfd523871e
# ╟─ed6cab5f-4fdb-4680-9978-6024aa57595c
# ╠═728e28a5-d807-4751-858c-4a94873463be
# ╠═2598752b-f99f-457b-8192-b9e256c72e1e
# ╠═828ec2df-aa76-44ff-b951-880e159cb82f
# ╠═262d57fa-8ab8-4a9e-a826-3c8550692811
# ╟─fb7bb03b-bcf3-4d2c-a066-294256e2d92e
# ╠═dc26b16a-25ba-4b16-9b52-16b912c4b8bb
# ╟─b75775ab-a852-4571-8e59-63b5083db3c2
# ╠═5363d193-fb8a-469d-9e87-09ece19a01fe
# ╠═1751daa5-78e7-471c-8576-1c9acc2dd68c
# ╠═0533a017-889b-4f72-ad21-58f762f4fae2
# ╠═547bc8b9-22fb-4143-9eb6-8f2b4090ed65
# ╟─64f43f00-eacb-4039-81b0-d09b94af8923
# ╠═3fa04a1c-25eb-4fd4-aa77-f0296c83dd31
# ╠═688bebd2-2d88-441c-90f6-9597de92a176
# ╟─310433cf-f3a6-4e4a-8f2c-c5d5967b5ad4
# ╠═907983f7-0950-49ae-ba41-85da7ac577ed
# ╟─16ff8546-92f0-4578-9310-28d8db1cb6e2
# ╠═e20a2623-e0bc-4421-8726-8f81554d63b1
# ╟─1753a908-a344-40f6-b25d-6dbc124f5272
# ╠═c8590734-c738-4225-9e4e-372f5ef7a63c
# ╠═5bb45fc2-1361-4555-952d-d50f9e6aea77
# ╠═7fe01089-0b35-417e-b857-1b0ba3653ccd
# ╟─e794f4a0-fcad-427e-af49-7fd20ed11a10
# ╠═b3e54a43-a997-43bf-994c-19100d49f765
# ╠═f33ac3be-782d-4183-9c84-ee9c161d33ee
# ╠═3b63e289-dddf-4460-bde9-ce6acd7607ab
# ╠═6b7a449f-3fcc-457d-b268-39fddda134a2
# ╠═8c207e61-241c-4af1-a1dd-0f5580157de8
# ╠═61c09dcb-b03a-485d-a954-9ebbc9501d35
# ╠═1641868a-a25b-499d-bc72-e6fff52036bc
# ╟─4670197e-4c4e-49ec-ad7d-ffdd0701631e
# ╠═d2821018-da14-45c2-a98e-47edf882a867
# ╠═a52bbb3c-3f25-46a0-819c-dce56bd7c75c
# ╟─7a039123-2afd-4f2d-b47b-d05f3cb4f13c
# ╟─99d6153f-38ae-4ba4-a96e-509e9094bdb1
# ╠═df4d71fd-a752-46fd-bb18-908a23ee0cd0
# ╠═1086d559-ef39-405a-a113-a060b391ad9e
# ╠═25922ea7-4054-4054-ba4a-f93dbee85503
# ╠═b39fb04a-1074-4441-9d6d-3612674475d9
# ╠═a87ed703-f514-41cf-8262-b5ace0a28e45
# ╟─b24a796c-4908-4ff9-b392-0781574829e4
# ╠═7709c287-c7b8-4ffe-a257-4ada27454db3
# ╠═fdc5dd62-50e7-4423-880b-1dbde1aebea3
# ╟─a565879c-2938-4f5b-8de8-9807cdff3948
# ╠═d74fe014-47a1-4636-9360-83fc311307d2
# ╠═b8903866-81e9-42b0-8bd7-263d5cbaf2b4
# ╠═94272156-621b-4ed5-8582-c079625fa977
# ╠═ef4051e0-38f2-4dc6-a765-b0c76a4b0705
# ╟─8fbf94ce-50f5-48a6-91ce-d2f67fa505e4
# ╠═dfc867ac-d8d7-4f90-9410-03746e134443
# ╠═f4e7d095-13c3-40dc-b248-61d8a065d61c
# ╟─559aeb30-0d2e-4077-9af1-b4ed4702f9e5
# ╠═dc815f4e-a731-4e6d-987d-9a9db7ee5270
# ╟─3e2119c2-9482-482b-8caf-f9d386ae0522
# ╠═466ab0b6-fb5e-4354-ae91-20c7bc20a583
# ╟─8ef0ac34-d033-4515-af2d-9f44c6749977
# ╠═ba404ab9-794d-499f-b41a-7601a994e42e
# ╠═fa9bcbb8-927f-45cd-96c1-14ff760b2af8
# ╠═fff03d6e-741b-48a6-b90f-89a52f4d707e
# ╟─df111c85-33bf-4ca1-846e-a20e3491dcbd
# ╟─9756b3f3-e826-4b6d-ac4f-54ea7a5d43d1
# ╟─6733d7f2-c434-4719-9ae8-e15fa58779ce
# ╟─53984f21-33d9-46b8-b08c-d15e044f2752
# ╟─23608a2c-77cb-4401-b598-9a7eb3d8d469
# ╠═3df2b0f4-a7ae-4ad6-aad5-255330edf54b
# ╟─87cac64d-90a8-431c-8179-7f7462e5c11a
# ╠═ba1205e6-dcde-4c44-a246-5bc180085ec7
# ╟─89c2bc30-220c-4add-8bc8-21b5b610d45b
# ╟─bae4a3e4-2224-4e5d-ab15-bbdf86310ba8
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

max_stars_repo_path: casestudies/Modelling infectious diseases/Modelling infectious diseases.jl


text:
module JungleHelperRemoteKevinRefills

using ..Ahorn, Maple

@mapdef Entity "JungleHelper/RemoteKevinRefill" RemoteKevinRefill(x::Integer, y::Integer, oneUse::Bool=false, sprite::String="", flashSprite::String="")

const placements = Ahorn.PlacementDict(
    "Slide Block Refill (Jungle Helper)" => Ahorn.EntityPlacement(
        RemoteKevinRefill,
        "rectangle"
    )
)

function Ahorn.selection(entity::RemoteKevinRefill)
    x, y = Ahorn.position(entity)

    return Ahorn.getSpriteRectangle("JungleHelper/SlideBlockRefill/idle00", x, y)
end

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::RemoteKevinRefill, room::Maple.Room)
    Ahorn.drawSprite(ctx, "JungleHelper/SlideBlockRefill/idle00", 0, 0)
end

end

ext: jl

max_stars_repo_name: Jackal-Celeste/JungleHelper

max_issues_repo_name: Jackal-Celeste/JungleHelper

max_forks_repo_name: Jackal-Celeste/JungleHelper


text:
include_dependency("ordering.jl")
include_dependency("core.jl")
include("mathprog/function_analysis.jl")
include("mathprog/decomposition.jl")

"""
Compute transition function for additively separable constraints.

# Input:
- `evals`: An array of decomposed terms of a constraint.
- `func`: An array of univariate functions that stores the separate functions and their properties for each variable (array elements must be undefined as input)
- `prop`: Keyword that contains an initial assessment for properties of the univariate functions (optional)
"""
# ultimately, we would want this function to decompose the separable functions in evals into an algebraic functional form
function create_constraint_transition_function!(func::Array{UVFunc, 1}, evals::SeparableFunctionEvaluation; prp::Array{Array{UVFuncProp, 1}, 1} = [])
	@assert(length(evals.separate_functions) == length(func))
	for i=1:length(evals.separate_functions)
		if isassigned(evals.separate_functions, i)
			# if no initial information is given for the function property
			if !isassigned(prp, i)
				#TODO: run a functional analysis, to obtain function properties on the domain
				a::UVFuncProp = UVFuncProp(false, false, false, -Inf, Inf)		# sets a general functional property
				func[i] = UVFunc(evals.separate_functions[i], [a])
			# otherwise, the given info is passed as the properties
			else
				func[i] = UVFunc(evals.separate_functions[i], prp[i])		#NOTE: prop[i] is an array, so changing its elements will change func[i].prop and vice-versa
			end
		end
	end
	return
end

"""
Compute initial state for additively separable constraints.

# Input:
- `eval`: Decomposed terms of a constraint.

"""
function create_constraint_initial_state(eval::SeparableFunctionEvaluation)::Float64
    return eval.constant  	# constant value of the constraint as moved to lhs of <= constraints
end

"""
Compute single function representing the variable part of constraints.

# Input:
- `eval`: Decomposed terms of a constraint.
"""
function create_constraint_single_function(eval::SeparableFunctionEvaluation)::Function
    return eval.single_function 	# variable part of the constraint as moved to lhs of <= constraints
end

"""
Compute domain range for additively separable constraints based on a given math prog model.

# Input:
- `domain`: store an array whose elements return a tuple (lowerbound, upperbound, variable type).
- `m`: math prog model.
"""
function create_constraint_domain_function!(domain::Array{Tuple{Float64, Float64, Symbol}, 1}, m::JuMP.Model)
	nvars::Int64 = MathProgBase.numvar(m)
	@assert(length(domain) == nvars)
	for v in 1:nvars
		v_obj = Variable(m, v)
		v3 = getcategory(v_obj)		# available categories: :Cont, :Int, :Bin
		if v3 == :Bin				# for now, handle Binary as Integer
			v3 == :Int
		end
		v1 = getlowerbound(v_obj)
		v2 = getupperbound(v_obj)
		domain[v] = (v1, v2, v3)
	end
end

"""
Construct a decision diagram.

# Input
- `func`: Array of transition functions for each variable.
- `initial`: Initial state value.
- `domain`: Array of variable domains
- `lb_ub`: Array of tuples that contains (lower bound, upper bound) of univariate functions over the domain of variables defined for this DD
- `ordering`: Ordering of the variables in DD.
- `reduced_arc`: Determines whether parallel arcs are merged to their lower and upper bound label values
"""
function construct_DD(func::Array{UVFunc, 1},  initial::Float64, domain::Array{Tuple{Float64, Float64, Symbol}, 1}, lb_ub::Array{Tuple{Float64, Float64}, 1}; ordering::Ordering = NoOrdering(), reduced_arc::Bool = false)::DecisionDiagram

	n::Int = length(func)	# number of variables
	# TODO
	# - This is not a top-down search, but a simple depth-first one
	# - Implement long arcs
	# - Implement mapping between problem variables and DD variables
	# - Implement reduction at the end

	# preprocessing the min max values function at each level
	lb_ahead = Array{Float64}(  n)		# For each variable, stores the lb value that can be obtained starting from the next layer to the terminal
	ub_ahead = Array{Float64}(  n)		# For each variable, stores the ub value that can be obtained starting from the next layer to the terminal
	lb_ahead[get_var(ordering,n)] = 0		#initialize values for the last layer
	ub_ahead[get_var(ordering,n)] = 0
	for i::Int in n-1:-1:1
		var::Int = get_var(ordering,i)		# gets the variable of current layer i
		var_next::Int = get_var(ordering,i+1)		# gets the variable in layer i+1
		if isassigned(func, var_next)		#checks if the function is defined for the variable
			lb_ahead[var] = lb_ub[var_next][1] + lb_ahead[var_next]
			ub_ahead[var] = lb_ub[var_next][2] + ub_ahead[var_next]
		else
			lb_ahead[var] = lb_ahead[var_next]	#if the function is not defined for the next layer, it does not affect the lb ub values
			ub_ahead[var] = ub_ahead[var_next]
		end
	end

	S = Float64
	# constructing dd
	dd::DecisionDiagram{S} = DecisionDiagram(n)

	# TODO For top-down, this should probably only be a single Dict
	layer_states::Array{Dict{S, Int}, 1} = [Dict{S, Int}() for i=1:n+1]		# stores pairs of (state value, node id) at each layer

	# Add source node
	source::Int = add_node!(dd, 1, initial)	#the index of the created node
	layer_states[1][initial] = source

	unexplored_nodes::Array{Tuple{Int, Int}, 1} = Array{Tuple{Int, Int}, 1}(  0)	# a node tuple contains (layer, node id)
	push!(unexplored_nodes, (1, source))
	niters::Int = 0

	# stores an array of possible arc labels with their computed lb value for each variable
	# this saves multiple evaluation of the variable function at similar values
	#NOTE: this can only be used, if the same grid/values of arc labels will be evaluated as outgoing arcs of all nodes at each layer.
	memo::Array{Array{Float64, 1}, 1} = [Array{Float64, 1}() for i=1:n]

	while !isempty(unexplored_nodes)

		# Read next unexplored node
		node::Tuple{Int, Int} = pop!(unexplored_nodes)
		niters += 1
		layer::Int = node[1]
		id::Int = node[2]

		if layer > n
			continue # terminal node
		end

		state::S = get_node_state(dd, layer, id)
		var = get_var(ordering, layer)

		# println("Layer $(layer): exploring $(node) with state $(state)")

		# Create children
		# if the variable is integer
		if domain[var][3] == :Int
			v1 = ceil(Int, domain[var][1])
			v2 = floor(Int, domain[var][2])
			for val0::Int in v1:v2
				new_state::S = state
				val = float(val0)			# converts Int values to float
				if isassigned(func, var)
					#if !isassigned(memo[var], val0-v1+1)		# the label has not been evaluated at the function yet
					#	@assert(length(memo[var]) == val0-v1)
					#	push!(memo[var], func[var].f(val))
					#end
					#new_state += memo[var][val0-v1+1]		# computes the new state value
					new_state += func[var].f(val)
				end

				#To determine feasibility, it is assumed that the constraint form is f(x) <= 0
				if new_state <= -ub_ahead[var]			#new state is feasible no matter what variable assignments are chosen from now on
					new_state = -ub_ahead[var]
				elseif new_state > -lb_ahead[var]			#new state is infeasible no matter what variable assignments are chosen
					continue							# do nothing and go to the next val in the loop
				end

				# Relaxation step
				#new_state = floor(new_state/10)
				new_state = floor(new_state)
				#new_state = floor(10*new_state)/10		# rounds down the fractional state values: ONLY use to construct RELAXED DD when state values are fractional, e.g., for the pricing problem

				next_layer::Int = layer + 1
				states_to_nodes::Dict{S, Int} = layer_states[next_layer]

				# Equivalence check
				if haskey(states_to_nodes, new_state)
					# Equivalent node found: update it
					t::Int = states_to_nodes[new_state]		#the index of the node with that state value
				else
					# No equivalent node: create new child
					t = add_node!(dd, next_layer, new_state)
					states_to_nodes[new_state] = t
					# Insert new node into list of unexplored nodes
					push!(unexplored_nodes, (next_layer,t))
				end

				if reduced_arc
					add_reduced_arc!(dd, layer, id, next_layer, t, val)
				else
					add_arc!(dd, layer, id, next_layer, t, val)
				end
				# println("Added $val-arc from $node to $t")
			end
		# if the variable is continuous
		elseif domain[var][3] == :Cont
			# TODO: implement an elaborate grid desgin (uniform, or non-uniform)
			dom_eps::Float64 = 1.00e-8
			if domain[var][2] - domain[var][1] <= dom_eps	# if the range is very small, then pick a single grid
				grid = 1
			else
				grid::Int = 30					# the number of intervals to divide the continuous range
			end
			frac::Float64 = (domain[var][2] - domain[var][1])/grid
			interval_lb = domain[var][1]
			for i=1:grid
				interval_ub = interval_lb + frac
				#println("\n", interval_lb, "\t", frac, "\n")
				new_state::S = state
				if isassigned(func, var)
					if !isassigned(memo[var], i)		# the label has not been evaluated at the function yet
						@assert(length(memo[var]) == i-1)
						# TODO: find the properties of the function over the new interval.
						# compute the lb of the function over the interval
						func_lb = uvf_lb(func[var].f, func[var].prop, (interval_lb, interval_ub, :Cont))
						push!(memo[var], func_lb)
					end
					new_state += memo[var][i]		# computes the new state value (for merged nodes on the inverval)
				end

				#To determine feasibility, it is assumed that the constraint form is f(x) <= 0
				if new_state <= -ub_ahead[var]			#new state is feasible no matter what variable assignments are chosen from now on
					new_state = -ub_ahead[var]
				elseif new_state > -lb_ahead[var]			#new state is infeasible no matter what variable assignments are chosen
					interval_lb = interval_ub
					continue							# do nothing and go to the next val in the loop
				end

				# Relaxation step
				#new_state = floor(new_state/10)
				new_state = floor(new_state)
				#new_state = floor(10*new_state)/10		# rounds down the fractional state values: ONLY use to construct RELAXED DD when state values are fractional, e.g., for the pricing problem

				next_layer::Int = layer + 1
				states_to_nodes::Dict{S, Int} = layer_states[next_layer]

				# Equivalence check
				if haskey(states_to_nodes, new_state)
					# Equivalent node found: update it
					t::Int = states_to_nodes[new_state]		#the index of the node with that state value
				else
					# No equivalent node: create new child
					t = add_node!(dd, next_layer, new_state)
					states_to_nodes[new_state] = t
					# Insert new node into list of unexplored nodes
					push!(unexplored_nodes, (next_layer,t))
				end

				if reduced_arc
					add_reduced_arc!(dd, layer, id, next_layer, t, interval_lb)
					add_reduced_arc!(dd, layer, id, next_layer, t, interval_ub)
				else
					# add both lb and ub label arcs between the nodes
					add_arc!(dd, layer, id, next_layer, t, interval_lb)
					add_arc!(dd, layer, id, next_layer, t, interval_ub)
				end
				# println("Added $val-arc from $node to $t")

				interval_lb = interval_ub
			end

		end
	end

	return dd
end

max_stars_repo_name: ctjandra/ddopt-ecp

max_issues_repo_name: ctjandra/ddopt-ecp

max_forks_repo_name: ctjandra/ddopt-ecp


text:
const MessageDictionnary = OrderedDict{Int64, String}

import Base: push!

export RateLimit, isexceeded, messages_remaining

mutable struct Container{T}
    data::T
end

struct RateLimit
    buffer::CircularBuffer{DateTime}
    period::Dates.Second

    function RateLimit(max_messages::Int64, period::Dates.Second)
        return new(CircularBuffer{DateTime}(max_messages), period)
    end
end

function push!(fix::RateLimit, n::DateTime)
    push!(fix.buffer, n)
end

function isexceeded(fix::RateLimit, n::DateTime)
    return n - first(fix) < fix.period
end

function messages_remaining(fix::RateLimit, n::DateTime)
    out = capacity(fix.buffer) - length(fix.buffer)
    for t in fix.buffer
        if n - t > fix.period
            out += 1
        end
    end
    return out
end

struct IncomingMessages
    login::MessageDictionnary
    logout::MessageDictionnary
    execution_reports::Dict{String, Vector{MessageDictionnary}}
    order_cancel_reject::Vector{MessageDictionnary}
    order_reject::Vector{MessageDictionnary}
    resend::Vector{MessageDictionnary}
    heartbeat::Vector{MessageDictionnary}
    msgseqnum::Container{String}
    ratelimit::RateLimit

    function IncomingMessages(ratelimit::RateLimit)
        return new(MessageDictionnary(),
               MessageDictionnary(),
               Dict{String, Vector{MessageDictionnary}}(),
               Vector{MessageDictionnary}(0),
               Vector{MessageDictionnary}(0),
               Vector{MessageDictionnary}(0),
               Vector{MessageDictionnary}(0),
               Container("0"),
               ratelimit)
    end
end

struct OutgoingMessages
    login::MessageDictionnary
    logout::MessageDictionnary
    new_order_single::Dict{String, MessageDictionnary}
    order_cancel_request::Dict{String, MessageDictionnary}
    order_status_request::Vector{MessageDictionnary}
    order_cancel_replace_request::Dict{String, MessageDictionnary}
    msgseqnum::Container{Int64}
    ratelimit::RateLimit

    function OutgoingMessages(ratelimit::RateLimit)
        return new(MessageDictionnary(),
               MessageDictionnary(),
               Dict{String, MessageDictionnary}(),
               Dict{String, MessageDictionnary}(),
               Vector{MessageDictionnary}(0),
               Dict{String, MessageDictionnary}(),
               Container(0),
               ratelimit)
    end
end

const ClientOrderID = String
const ExchangeOrderID = String

struct OpenOrders
    client_id::Dict{String, MessageDictionnary}
    exchange_id::Dict{String, MessageDictionnary}
    verbose::Bool

    function OpenOrders(verbose::Bool=false)
        return new(Dict{String, MessageDictionnary}(), Dict{String, MessageDictionnary}(), verbose)
    end
end

struct MessageManagement
    incoming::IncomingMessages
    outgoing::OutgoingMessages
    open::OpenOrders

    function MessageManagement(ratelimit::RateLimit, verbose::Bool=false)
        return new(IncomingMessages(ratelimit), OutgoingMessages(ratelimit), OpenOrders(verbose))
    end
end

function get_position(fix::IncomingMessages, instrument::String)
    out = 0.0
    if !haskey(fix.execution_reports, "1")
        return out
    end

    for report in fix.execution_reports["1"]
        if report[55]≠ instrument
            continue
        end
        if report[54] == "1"
            out += parse(Float64, report[32])
        else
            out -= parse(Float64, report[32])
        end
    end

    return out
end

function get_positions(fix::IncomingMessages)
    out = Dict{String, Float64}()
    if !haskey(fix.execution_reports, "1")
        return out
    end

    for report in fix.execution_reports["1"]
        if !haskey(out, report[55])
            out[report[55]] = 0.0
        end
        sz = parse(Float64, report[32])
        if report[54] == "1"
            out[report[55]] = out[report[55]] + sz
        else
            out[report[55]] = out[report[55]] - sz
        end
    end

    return out
end

function on_new_message(fix::IncomingMessages, msg::MessageDictionnary)
    fix.msgseqnum.data = msg[34]
    msg_type = msg[35]

    if msg_type == "8"
        add_execution_report(fix, msg)
    elseif msg_type == "9"
        add_order_cancel_reject(fix, msg)
    elseif msg_type == "3"
        @printf("[%ls][FIX:ER] received order reject. Reason: %ls\n", now(), msg[58])
        add_order_reject(fix, msg)
    elseif msg_type == "0"
        add_heartbeat(fix, msg)
    elseif msg_type == "A"
        add_login(fix, msg)
    elseif msg_type == "5"
        add_logout(fix, msg)
    elseif msg_type == "2"
        add_resend(fix, msg)
    else
        @printf("[%ls] Unknown incoming message type: %ls\n", now(), msg_type)
    end
end

function add_login(fix::IncomingMessages, msg::MessageDictionnary)
    [fix.login[k] = v for (k, v) in msg]
end

function add_logout(fix::IncomingMessages, msg::MessageDictionnary)
    [fix.logout[k] = v for (k, v) in msg]
end

function add_execution_report(fix::IncomingMessages, msg::MessageDictionnary)
    exec_type = msg[150]
    if !haskey(fix.execution_reports, exec_type)
        fix.execution_reports[exec_type] = MessageDictionnary[]
    end
    push!(fix.execution_reports[msg[150]], msg)
end

function add_order_cancel_reject(fix::IncomingMessages, msg::MessageDictionnary)
    push!(fix.order_cancel_reject, msg)
end

function add_order_reject(fix::IncomingMessages, msg::MessageDictionnary)
    push!(fix.order_reject, msg)
end

function add_heartbeat(fix::IncomingMessages, msg::MessageDictionnary)
    push!(fix.heartbeat, msg)
end

function add_resend(fix::IncomingMessages, msg::MessageDictionnary)
    push!(fix.resend, msg)
end

function on_new_message(fix::OutgoingMessages, msg::MessageDictionnary)
    msg_type = msg[35]
    fix.msgseqnum.data = parse(Int64, msg[34])

    if msg_type == "D"
        add_new_order(fix, msg)
    elseif msg_type == "F"
        add_order_cancel_request(fix, msg)
    elseif msg_type == "H"
        add_orde_status_request(fix, msg)
    elseif msg_type == "A"
        add_login(fix, msg)
    elseif msg_type == "5"
        add_logout(fix, msg)
    elseif msg_type == "G"
        add_cancel_replace(fix, msg)
    else
        @printf("[%ls] Unknown outgoing message type: %ls\n", now(), msg_type)
    end

    return nothing
end

function add_login(fix::OutgoingMessages, msg::MessageDictionnary)
    [fix.login[k] = v for (k, v) in msg]
end

function add_logout(fix::OutgoingMessages, msg::MessageDictionnary)
    [fix.logout[k] = v for (k, v) in msg]
end

function add_new_order(fix::OutgoingMessages, msg::MessageDictionnary)
    fix.new_order_single[msg[11]] = msg
end

function add_order_cancel_request(fix::OutgoingMessages, msg::MessageDictionnary)
    fix.order_cancel_request[msg[11]] = msg
end

function add_orde_status_request(fix::OutgoingMessages, msg::MessageDictionnary)
    push!(fix.order_status_request, msg)
end

function add_cancel_replace(fix::OutgoingMessages, msg::MessageDictionnary)
    fix.order_cancel_replace_request[msg[1]]
end

function on_sent(fix::MessageManagement, msg::MessageDictionnary)
    on_new_message(fix.outgoing, msg)
    push!(fix.outgoing.ratelimit, now())
    return nothing
end

function on_get(fix::MessageManagement, msg::MessageDictionnary)
    on_new_message(fix.incoming, msg)

    if msg[35] == "8"
        on_execution_report(fix.open, msg)
    end

    return nothing
end

function on_cancel_reject(fix::MessageManagement, msg::MessageDictionnary)
    add_order_cancel_reject(fix.incoming, msg)
end

function on_order_reject(fix::MessageManagement, msg::MessageDictionnary)
    add_order_reject(fix.incoming, msg)
end

function on_new_order(fix::OpenOrders, msg::MessageDictionnary)
    fix.client_id[msg[11]] = msg
    fix.exchange_id[msg[37]] = msg
    return nothing
end

function on_fill(fix::OpenOrders, msg::MessageDictionnary)
    exchange_id = msg[37]
    filled = msg[32]
    orig_exec = get_order_by_exch_id(fix, exchange_id)
    prev_amount = parse(Float64, orig_exec[38])
    filled_amount = parse(Float64, filled)
    cur_amount = prev_amount - filled_amount
    orig_exec[38] = string(cur_amount)
end

function on_done(fix::OpenOrders, msg::MessageDictionnary)
    delete_order_by_exch_id(fix, msg[37])
end

function on_cancel(fix::OpenOrders, msg::MessageDictionnary)
    delete_order_by_exch_id(fix, msg[37])
end

function on_reject(fix::OpenOrders, msg::MessageDictionnary)
    delete_order_by_exch_id(fix, msg[37])
end

function get_order_by_exch_id(fix::OpenOrders, exchange_id::String)
    return fix.exchange_id[exchange_id]
end

function has_order_by_client_id(fix::OpenOrders, client_id::String)
    return haskey(fix.client_id, client_id)
end

function has_order_by_exch_id(fix::OpenOrders, exchange_id::String)
    return haskey(fix.exchange_id, exchange_id)
end

function delete_order_by_exch_id(fix::OpenOrders, exchange_id::String)::Void
    if !has_order_by_exch_id(fix, exchange_id)
        return nothing
    end

    orig_exec = get_order_by_exch_id(fix, exchange_id)

    client_id = orig_exec[11]
    exchange_id = orig_exec[37]

    delete!(fix.client_id, client_id)
    delete!(fix.exchange_id, exchange_id)

    return nothing
end

function on_execution_report(fix::OpenOrders, msg::MessageDictionnary)
    exec_type = msg[150]

    if exec_type == "0"
        fix.verbose && @printf("[%ls][FIX:ER] new order\n", now())
        on_new_order(fix, msg)
    elseif exec_type == "1" #fill
        fix.verbose && @printf("[%ls][FIX:ER] fill\n", now())
        on_fill(fix, msg)
    elseif exec_type == "3"
        fix.verbose && @printf("[%ls][FIX:ER] done\n", now())
        on_done(fix, msg)
    elseif exec_type == "4"
        fix.verbose && @printf("[%ls][FIX:ER] cancel\n", now())
        on_cancel(fix, msg)
    elseif exec_type == "8"
        on_reject(fix, msg)
        fix.verbose && @printf("[%ls][FIX:ER] reject\n", now())
    else
        @printf("Received unhandled execution report of type %ls\n", exec_type)
    end

end

function get_order(fix::OpenOrders)::Vector{MessageDictionnary}
    return collect(values(fix.client_id))
end

function get_open_orders(fix::MessageManagement)::Vector{MessageDictionnary}
    return get_order(fix.open)
end

function get_next_outgoing_message_num(fix::MessageManagement)::String
    return get_next_outgoing_message_num(fix.outgoing)
end

function get_next_outgoing_message_num(fix::OutgoingMessages)::String
    return string(fix.msgseqnum.data + 1)
end

function get_position(fix::MessageManagement, instrument::String)
    return get_position(fix.incoming, instrument)
end

function get_positions(fix::MessageManagement)
    return get_positions(fix.incoming)
end


text:
"""
    struct GeometryUnion{T, N, A, B} <: AbstractSet{T, N}

a || b
"""
struct GeometryUnion{T <: Real, N, A, B} <: AbstractSet{T, N}
    # just `Union` seems to conflict with Core.Union... :(
    a::A
    b::B
    translate::Union{CartesianVector{T},Missing}
end

function GeometryUnion{T, N}(a::A, b::B) where {T, N, A, B}
    GeometryUnion{T, N, A, B}(a, b, missing)
end
function GeometryUnion{T, N}(a::A, b::B, translate::CartesianVector{T}) where {T, N, A, B}
    GeometryUnion{T, N, A, B}(a, b, translate)
end

function in(pt::CylindricalPoint{T}, set::GeometryUnion{T, N})::Bool where {T <: Real, N}
    inside::Bool = false
    ismissing(set.translate) ? nothing : pt = CylindricalPoint(CartesianPoint(pt) - set.translate)
    if in(pt, set.a) || in(pt, set.b)
        inside = true
    end
    return inside
end

function in(pt::CartesianPoint{T}, set::GeometryUnion{T, N})::Bool where {T <: Real, N}
    inside::Bool = false
    ismissing(set.translate) ? nothing : pt -= set.translate
    if in(pt, set.a) || in(pt, set.b)
        inside = true
    end
    return inside
end

function Geometry(T::DataType, t::Val{:union}, dict::Dict{Union{Any,String}, Any}, inputunit_dict::Dict{String,Unitful.Units})
    union_of_parts = sum( map(x-> Geometry(T, x, inputunit_dict), dict["parts"]) )
    if haskey(dict,"translate")
        translate = CartesianVector{T}( haskey(dict["translate"],"x") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["x"]) * inputunit_dict["length"] ))) : 0.0,
                                        haskey(dict["translate"],"y") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["y"]) * inputunit_dict["length"] ))) : 0.0,
                                        haskey(dict["translate"],"z") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["z"]) * inputunit_dict["length"] ))) : 0.0)
        return GeometryUnion{T,3}(union_of_parts.a, union_of_parts.b, translate )
    else
        return union_of_parts
    end
end

"""
    struct Intersection{T, N, A, B} <: AbstractSet{T, N}

a && b
"""
struct Intersection{T, N, A, B} <: AbstractSet{T, N}
    a::A
    b::B
    translate::Union{CartesianVector{T},Missing}
end
function Intersection{T, N}(a::A, b::B) where {T, N, A, B}
    Intersection{T, N, A, B}(a, b, missing)
end

function Intersection{T, N}(a::A, b::B, translate::CartesianVector{T}) where {T, N, A, B}
    Intersection{T, N, A, B}(a, b, translate)
end

function in(pt::CartesianPoint{T}, set::Intersection{T, N})::Bool where {T <: Real, N}
    inside::Bool = false
    ismissing(set.translate) ? nothing : pt -= set.translate
    if in(pt, set.a) && in(pt, set.b)
        inside = true
    end
    return inside
end
function in(pt::CylindricalPoint{T}, set::Intersection{T, N})::Bool where {T <: Real, N}
    inside::Bool = false
    ismissing(set.translate) ? nothing : pt = CylindricalPoint(CartesianPoint(pt) - set.translate)
    if in(pt, set.a) && in(pt, set.b)
        inside = true
    end
    return inside
end

function Geometry(T::DataType, t::Val{:intersection}, dict::Dict{Union{Any,String}, Any}, inputunit_dict::Dict{String,Unitful.Units})
    parts = map(x-> Geometry(T, x, inputunit_dict), dict["parts"])
    intersection = parts[1]
    for part in parts[2:end]
        intersection = Intersection{T,3}(intersection, part)
    end
    if haskey(dict,"translate")
        translate = CartesianVector{T}( haskey(dict["translate"],"x") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["x"]) * inputunit_dict["length"] ))) : 0.0,
                                        haskey(dict["translate"],"y") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["y"]) * inputunit_dict["length"] ))) : 0.0,
                                        haskey(dict["translate"],"z") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["z"]) * inputunit_dict["length"] ))) : 0.0)
        return Intersection{T,3}(intersection.a, intersection.b, translate )
    else
        return intersection
    end
end

"""
    struct Difference{T, N, A, B} <: AbstractSet{T, N}

a && !b
"""
struct Difference{T, N, A, B} <: AbstractSet{T, N}
    a::A
    b::B
    translate::Union{CartesianVector{T},Missing}
end

function Difference{T, N}(a::A, b::B) where {T, N, A, B}
    Difference{T, N, A, B}(a, b, missing)
end
function Difference{T, N}(a::A, b::B, translate::Union{CartesianVector{T},Missing}) where {T, N, A, B}
    Difference{T, N, A, B}(a, b, translate)
end
function in(pt::CartesianPoint{T}, set::Difference{T, N})::Bool where {T <: Real, N}
    inside::Bool = false
    ismissing(set.translate) ? nothing : pt -= set.translate
    if in(pt, set.a) && !in(pt, set.b)
        inside = true
    end
    return inside
end

function in(pt::CylindricalPoint{T}, set::Difference{T, N})::Bool where {T <: Real, N}
    inside::Bool = false
    ismissing(set.translate) ? nothing : pt = CylindricalPoint(CartesianPoint(pt) - set.translate)
    if in(pt, set.a) && !in(pt, set.b)
        inside = true
    end
    return inside
end

function Geometry(T::DataType, t::Val{:difference}, dict::Dict{Union{Any,String}, Any}, inputunit_dict::Dict{String,Unitful.Units})
    difference = Geometry(T, dict["parts"][1], inputunit_dict) - sum( map(x-> Geometry(T, x, inputunit_dict), dict["parts"][2:end]) )
    if haskey(dict,"translate")
        translate = CartesianVector{T}( haskey(dict["translate"],"x") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["x"]) * inputunit_dict["length"] ))) : 0.0,
                                        haskey(dict["translate"],"y") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["y"]) * inputunit_dict["length"] ))) : 0.0,
                                        haskey(dict["translate"],"z") ? geom_round(ustrip(uconvert(u"m", T(dict["translate"]["z"]) * inputunit_dict["length"] ))) : 0.0)
        return Difference{T,3}(difference.a, difference.b, translate )
    else
        return difference
    end
end

# function Geometry(T::DataType, t::Val{:difference}, dict::Dict{Union{Any,String}, Any}, inputunit_dict::Dict{String,Unitful.Units})
#     return Geometry(T, dict["parts"][1], inputunit_dict) - sum( map(x-> Geometry(T, x, inputunit_dict), dict["parts"][2:end]) )
# end

function (+)(a::A, b::B)::AbstractSet{T, N} where {T, N, A <: AbstractGeometry{T, N}, B <: AbstractGeometry{T, N}}
    return GeometryUnion{T, N}(a, b)
end

function (-)(a::A, b::B)::AbstractSet{T, N} where {T, N, A <: AbstractGeometry{T, N}, B <: AbstractGeometry{T, N}}
    return Difference{T, N}(a, b)
end

function (&)(a::A, b::B)::AbstractSet{T, N} where {T, N, A <: AbstractGeometry{T, N}, B <: AbstractGeometry{T, N}}
    return Intersection{T, N}(a, b)
end