using PathWeightSampling: GaussianSystem
using Distributions
using Test
using LinearAlgebra

system = GaussianSystem(delta_t=0.05, duration=1.0)
initial = generate_configuration(system)

n_dim = length(initial) ÷ 2
c_xx = system.joint.Σ[n_dim + 1:end, n_dim + 1:end]
@test issymmetric(c_xx)

marginal = MvNormal(c_xx)
val1 = logpdf(marginal, initial[n_dim + 1:end]) # this is the analytically correct value of the log marginal

# we now test for every implemented estimation algorithm whether they yield results within 1% of the correct value

algorithms = [
    DirectMCEstimate(2^16),
    AnnealingEstimate(10, 50, 2000),
    TIEstimate(2^10, 10, 2^17)
]

for algorithm in algorithms
    local result = PathWeightSampling.simulate(algorithm, initial, system, scale=0.6)
    val2 = PathWeightSampling.log_marginal(result)
    @show algorithm (val2 / val1 - 1)
    if :acceptance in fieldnames(typeof(result))
        @show mean(result.acceptance)
    end
    @test isapprox(val1, val2, rtol=1e-2)
end


function preprocessSpectral(H ,g, params::Tparams,n1,n2)
    #global V, D, rho, ncomp

    # Δ, V = eig(H)
    X = eigen(H)
    Δ = X.values; V = X.vectors

    l_m, = findmin(Δ)
    g̃ = V'*g
    n_g = norm(g)
    ɛ = 1.0e-10
    λ = max(-l_m,0.0)

    return  PDataSpectral(V,Δ,g̃,λ,true,true)
end

function AInv(X :: PDataSpectral, d̃ ::  Array{Float64,1})
    return X.V * d̃
end

function reconstructH(X :: PDataSpectral)
    return X.V*diagm(X.Δ)*X.V'
end


# # Crane

# ## Model

module crane    #jl

using ReachabilityAnalysis

A = [ 0.0           1.0               0.0   0.0   0.0           0.0;
     -0.417533     -3.1931759963     39.24  0.0 -14.825331     11.123344;
      0.0           0.0               0.0   1.0   0.0           0.0;
      0.0417533     0.31931759963    -4.905 0.0   1.4825331    -1.1123344;
      0.0638407957 -0.32473339016573  0.0   0.0  -3.7332068901 -0.7007592976;
      0.0853437452 -0.72366802635628  0.0   0.0  -5.9714023436 -2.2736115136]

function model(X0)
    S = @system(x' = Ax)
    return IVP(S, X0)
end

end #jl

# ## References


rad(x, y, z) = sqrt(x^2 + y^2 + z^2)
lat(x, y, z) = asin(z / rad(x, y, z)) # ϕ ∈ [-π/2, π/2]
lon(x, y, z) = atan(y, x) # λ ∈ [-π, π)

r̂ⁿᵒʳᵐ(x, y, z) = norm([x, y, z]) ≈ 0 ? 1 : norm([x, y, z])^(-1)
ϕ̂ⁿᵒʳᵐ(x, y, z) = norm([x, y, 0]) ≈ 0 ? 1 : (norm([x, y, z]) * norm([x, y, 0]))^(-1)
λ̂ⁿᵒʳᵐ(x, y, z) = norm([x, y, 0]) ≈ 0 ? 1 : norm([x, y, 0])^(-1)

r̂(x, y, z) = r̂ⁿᵒʳᵐ(x, y, z) * @SVector([x, y, z])
ϕ̂(x, y, z) = ϕ̂ⁿᵒʳᵐ(x, y, z) * @SVector [x * z, y * z, -(x^2 + y^2)]
λ̂(x, y, z) = λ̂ⁿᵒʳᵐ(x, y, z) * @SVector [-y, x, 0]

rfunc(p, x...) = ρuʳᵃᵈ(p, lon(x...), lat(x...), rad(x...)) * r̂(x...)
ϕfunc(p, x...) = ρuˡᵃᵗ(p, lon(x...), lat(x...), rad(x...)) * ϕ̂(x...)
λfunc(p, x...) = ρuˡᵒⁿ(p, lon(x...), lat(x...), rad(x...)) * λ̂(x...)

ρu⃗(p, x...) = rfunc(p, x...) + ϕfunc(p, x...) + λfunc(p, x...)


#######################################################################
function noisyintersectionML(; posterior = posterior, g = g, σ2 = 1e-6*ones(length(g)))
#######################################################################

    # work with normalised vector

    gunit = g / norm(g)

    linemeansigma = marginaline(posterior, σ2)

    function loss(p)

        local μ, Σ = linemeansigma(p[2])

        -1.0 * logpdf(MvNormal(μ, Σ), p[1]*gunit)

    end

    # get parameters of mean line

    D = length(g)

    a = mean(posterior)[1:D]

    b = mean(posterior)[D+1:2*D]

    # find point on g that minimises distance to mean line

    aux1(s) = norm(s*gunit - b - proj(a, s*gunit - b))^2

    s0 = optimize(aux1, -1000.0, 1000.0).minimizer

    # find point on mean line that minimises distance to point on g we just found

    aux2(t) = norm(s0*gunit - (a*t + b))^2

    t0 = optimize(aux2, -1000.0, 1000.0).minimizer

    # find s, t starting from s0, t0 that minimise above log-likelihood

    opt    = Optim.Options(show_trace = false, iterations = 1_000_000)

    result = Optim.optimize(loss, [s0; t0], NelderMead(), opt).minimizer

end

"""
    randx = noisyintersectionvi(; posterior = posterior, g = g, [σ2 = 1e-6*ones(length(g))])

`posterior` is the posterior distribution returned by bpca, of type ```Distributions.MvNormal```.

`g` ∈ ℜⁿ₊ is the candidate direction vector that is being tested, of type ```Array{Float64, 1}```.

The function returns a function `randx()` that draws a sample intersection point.

"""
function noisyintersectionvi(; posterior = posterior, g = g, σ2 = 1e-6*ones(length(g)))

    @info("Scale parameter re-parametrised to enforce positivity")

    # check dimensions of arguments

    @assert(length(g) == round(Int, length(posterior)/2))

    # check that components of vector are positive

    @assert(all(g .> 0.0))

    # work with normalised vector

    gunit = g / norm(g)

    # instantiate posterior of lines

    linemeansigma = marginaline(posterior, σ2)

    # auxiliary functions for making positive parameters

    makepos(x)    = x^2

    invmakepos(x) = sqrt(x)

    # Initialise with ML estimate

    bestsolutionML = noisyintersectionML(; posterior = posterior, g = gunit, σ2 = σ2)

    # log-likleihood function for VI

    function logp(p)

        local μ, Σ = linemeansigma(p[2])

        logpdf(MvNormal(μ, Σ), makepos(p[1])*gunit)

    end

    # Get ML estimate as initial solution

    @printf("ML solution for scale is %f\n", bestsolutionML[1])

    bestsolutionML[1] = invmakepos(max(1e-3, bestsolutionML[1]))

    # Call variational inference

    postp, logevidence = VI(x -> logp(x), [bestsolutionML.+(i/10)*(randn(length(bestsolutionML))) for i=1:30], optimiser = NelderMead(), S = 500, inititerations = 50, iterations = 10_000, show_every = 25)

    # Retrieve results from VI and instantiate sampling function

    pdfscale = Normal(mean(postp)[1], sqrt(cov(postp)[1,1]))

    randscale = () -> makepos(rand(pdfscale))

    # xavg = mean([randscale() for i=1:10_000]) * gunit

    randx = () -> randscale() * gunit

    @printf("Log-evidence is %f (higher is better)\n", logevidence)

    return randx

end


export ARC_step_computation

function ARC_step_computation(h, g, Δ; kwargs...)

    h2     = ((h) .^ 2)
    gdelta = (4 .* (g ./ Δ))
    discr1 = h2 .- gdelta
    discr2 = h2 .+ gdelta

    if true in (h .> .0)
      if true in (g .> .0)
        d = (-2 .* g) ./ (h .+ sqrt.(discr2))
      else
        d = (-2 .* g) ./ (h .+ sqrt.(discr1))
      end
    else
      if true in (g .< .0)
        d = (-h .+ sqrt.(discr1)) ./ (2 ./ Δ)
      else
        d = (-h .+ sqrt.(discr2)) ./ (-2 ./ Δ)
      end
    end

    return vec(d)
  end


mutable struct zmp_data_t <: LCMType
    timestamp::Int64
    A::SMatrix{4, 4, Float64, 16}
    B::SMatrix{4, 2, Float64, 8}
    C::SMatrix{2, 4, Float64, 8}
    D::SMatrix{2, 2, Float64, 4}
    x0::SMatrix{4, 1, Float64, 4}
    y0::SMatrix{2, 1, Float64, 2}
    u0::SMatrix{2, 1, Float64, 2}
    R::SMatrix{2, 2, Float64, 4}
    Qy::SMatrix{2, 2, Float64, 4}
    S::SMatrix{4, 4, Float64, 16}
    s1::SMatrix{4, 1, Float64, 4}
    s1dot::SMatrix{4, 1, Float64, 4}
    s2::Float64
    s2dot::Float64

    com::SMatrix{4, 1, Float64, 4}
end

@lcmtypesetup(zmp_data_t)


# Load Julia packages (libraries) needed  for the snippets in chapter 0

using StatisticalRethinking, CmdStan, LinearAlgebra
#gr(size=(600,600));

# CmdStan uses a tmp directory to store the output of cmdstan

ProjDir = rel_path("..", "scripts", "04")
cd(ProjDir)

# ### snippet 4.7

howell1 = CSV.read(rel_path("..", "data", "Howell1.csv"), delim=';')
df = convert(DataFrame, howell1);

# Use only adults

df2 = filter(row -> row[:age] >= 18, df);
first(df2, 5)

# Use data from m4.1s

# Check if the m4.1s.jls file is present. If not, run the model.

!isfile(joinpath(ProjDir, "m4.1s.jls")) && include(joinpath(ProjDir, "m4.1s.jl"))

chn = deserialize(joinpath(ProjDir, "m4.1s.jls"))

# Describe the draws

MCMCChains.describe(chn)

# Plot the density of posterior draws

density(chn, lab="All heights", xlab="height [cm]", ylab="density")

# Compute cor

mu_sigma = hcat(chn.value[:, 2, 1], chn.value[:,1, 1])
LinearAlgebra.diag(cov(mu_sigma))

# Compute cov

cor(mu_sigma)

# End of `clip_07.0s.jl`


### A Pluto.jl notebook ###
# v0.17.1

using Markdown
using InteractiveUtils

# ╔═╡ 5000b8f3-8a09-4440-80e9-ab56325de90f
begin
using DataFrames
using Plots
end

# ╔═╡ 266c7f54-ce35-4b97-bdb6-bb5611332811
md"""
# Multi-objective optimization in RNA-seq deconvolution.

Multi-objective optimization is used to solve problems that require more than one objective function to be optimized simultaneously. Typically, there is not one single solution that optimizes each objective.
"""

# ╔═╡ d6285877-636a-4804-8790-7e4525d870b9
md"""
## Easy example

Consider two towns, with two paths between them. The objectives that we care about are the distance (km) between the towns, and the travel time (h).

Both paths are 1000 km long, so path A and path B are equally good. Each path has a 50% chance of being choosen.

![]
(https://github.com/licollier/Multiobjectopt/blob/master/notebook/Figures/1000 km.jpg?raw=true)
"""

# ╔═╡ c6af78f8-d0f2-48ed-b607-7c09dd3d535d
md"""

Now, let's imagine that the first path has a speed limit of 50 km/h and the second path one of 100 km/h. If we would always drive at this speed limit, it would take us about 20 hours to reach the other city. If we would take the second path, this would take us only 10 hours. Based on the travel time alone, the second path is 2 times as good as the first path. The red path has a 33% chance of being choosen, while the green path has a 66% chance of being choosen.

![]
(https://github.com/licollier/Multiobjectopt/blob/master/notebook/Figures/speedlimits.jpg?raw=true)

"""

# ╔═╡ 7cb56496-5184-44ad-9323-db908a8253c8
md"""

If we take both the distance and travel time into account, we can clearly see that the green path is better. But how much better?

If we would take a naive approach, we could just combine the distance and travel time to compute the fitness of each path.

$$red: 1000 + 20 = 1020$$
$$green: 1000 + 10 = 1010$$

The red path now has a 50,2% chance of being chosen and the green path 49,8%. The travel time has almost no impact on the fitness of the path. We could solve this by multiplying the time distance with some factor, so it becomes similar in scale to the distance:

$$red: 1000 + 20*50 = 2000$$
$$green: 1000 +10*50 = 1500$$

Now the red path has a 57% chance of being chosen, and the green path a 43% chance.
This is slightly better, but when we put time on the same scale as the distance, we assume that travel time and distance are equally important.

"""

# ╔═╡ f398de0e-f8e6-4f0f-931e-aa015d2aeb7e
md"""
By scaling one objective and just combining them to calculate the fitness of each path, we also loose information about the solutions. Look at the following example:

![]
(https://github.com/licollier/Multiobjectopt/blob/master/notebook/Figures/speedlimits2.jpg?raw=true)
"""

# ╔═╡ 4265b668-3c60-4859-98e6-71e65e8dccd8
md"""

$$red: 1000 + 10 *50 = 1500$$
$$green: 500 + 20*50 = 1500$$

Now, the red and green path have the same fitness, even tough they are very different solutions.

We have realized that just combining the travel time and distance to find the fitness of each solution is not the best way. This way we assume that each objective is equally important and we loose valuable information about the solution. So, what can we do? """

# ╔═╡ 66d7232f-c7b7-4385-95b8-460b0e570105
md"""
## Pareto fronts"""

# ╔═╡ 4bc3066e-a0f5-4829-9c72-fa9cb6046427
md"""

Continuing our problem of travelling between two towns. We now have 4 different solutions. We can visualise each solution on a simple plot. """

# ╔═╡ af094e89-71e5-4468-93a9-941a1c32f8ff
DataFrame(Distance =[1000, 750, 800, 500], Time = [10, 14, 14.5, 20])

# ╔═╡ 8147f6ce-16ac-4085-a5d5-ef3c863d1018
begin

	Distance =[1000 750 800 500 ]
	Time = [10 14 14.5 20]
	markercolor = [:red :blue :purple :green]

plot(Time,Distance,seriestype = :scatter, color = markercolor, markersize = 10,xlabel="Time (h)",ylabel="Distance (km)",legend = false)
end

# ╔═╡ 4c7e1677-bd51-475d-b996-1a3bf2d31ae2
md"""
When we compare the blue solution with the red solution, we see that blue is better in distance, but not in time. When we compare blue with green, we see that blue is better in time, but not in distance. Blue is another equally good solution as green and red, but with other trade offs between our objectives.

When we look at the purple solution, we see that blue is better both in distance and in time. We can say that purple is **dominated** by blue.

We have now found a way to order our solutions by fitness, when we have more than one objective. By identifying the dominating solutions, we can find the best solutions in our population. But what about the remainder of the solutions? Let us look at a few more solutions."""

# ╔═╡ 24785674-8024-4d9e-9bb3-8b5e4589e7b5
begin
	Distance1 =[800 700 650 600 570 525 1000 900 750 1000 850 700 850]
	Time1 = [10 11 13 15 15.5 19 11 12 14 16 13.5 16 17]

plot(Time1,Distance1,seriestype = :scatter, markersize =8,xlabel="Time (h)", ylabel="Distance (km)",legend=false)
end

# ╔═╡ 52aaf73e-7d97-43e6-810d-f3a8eea2e081
md"""

We can immediately identify the non-dominated solutions (in red), for these solutions we can not necessarily find a better solution, while for the blue ones we can.
"""

# ╔═╡ d84017c8-3e45-495d-8748-e758cca32932
begin
markercolor2=[:red :red :red :red :red :red :blue :blue :blue :blue :blue :blue :blue]
plot(Time1,Distance1,seriestype = :scatter, markersize =8,xlabel="Time (h)", ylabel="Distance (km)",legend=false,color=markercolor2)
end

# ╔═╡ aca14538-cc05-49bd-9371-f4358e70150e
md"""
The line that is formed between these non-dominated points, is called a Pareto front. Every solution on the pareto front is equally fit. Once we have the first front we can identify a second, third,fourth,... front:"""

# ╔═╡ 78bb0c77-8722-4854-aa74-d502525469c5
begin
markercolor3=[:red :red :red :red :red :red :green :green :green :yellow :green :green :yellow]
plot(Time1,Distance1,seriestype = :scatter, markersize =8,xlabel="Time (h)", ylabel="Distance (km)",legend=false,color=markercolor3)
end

# ╔═╡ 92652f0b-8112-4ed9-9412-ed714e66b2f8
md"""

We have found the red solutions to be the best, we can choose one solution on this Pareto front based on our own preferences. For example, I would rather drive more kilometers on a highway at a higher speed, than less kilometers on a "boerewegel" at low speed. So I would probably choose one of the most left solutions. """

# ╔═╡ 9350e66f-bfa0-4772-a8c9-8d42df4c76ed
md"""

Now that we understand the basic concept of multi-object optimization, how can this be used to deconvolute bulk RNA-seq data? """

# ╔═╡ ad3efd53-7051-4007-a276-b1b91aa76339
md"""
## Deconvolution"""

# ╔═╡ f8b45b20-1a32-49c1-ad84-26e165328614
md"""

Deconvolution is typically used to obtain cell fractions (composition deconvolution) from heterogenous bulk gene expression data such as blood samples or tumor samples.

A general pipeline for deconvolution can be seen below. Tumor or blood samples can be sequenced to generate a bulk RNA gene expression profile. To determine the abundance of each cell type that is present in this cell mixture, deconvolution is used. For each cell type, the gene expression profile is established, preferably by single cell RNA sequencing (scRNA-seq). These gene expression profiles can then be used to generate a signature matrix. By combining these two inputs, the cell proportions can be determined.

![]
(https://github.com/licollier/Multiobjectopt/blob/master/notebook/Figures/Deconvolution_pipeline.jpg?raw=true)"""

# ╔═╡ a7c46cef-e37d-41ee-acfa-234c527906cf
md"""

The signature matrix contains a gene expression profile for each cell type that is present in the bulk mixture. These expression profiles contain marker genes and there expression values. However, some cell types are closely related, so finding a signature profile that is unique enough to seperate them can be difficult. This is where multi-objective optimization can be used. We want to select a set of genes with two objectives: simultaneously minimize the correlation and maximize the distance between the cell types. First the Pareto optimals are found, and then it is again up to the researchers to choose what they find most important. Aliee and Theis (2021) found that using the solution with the least correlation will be the best. However, other solutions will still work well. """

# ╔═╡ 23a2aa94-aee7-44d3-9658-d41724081b0a
md"""

## References

Multiobjective Optimization | NEOS. (z.d.). Neos-Guide.Org. Geraadpleegd op 30 januari 2022, van https://neos-guide.org/content/multiobjective-optimizationTutorials with Gary. (2020, 13 juni).

Multi-Objective Optimisation - Writing your own Genetic Algorithm Part 6 [Video]. YouTube. https://www.youtube.com/watch?v=3JrpyuSHEWQ&t=569sWikipedia contributors. (2021, 26 november).

Pareto front. Wikipedia. Geraadpleegd op 30 januari 2022, van https://en.wikipedia.org/wiki/Pareto_front"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

[compat]
DataFrames = "~1.3.2"
Plots = "~1.25.7"
"""

# ╔═╡ 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 = "af92965fb30777147966f58acb05da51c5616b5f"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.3"

[[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 = "4b859a208b2397a7a623a03449e4636bdb17bcf2"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+1"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "f9982ef575e19b0e5c7a98c6e75ee496c0f73a93"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.12.0"

[[ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.2"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "6b6f04f93710c71550ec7e16b650c1b9a612d0b6"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.16.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 = "44c37b4636bc54afac5c574d2d02b625349d6582"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.41.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"

[[Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"

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

[[DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "ae02104e835f219b8930c7664b8012c93475c340"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.3.2"

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

[[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"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[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"

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

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

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

[[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 = "aa22e1ee9e722f1da183eb33370df4c1aeb6c2cd"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.63.1+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 = "a32d672ac2c967f3deb8a81d828afc739c838a06"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.3+2"

[[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 = "0fa77022fe4b511826b39c894c90daf5fce3334a"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.17"

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

[[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"

[[InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"

[[InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

[[IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[IterTools]]
git-tree-sha1 = "fa6287a4469f5e048d763df38279ee729fbd44e5"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.4.0"

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

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "22df5b96feef82434b07327e2d3c770a9b21e023"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.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 = "f2355693d6778a178ade15952b7ac47a4ff97996"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.3.0"

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

[[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 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+1"

[[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"

[[LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "e5718a00af0ab9756305a0392832c8952c7426c1"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.6"

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

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

[[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 = "b086b7ea07f8e38cf122f5016af580881ac914fe"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.7"

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

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

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "648107615c15d4e09f7eca16307bc821c1f718d8"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.13+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 = "0b5cfbb704034b5b4c1869e36634438a047df065"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.1"

[[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 = "6f1b25e8ea06279b5689263cc538f51331d7ca17"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.1.3"

[[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", "UnicodeFun", "Unzip"]
git-tree-sha1 = "7e4920a7d4323b8ffc3db184580598450bde8a8e"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.25.7"

[[PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "db3a23166af8aebf4db5ef87ac5b00d36eb771e2"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.4.0"

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

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "dfb54c4e414caa595a1f2ed759b160f5a3ddcba5"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.3.1"

[[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 = "6bf3f380ff52ce0832ddd3a2a7b9538ed1bcca7d"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.2.1"

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

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

[[RelocatableFolders]]
deps = ["SHA", "Scratch"]
git-tree-sha1 = "cdbd3b1338c72ce29d9584fdbe9e9b70eeb5adca"
uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
version = "0.1.3"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

[[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 = "2884859916598f974858ff01df7dfc6c708dd895"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.3.3"

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

[[StatsAPI]]
git-tree-sha1 = "d88665adc9bcf45903013af0982e2fd05ae3d0a6"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.2.0"

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

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

[[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 = "bb1064c9a84c52e277f1096cf41434b675cd368b"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.1"

[[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"

[[UnicodeFun]]
deps = ["REPL"]
git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
version = "0.4.1"

[[Unzip]]
git-tree-sha1 = "34db80951901073501137bdbc3d5a8e7bbd06670"
uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d"
version = "0.1.2"

[[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"]
git-tree-sha1 = "66d72dc6fcc86352f01676e8f0f698562e60510f"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.23.0+0"

[[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 = "b910cb81ef3fe6e78bf6acee440bda86fd6ae00c"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+1"

[[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:
# ╟─266c7f54-ce35-4b97-bdb6-bb5611332811
# ╟─5000b8f3-8a09-4440-80e9-ab56325de90f
# ╟─d6285877-636a-4804-8790-7e4525d870b9
# ╟─c6af78f8-d0f2-48ed-b607-7c09dd3d535d
# ╟─7cb56496-5184-44ad-9323-db908a8253c8
# ╟─f398de0e-f8e6-4f0f-931e-aa015d2aeb7e
# ╠═4265b668-3c60-4859-98e6-71e65e8dccd8
# ╟─66d7232f-c7b7-4385-95b8-460b0e570105
# ╟─4bc3066e-a0f5-4829-9c72-fa9cb6046427
# ╟─af094e89-71e5-4468-93a9-941a1c32f8ff
# ╟─8147f6ce-16ac-4085-a5d5-ef3c863d1018
# ╟─4c7e1677-bd51-475d-b996-1a3bf2d31ae2
# ╟─24785674-8024-4d9e-9bb3-8b5e4589e7b5
# ╟─52aaf73e-7d97-43e6-810d-f3a8eea2e081
# ╟─d84017c8-3e45-495d-8748-e758cca32932
# ╟─aca14538-cc05-49bd-9371-f4358e70150e
# ╟─78bb0c77-8722-4854-aa74-d502525469c5
# ╟─92652f0b-8112-4ed9-9412-ed714e66b2f8
# ╟─9350e66f-bfa0-4772-a8c9-8d42df4c76ed
# ╟─ad3efd53-7051-4007-a276-b1b91aa76339
# ╟─f8b45b20-1a32-49c1-ad84-26e165328614
# ╟─a7c46cef-e37d-41ee-acfa-234c527906cf
# ╠═23a2aa94-aee7-44d3-9658-d41724081b0a
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


@testset "HO-SVD" begin
    r = 2
    T = rand_kruskal3(r, (10, 20, 30), false)

    @testset "no residuals calculation" begin
        @time factors = hosvd(T, r)
        @test length(factors.factors) == ndims(T)
        @test size.(factors.factors) == ntuple(i -> (size(T, i), r), ndims(T))
        @test size(factors.core) == (r, r, r)
        @test isnan(rel_residue(factors))
    end

    @testset "core reconstruction and residuals" begin
        @time factors = hosvd(T, r, compute_error=true)
        @test size(factors.core) == (r, r, r)
        @test rel_residue(factors) < 1e-5
    end

    @testset "core dimension equal to the original dimension" begin
        @time factors = hosvd(randn(10, 20, 30), (10, 15, 5), compute_error=true)
        @test size(factors.core) == (10, 15, 5)
    end

end


##=================####==============##
## TEST SIMULATION ABM NETWORK-EPIDEMICS
##=================####==============##

##=================####==============##

using StatsBase     # For sample function
using SparseArrays  # For Adyacency Matrix
using Distributions # For probability distributions

using Plots         # plotting
using LaTeXStrings  # latex labels in plots

##=================####==============##

include("NetABM.jl") # Include file with functions and definitions

##=================####==============##
# PATH TO SAVE IMGS (MODIFY WITH YOUR OWN!)

img_out_path = joinpath(homedir(), "Code", "Collaborations", "networkEpidemiology_MX", "julia_ABM", "figures")

##=================####==============##
## SYSTEM'S PARAMETERS

params = Params()

params.p_infected_t0 = 0.01 # Initial fraction of infected agents
params.attack_rate   = 0.25 # Virus attack rate
params.repetition    = 1 # Repetition ID for ensemble statistics

params.μ_cop_agents  = 0.15 # Mean of cooperation probability
params.σ_cop_agents  = 0.1 # Std deviation of cooperation probability
params.μ_recovT_agents = 7 # Mean recovery time

# Link probability (Erdos-Renyi)
params.p_link = ((1+5exp10(-2))*log(params.num_agents))/params.num_agents

##=================####==============##
# INITIALIZE ADJACENCY MATRIX (ERDOS-RENYI)

adj_mat, rows = initialize_erdos_renyi(params);

##=================####==============##
# INITIALIZE MEETS, COOPERATION AND RECOVERY TIMES DISTRIBUTION

rt_μ = params.μ_recovT_agents # MEAN RECOVERY TIME
pc_μ = params.μ_cop_agents # MEAN COOPERATION PROBABILITY
pc_σ = params.σ_cop_agents # STD_DEV COOPERATION PROBABILITY

# meets_dist = Exponential()
meets_dist  = Geometric()
coop_dist   = truncated(Normal(pc_μ, pc_σ), 0.0, 1.0)
recovt_dist = truncated(Poisson(rt_μ), 7, 35)

##=================####==============##

# DEFINE AGENTS POPULATION (DEFAULT VALUES)
agents = [Agent(id) for id in 1:params.num_agents]

# MODIFY AGENT'S CHARACTERISTICS ACCORDING TO ITS DEMOGRAPHICS
initialize_demographics!(agents, params, coop_dist, meets_dist, recovt_dist)

# ASSGINS CONTACTS TO agent FROM adj_mat
for ag in agents
    assign_contacts!(ag, agents, adj_mat, rows)
end

##=================####==============##

time_lockdown = 10

tau = []
num_S = []
num_I = []
num_R = []

# INITIALIZE TIME AND POPULATIONS
params.now_t = 0
populations = get_populations(agents, params)

while populations[2] > 0.0 # WHILE THERE ARE INFECTED AGENTS

    # RECORD DATA
    push!(tau, params.now_t)
    push!(num_S, populations[1])
    push!(num_I, populations[2])
    push!(num_R, populations[3])

    if params.now_t == time_lockdown
        set_coop_agents!(agents, params)
        println("start lockdown")
    end

   # FINDS NEXT STATE AND UPDATES ALL AGENTS
    update_all_agents!(agents, params)

    # GET CURRENT POPULATIONS AND UPDATE TIME
    global populations = get_populations(agents, params);
    params.now_t += 1
end

println("END")

##=================####==============##
# POPULATIONS GRAPH

plt = plot(
        xlabel = L"t",
        ylabel = L"N(s)/N",
        guidefontsize = 16,
        tickfontsize = 10
    )

plot!(plt, tau, num_S, color = :blue,  lab = "S", lw = 1.25)
plot!(plt, tau, num_I, color = :red,   lab = "I", lw = 1.25)
plot!(plt, tau, num_R, color = :green, lab = "R", lw = 1.25)
# plot!(plt, tau, 1.0 .- num_R, color = :green, lab = "R", lw = 1.25)

vline!(plt, [time_lockdown], ls = :dash, color = :red, lab = "")

display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "SIR_N_$(params.num_agents)_AR_$(params.attack_rate).png"))
##=================####==============##
# Distribución de Números de Encuentros

plt = plot(
        xlabel = L"N_m",
        ylabel = L"N (N_m)",
        title = "Encounters per agent per time step distribution",
        guidefontsize = 16,
        tickfontsize = 10
    )

vals = 1 .+ rand(meets_dist, params.num_agents)
histogram!(plt, vals, lab = false, bar_width = 0.85)

# vals = rand(meets_dist, params.num_agents)
# histogram!(plt, vals, normalized = true, lab = false, bar_width = 0.85)

xticks!(plt, 1:maximum(vals))
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "meetsDist_N_$(params.num_agents).png"))
##=================####==============##
# Distribución de Tiempo de Recuperación

plt = plot(
        xlabel = L"R_t",
        ylabel = L"N(R_t)",
        title = "Agent's recovery times distribution",
        guidefontsize = 16,
        tickfontsize = 10
    )

vals = rand(recovt_dist, params.num_agents)
histogram!(plt, vals, lab = false)

# histogram!(plt, vals, normalized = true, lab = false)

# xticks!(plt, unique(vals))
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "recovtDist_N_$(params.num_agents).png"))
##=================####==============##
# Tiempo de Infección

plt = plot(
        xlabel = L"t_I",
        ylabel = L"p(t_I)",
        title = "Time of infection distribution",
        guidefontsize = 16,
        tickfontsize = 10
    )

histogram!(plt, [ag.infection_t for ag in agents], normalized = true, lab = false)
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "tiempoI_N_$(params.num_agents)_AR_$(params.attack_rate).png"))
##=================####==============##
# Distribución de Grado

plt = plot(
        xlabel = L"\kappa_i",
        ylabel = L"p\;(\kappa_i)",
        title = "Network's degree distribution ",
        guidefontsize = 16,
        tickfontsize = 10
    )

histogram!(plt, [ag.degree_t for ag in agents], normalized = true, lab = false)
display(plt)

##=================####==============##
savefig(plt, joinpath(img_out_path, "degDist_N_$(params.num_agents)_ER.png"))
##=================####==============##


using DSP: conv
using LinearAlgebra

Funcs3D = (
    (x, y, z) -> cat(x, y, z,dims=4),
    (x, y, z) -> cat(
        sqrt(3) * x .* z,
        sqrt(3) * x .* y,
        y .^ 2 - (x .^ 2 + z .^ 2) / 2,
        sqrt(3) * y .* z,
        (z .^ 2 - x .^ 2) * sqrt(3) / 2,
    dims=4),
)

 Funcs2D = (
    (x, y) -> [x, y],
    #     # (x,y)->        [
    #     #             sqrt(3) * x*z,
    #     #             sqrt(3) * x*y,
    #     #             y^ 2 - (x^ 2 + z^ 2) / 2,
    #     #             sqrt(3) *y*z,
    #     #             (z^ 2 - x^ 2) * sqrt(3) / 2,
    #     #         ],
)

function make_spherical_harmonics(dx, size, origin, rank_max; dims = 3)
    if rank_max < 0
        return [], []
    end
    if dims == 3
        rvecs =
            [
                [x, y, z] .- origin for x = 1:size[1], y = 1:size[2],
                z = 1:size[3]
            ] * dx
        Funcs = Funcs3D
    elseif dims == 2
        rvecs = [[x, y] .- origin for x = 1:size[1], y = 1:size[2]] * dx
        Funcs = Funcs2D
    end
    r = norm.(rvecs)
    if rank_max > 0
        rhat = rvecs ./ broadcast(x -> x ≈ 0.0 ? 1.0 : x, r)
        rhat = [getindex.(rhat, i) for i = 1:dims]
    end
    sh = [Funcs[i](rhat...) for i = 1:rank_max]
    r, sh
end


# # Certificate

#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Extension/certificate.ipynb)
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Extension/certificate.ipynb)
# **Contributed by**: Benoît Legat

# ## Introduction

# Consider the polynomial optimization problem (a variation from [L09, Example 2.2]) of
# minimizing the polynomial $x^3 - x^2 + 2xy - y^2 + y^3$
# over the polyhedron defined by the inequalities $x \ge 0, y \ge 0$ and $x + y \geq 1$.

# [L09] Lasserre, J. B.
# *Moments, positive polynomials and their applications*.
# World Scientific, **2009**.

using Test #src
using DynamicPolynomials
@polyvar x y
p = x^3 - x^2 + 2x*y -y^2 + y^3 + x^3 * y
using SumOfSquares
S = @set x >= 0 && y >= 0 && x^2 + y^2 >= 2

# We will now see how to find the optimal solution using Sum of Squares Programming.
# We first need to pick an SDP solver, see [here](https://jump.dev/JuMP.jl/v0.21.6/installation/#Supported-solvers) for a list of the available choices.

import CSDP
solver = optimizer_with_attributes(CSDP.Optimizer, MOI.Silent() => true)

# A Sum-of-Squares certificate that $p \ge \alpha$ over the domain `S`, ensures that $\alpha$ is a lower bound to the polynomial optimization problem.
# The following program searches for the largest upper bound and finds zero.

model = SOSModel(solver)
@variable(model, α)
@objective(model, Max, α)
@constraint(model, c, p >= α, domain = S)
optimize!(model)
@show termination_status(model)
@show objective_value(model)

# We now define the Schmüdgen's certificate:

using MultivariateBases
const MB = MultivariateBases
const SOS = SumOfSquares
const SOSC = SOS.Certificate
struct Schmüdgen{IC <: SOSC.AbstractIdealCertificate, CT <: SOS.SOSLikeCone, BT <: SOS.AbstractPolynomialBasis} <: SOSC.AbstractPreorderCertificate
    ideal_certificate::IC
    cone::CT
    basis::Type{BT}
    maxdegree::Int
end

SOSC.cone(certificate::Schmüdgen) = certificate.cone

function SOSC.preprocessed_domain(::Schmüdgen, domain::BasicSemialgebraicSet, p)
    return SOSC.with_variables(domain, p)
end

function SOSC.preorder_indices(::Schmüdgen, domain::SOSC.WithVariables)
    n = length(domain.inner.p)
    if n >= Sys.WORD_SIZE
        error("There are $(2^n - 1) products in Schmüdgen's certificate, they cannot even be indexed with `$Int`.")
    end
    return map(SOSC.PreorderIndex, 1:(2^n-1))
end

function SOSC.multiplier_basis(certificate::Schmüdgen, index::SOSC.PreorderIndex, domain::SOSC.WithVariables)
    q = SOSC.generator(certificate, index, domain)
    return SOSC.maxdegree_gram_basis(certificate.basis, variables(domain), SOSC.multiplier_maxdegree(certificate.maxdegree, q))
end
function SOSC.multiplier_basis_type(::Type{Schmüdgen{IC, CT, BT}}) where {IC, CT, BT}
    return BT
end

function SOSC.generator(::Schmüdgen, index::SOSC.PreorderIndex, domain::SOSC.WithVariables)
    I = [i for i in eachindex(domain.inner.p) if !iszero(index.value & (1 << (i - 1)))]
    return prod([domain.inner.p[i] for i in eachindex(domain.inner.p) if !iszero(index.value & (1 << (i - 1)))])
end

SOSC.ideal_certificate(certificate::Schmüdgen) = certificate.ideal_certificate
SOSC.ideal_certificate(::Type{<:Schmüdgen{IC}}) where {IC} = IC

SOS.matrix_cone_type(::Type{<:Schmüdgen{IC, CT}}) where {IC, CT} = SOS.matrix_cone_type(CT)

# Let's try again with our the Schmüdgen certificate:

model = SOSModel(solver)
@variable(model, α)
@objective(model, Max, α)
ideal_certificate = SOSC.Newton(SOSCone(), MB.MonomialBasis, tuple())
certificate = Schmüdgen(ideal_certificate, SOSCone(), MB.MonomialBasis, maxdegree(p))
@constraint(model, c, p >= α, domain = S, certificate = certificate)
optimize!(model)
@show termination_status(model)
@show objective_value(model)
@test length(lagrangian_multipliers(c)) == 7 #src


"""
The base type of a fractional integral sense, in FractionalCalculus.jl, all of the fractional integral senses belong to ```FracIntAlg```
"""
abstract type FracIntAlg end

include("RL.jl")

"""
    fracint(f, α, point, h, FracIntAlg())

The general API used for fractional integral computing.
"""
function fracint(f, α, point, h, ::FracIntAlg)
end

## Macros for convenient computing.
"""
    @fracint(f, α, point)

Return the α-order integral of **f** at specific point.

```julia-repl
julia> @fracint(x->x, 0.5, 1)
0.7522525439593486
```
"""
macro fracint(f, α, point)
    return :(fracint($f, $α, $point, 0.0001, RLInt_Approx()))
end

"""
    @semifracint(f, point)

Return the semi-integral of **f** at specific point.

```julia-repl
julia> @semifracint(x->x, 1)
0.7522525439593486
```
"""
macro semifracint(f, point)
    return :(fracint($f, 0.5, $point, 0.0001, RLInt_Approx()))
end


# * Relativistic orbital

"""
    κ2ℓ(κ::Integer) -> Integer

Calculate the `ℓ` quantum number corresponding to the `κ` quantum number.

Note: `κ` and `ℓ` values are always integers.
"""
function κ2ℓ(κ::Integer)
    κ == zero(κ) && throw(ArgumentError("κ can not be zero"))
    (κ < 0) ? -(κ + 1) : κ
end

"""
    κ2j(κ::Integer) -> HalfInteger

Calculate the `j` quantum number corresponding to the `κ` quantum number.

Note: `κ` is always an integer, but `j` will be a half-integer value.
"""
function κ2j(kappa::Integer)
    kappa == zero(kappa) && throw(ArgumentError("κ can not be zero"))
    half(2*abs(kappa) - 1)
end

"""
    ℓj2κ(ℓ::Integer, j::Real) -> Integer

Converts a valid `(ℓ, j)` pair to the corresponding `κ` value.

Note: there is a one-to-one correspondence between valid `(ℓ,j)` pairs and `κ` values
such that for `j = ℓ ± 1/2`, `κ = ∓(j + 1/2)`.
"""
function ℓj2κ(ℓ::Integer, j::Real)
    assert_orbital_ℓj(ℓ, j)
    (j < ℓ) ? ℓ : -(ℓ + 1)
end

function assert_orbital_ℓj(ℓ::Integer, j::Real)
    j = HalfInteger(j)
    s = half(1)
    (ℓ == j + s) || (ℓ == j - s) ||
        throw(ArgumentError("Invalid (ℓ, j) = $(ℓ), $(j) pair, expected j = ℓ ± 1/2."))
    return
end

"""
    struct RelativisticOrbital{N <: AtomicLevels.MQ} <: AbstractOrbital

Label for an atomic orbital with a principal quantum number `n::N` and well-defined total
angular momentum ``j``. The angular component of the orbital is labelled by the ``(\\ell, j)``
pair, conventionally written as ``\\ell_j`` (e.g. ``p_{3/2}``).

The ``\\ell`` and ``j`` can not be arbitrary, but must satisfy ``j = \\ell \\pm 1/2``.
Internally, the ``\\kappa`` quantum number, which is a unique integer corresponding to every
physical ``(\\ell, j)`` pair, is used to label each allowed pair.
When ``j = \\ell \\pm 1/2``, the corresponding ``\\kappa = \\mp(j + 1/2)``.

When printing and parsing `RelativisticOrbital`s, the notation `nℓ` and `nℓ-` is used (e.g.
`2p` and `2p-`), corresponding to the orbitals with ``j = \\ell + 1/2`` and
``j = \\ell - 1/2``, respectively.

The type parameter `N` has to be such that it can represent a proper principal quantum number
(i.e. a subtype of [`AtomicLevels.MQ`](@ref)).

# Properties

The following properties are part of the public API:

* `.n :: N` -- principal quantum number ``n``
* `.κ :: Int` -- ``\\kappa`` quantum number
* `.ℓ :: Int` -- the orbital angular momentum label ``\\ell``
* `.j :: HalfInteger` -- total angular momentum ``j``

```jldoctest
julia> orb = ro"5g-"
5g-

julia> orb.n
5

julia> orb.j
7/2

julia> orb.ℓ
4
```

# Constructors

    RelativisticOrbital(n::Integer, κ::Integer)
    RelativisticOrbital(n::Symbol, κ::Integer)
    RelativisticOrbital(n, ℓ::Integer, j::Real)

Construct an orbital label with the quantum numbers `n` and `κ`.
If the principal quantum number `n` is an integer, it has to positive and the orbital angular
momentum must satisfy `0 <= ℓ < n`.
Instead of `κ`, valid `ℓ` and `j` values can also be specified instead.

```jldoctest
julia> RelativisticOrbital(1, 0, 1//2)
1s

julia> RelativisticOrbital(2, -1)
2s

julia> RelativisticOrbital(:K, 2, 3//2)
Kd-
```
"""
struct RelativisticOrbital{N<:MQ} <: AbstractOrbital
    n::N
    κ::Int
    function RelativisticOrbital(n::Integer, κ::Integer)
        n ≥ 1 || throw(ArgumentError("Invalid principal quantum number $(n)"))
        κ == zero(κ) && throw(ArgumentError("κ can not be zero"))
        ℓ = κ2ℓ(κ)
        0 ≤ ℓ && ℓ < n || throw(ArgumentError("Angular quantum number has to be ∈ [0,$(n-1)] when n = $(n)"))
        new{Int}(n, κ)
    end
    function RelativisticOrbital(n::Symbol, κ::Integer)
        κ == zero(κ) && throw(ArgumentError("κ can not be zero"))
        new{Symbol}(n, κ)
    end
end
RelativisticOrbital(n::MQ, ℓ::Integer, j::Real) = RelativisticOrbital(n, ℓj2κ(ℓ, j))

Base.:(==)(a::RelativisticOrbital, b::RelativisticOrbital) =
    a.n == b.n && a.κ == b.κ

Base.hash(o::RelativisticOrbital, h::UInt) = hash(o.n, hash(o.κ, h))

"""
    mqtype(::RelativisticOrbital{MQ}) = MQ

Returns the main quantum number type of a [`RelativisticOrbital`](@ref).
"""
mqtype(::RelativisticOrbital{MQ}) where MQ = MQ

Base.propertynames(::RelativisticOrbital) = (fieldnames(RelativisticOrbital)..., :j, :ℓ)
function Base.getproperty(o::RelativisticOrbital, s::Symbol)
    s === :j ? κ2j(o.κ) :
    s === :ℓ ? κ2ℓ(o.κ) :
    getfield(o, s)
end

function Base.show(io::IO, orb::RelativisticOrbital)
    write(io, "$(orb.n)$(spectroscopic_label(κ2ℓ(orb.κ)))")
    orb.κ > 0 && write(io, "-")
end

function flip_j(orb::RelativisticOrbital)
    orb.κ == -1 && return RelativisticOrbital(orb.n, -1) # nothing to flip for s-orbitals
    RelativisticOrbital(orb.n, orb.κ < 0 ? abs(orb.κ) - 1 : -(orb.κ + 1))
end

degeneracy(orb::RelativisticOrbital{N}) where N = 2*abs(orb.κ) # 2j + 1 = 2|κ|

function Base.isless(a::RelativisticOrbital, b::RelativisticOrbital)
    nisless(a.n, b.n) && return true
    aℓ, bℓ = κ2ℓ(a.κ), κ2ℓ(b.κ)
    a.n == b.n && aℓ < bℓ && return true
    a.n == b.n && aℓ == bℓ && abs(a.κ) < abs(b.κ) && return true
    false
end

parity(orb::RelativisticOrbital) = p"odd"^κ2ℓ(orb.κ)
symmetry(orb::RelativisticOrbital) = orb.κ

isbound(::RelativisticOrbital{Int}) = true
isbound(::RelativisticOrbital{Symbol}) = false

"""
    angular_momenta(orbital)

Returns the angular momentum quantum numbers of `orbital`.

# Examples
```jldoctest
julia> angular_momenta(ro"2p-")
(1/2,)

julia> angular_momenta(ro"3d")
(5/2,)
```
"""
angular_momenta(orbital::RelativisticOrbital) = (orbital.j,)
angular_momentum_labels(::RelativisticOrbital) = ("j",)

# ** Saving/loading

Base.write(io::IO, o::RelativisticOrbital{Int}) = write(io, 'i', o.n, o.κ)
Base.write(io::IO, o::RelativisticOrbital{Symbol}) = write(io, 's', sizeof(o.n), o.n, o.κ)

function Base.read(io::IO, ::Type{RelativisticOrbital})
    kind = read(io, Char)
    n = if kind == 'i'
        read(io, Int)
    elseif kind == 's'
        b = Vector{UInt8}(undef, read(io, Int))
        readbytes!(io, b)
        Symbol(b)
    else
        error("Unknown RelativisticOrbital type $(kind)")
    end
    κ = read(io, Int)
    RelativisticOrbital(n, κ)
end

# * Orbital construction from strings

function Base.parse(::Type{<:RelativisticOrbital}, orb_str)
    m = match(r"^([0-9]+|.)([a-z]|\[[0-9]+\])([-]{0,1})$", orb_str)
    isnothing(m) && throw(ArgumentError("Invalid orbital string: $(orb_str)"))
    n = parse_orbital_n(m)
    ℓ = parse_orbital_ℓ(m)
    j = ℓ + (m[3] == "-" ? -1 : 1)*1//2
    RelativisticOrbital(n, ℓ, j)
end

"""
    @ro_str -> RelativisticOrbital

A string macro to construct an [`RelativisticOrbital`](@ref) from the canonical string
representation.

```jldoctest
julia> ro"1s"
1s

julia> ro"2p-"
2p-

julia> ro"Kf-"
Kf-
```
"""
macro ro_str(orb_str)
    parse(RelativisticOrbital, orb_str)
end

"""
    @ros_str -> Vector{RelativisticOrbital}

Can be used to easily construct a list of [`RelativisticOrbital`](@ref)s.

# Examples

```jldoctest
julia> ros"2[s-p] 3[p] k[0-d]"
10-element Array{RelativisticOrbital,1}:
 2s
 2p-
 2p
 3p-
 3p
 ks
 kp-
 kp
 kd-
 kd
```
"""
macro ros_str(orbs_str)
    orbitals_from_string(RelativisticOrbital, orbs_str)
end

"""
    str2κ(s) -> Int

A function to convert the canonical string representation of a ``\\ell_j`` angular label
(i.e. `ℓ-` or `ℓ`) into the corresponding ``\\kappa`` quantum number.

```jldoctest
julia> str2κ.(["s", "p-", "p"])
3-element Array{Int64,1}:
 -1
  1
 -2
```
"""
function str2κ(κ_str)
    m = match(r"^([a-z]|\[[0-9]+\])([-]{0,1})$", κ_str)
    m === nothing && throw(ArgumentError("Invalid κ string: $(κ_str)"))
    ℓ = parse_orbital_ℓ(m, 1)
    j = ℓ + half(m[2] == "-" ? -1 : 1)
    ℓj2κ(ℓ, j)
end

"""
    @κ_str -> Int

A string macro to convert the canonical string representation of a ``\\ell_j`` angular label
(i.e. `ℓ-` or `ℓ`) into the corresponding ``\\kappa`` quantum number.

```jldoctest
julia> κ"s", κ"p-", κ"p"
(-1, 1, -2)
```
"""
macro κ_str(κ_str)
    str2κ(κ_str)
end

"""
    nonrelorbital(o)

Return the non-relativistic orbital corresponding to `o`.

# Examples

```jldoctest
julia> nonrelorbital(o"2p")
2p

julia> nonrelorbital(ro"2p-")
2p
```
"""
nonrelorbital(o::Orbital) = o
nonrelorbital(o::RelativisticOrbital) = Orbital(o.n, o.ℓ)

export RelativisticOrbital, @ro_str, @ros_str, @κ_str, nonrelorbital, str2κ, κ2ℓ, κ2j, ℓj2κ


# Test real-to-complex FFT using a backwards plan (BRFFT).

using PencilFFTs
using MPI
using LinearAlgebra
using FFTW

using Test

MPI.Init()
comm = MPI.COMM_WORLD

let dev_null = @static Sys.iswindows() ? "nul" : "/dev/null"
    MPI.Comm_rank(comm) == 0 || redirect_stdout(open(dev_null, "w"))
end

@testset "BRFFT: odd = $odd" for odd ∈ (false, true)
    dims_real = (12, 13, 16 + odd)  # dimensions in physical (real) space
    dims_coef = (12, 13, 9)         # dimensions in coefficient (complex) space
    pen = Pencil(dims_coef, comm)
    uc = PencilArray{ComplexF64}(undef, pen)
    uc .= 0
    uc[2, 4, 3] = 1 + 2im

    plan_c2r = PencilFFTPlan(uc, Transforms.BRFFT(dims_real))
    @test size(plan_c2r) == dims_coef  # = size of input

    ur = plan_c2r * uc
    @test size_global(ur, LogicalOrder()) == dims_real

    # Equivalent using FFTW.
    # Note that by default FFTW performs the c2r transform along the first
    # dimension, while PencilFFTs does it along the last one.
    uc_fftw = gather(uc)
    ur_full = gather(ur)
    if uc_fftw !== nothing
        bfft!(uc_fftw, (1, 2))
        ur_fftw = brfft(uc_fftw, dims_real[end], 3)
        @test ur_full ≈ ur_fftw
    end

    # Check normalisation
    uc_back = plan_c2r \ ur
    @test isapprox(uc_back, uc; atol = 1e-8)
end


module OrthogonalPolynomials

# 0. Our mission
# Dear codes, should you choose to accept it, is to learn and implement the following
# mathematical in Julia, whilst learning the magic of multiple dispatch as a design
# paradigm. Ready? Steady? Go!
# If you are a beginner and this code just looks like squiggles, worry not!
# There is a video tutorial online at my youtube channel, BrainRPG.
# Happy Hacking!
# - Miguel Raz Guzman Macedo
# http://people.math.sfu.ca/~cbm/aands/page_789.jpg

# 1. First design - Laguerre only
# Note the _names with underscore are used as convention for package internals.
_d(n, α=0) = binomial(n+α, n)
_b(n, m) = n-m+1
_c(α, m) = m*(α+m)
_k(n, α=0) = [-_b(n,i)*inv(_c(α, i)) for i in 1:n]
_f(x) = x
_α = 0

_a(x, n, ks = _k(n,α), i=0) = i == n ? :(1) : return :(muladd( $(ks[i+1]*_f(x)), $((_a)(x, n, ks, i+1)) , 1))

# 2. Macro function as a for loop
macro _a(x,n,ks=k(n,α))
    ex = 1
    for i in n-1 : -1 : 0
        ex = :(muladd( $(ks[i+1]*_f(x)), $ex, 1))
    end
    return :($ex)
end

# 2. Second design, now with dispatching on types.
# 2.1 FIRST, we will define all the infrastructure for dispatching for Laguerre.
# 2.2 THEN, we will extend that design by adding dispatches for the Hermite case.
# 2.3 LAST, we will extend for all the other cases.

# 2.1 FIRST, the Laguerre case.
abstract type OP end
struct Laguerre{α,n} <: OP end
params(a :: T) where T = Tuple(T.parameters) # Credit to @yingboma
Laguerre(α, n :: Int) = Laguerre{α, n}()
Laguerre(n :: Int) = n >= 0 ? Laguerre{0,n}() : throw("The degree n must be non-negative.")
# Generalized binomial, useful too in the Jacobi case.
function Base.binomial(α :: T,k :: Int) where T<:AbstractFloat
    res = 1
    for i in 0:k-1
        res = res * (α - i)
    end
    return res/factorial(k)
end

d(p :: T, x) where T<:Laguerre = d(p)
function d(p :: T) where T<:Laguerre
    α,n = params(p)
    return binomial(α + n, n)
end

function b(p :: T, m) where T<:Laguerre
    α,n = params(p)
    return n-m+1
end

function c(p :: T, m) where T<:Laguerre
    α, n = params(p)
    return m*(α + m)
end

f(p :: T, x) where T<:Laguerre = x

# This now works for all Orthogonal Polynomials!
function k(p :: T, x) where T<:OP
    n = params(p)[end]
    return [-b(p, i)*inv(c(p, i)) for i in 1:n]
end

# Notice this works for all types Orthogonal Polynomials!
# Note: It doesn't return a specialized function yet - try to run it with @code_llvm
# We will make this specialized in the Third stage of design, after we add all the dispatches
# for all the polynomials.
function a(p :: T, x, m = params(p)[end]) where T<:OP
    res = oneunit(x)
    ks = k(p, x)

    for i in m-1: -1 : 0
        res = muladd(res, ks[i+1]*f(p, x),1)
    end

    return res * d(p, x)
end

# function a(p :: T, x, i=

# 2.2 SECOND - add a little abstract type machinery to handle dispatches for even/odd cases.
abstract type EvenOP <: OP end
abstract type OddOP <: OP end
struct Hermite{n} <: OP end
struct HermiteEven{n} <: EvenOP end
struct HermiteOdd{n} <: OddOP end

Hermite{n}() where n = iseven(n) ? HermiteEven{n ÷ 2}() : HermiteOdd{(n-1) ÷ 2}()
Hermite(n) =  n>= 0 ? Hermite{n}() : throw("The degree n must be non-negative.")

#Heven
function d(p :: T, x) where T<:HermiteEven
    n = params(p)[1]
    return (-1) ^ n * (factorial(2n)/factorial(n))
end

function b(p :: T, m) where T<:HermiteEven
    n = params(p)[1]
    return  2*(n - m + 1)
end

# This now covers half the cases!
c(p :: T, m) where T<:EvenOP = m*(2m - 1)

# This catches almost every case!
f(p :: T, x) where T<:OP = x^2

#Hodd
function d(p :: T, x) where T<:HermiteOdd
    n = params(p)[1]
    return (-1) ^ n * (factorial(2n +1)/factorial(n))*2x
end

function b(p :: T, m) where T<:HermiteOdd
    n = params(p)[1]
    return  2*(n - m + 1)
end

# This catches almost every case left over! We are only missing the Jacobi case.
c(p :: T, m) where T<:OddOP = m * (2m + 1)

# 2.3 THIRD, we now add dispatches to all the other cases.
struct Legendre{n} <: OP end
struct LegendreEven{n} <: EvenOP end
struct LegendreOdd{n} <: OddOP end
Legendre{n}() where n = iseven(n) ? LegendreEven{n ÷ 2}() : LegendreOdd{(n-1) ÷ 2}()
Legendre(n :: Int) = n>=0 ? Legendre{n}() : throw("The degree n must be non-negative.")

# PEven
# Note! We only have to define two functions per case here, because the other cases already got caught by
# some adequate planning and dispatching.
function d(p :: T, x) where T<:LegendreEven
    n = params(p)[1]
    return (-1/4)^n * binomial(2n,n)
end

function b(p :: T, m) where T<:LegendreEven
    n = params(p)[1]
    return (n - m + 1) * (2n + 2m -1)
end

# POdd
function d(p :: T, x) where T<:LegendreOdd
    n = params(p)[1]
    return (-1/4)^n * binomial(2n+1,n) * (n+1) * x
end

function b(p :: T, m) where T<:LegendreOdd
    n = params(p)[1]
    return (n - m + 1) * (2n + 2m + 1)
end

# TEven
struct ChebyshevFirstKind{n} <: OP end
struct ChebyshevFirstKindEven{n} <: EvenOP end
struct ChebyshevFirstKindOdd{n} <: OddOP end
ChebyshevFirstKind{n}() where n = iseven(n) ? ChebyshevFirstKindEven{n ÷ 2}() : ChebyshevFirstKindOdd{(n-1) ÷ 2}()
ChebyshevFirstKind(n :: Int) = n>=0 ? ChebyshevFirstKind{n}() : throw("The degree n must be non-negative")

function d(p :: T, x) where T<:ChebyshevFirstKindEven
    n = params(p)[1]
    return (-1)^n
end

function b(p :: T, m) where T<:ChebyshevFirstKindEven
    n = params(p)[1]
    return 2(n-m+1)*(n+m-1)
end

# TOdd
function d(p :: T, x) where T<: ChebyshevFirstKindOdd
    n = params(p)[1]
    return (-1^n)*(2n+1)*x
end

function b(p ::T, m) where T<: ChebyshevFirstKindOdd
    n = params(p)[1]
    return  2*(n-m+1)*(n+m)
end

# UEven
struct ChebyshevSecondKind{n} <: OP end
struct ChebyshevSecondKindEven{n} <: EvenOP end
struct ChebyshevSecondKindOdd{n} <: OddOP end
ChebyshevSecondKind{n}() where n = iseven(n) ? ChebyshevSecondKindEven{n ÷ 2}() : ChebyshevSecondKindOdd{(n-1) ÷ 2}()
ChebyshevSecondKind(n :: Int) = n>=0 ? ChebyshevSecondKind{n}() : throw("The degree n must be non-negative")

function d(p :: T, x) where T<:ChebyshevSecondKindEven
    n = params(p)[1]
    return (-1)^n
end

function b(p :: T, m) where T<:ChebyshevSecondKindEven
    n = params(p)[1]
    return 2*(n-m+1)*(n+m)
end

# UOdd
function d(p :: T, x) where T<:ChebyshevSecondKindOdd
    n = params(p)[1]
    return (-1)^n * 2(n+1)*x
end

function b(p :: T, m) where T<:ChebyshevSecondKindOdd
    n = params(p)[1]
    return 2*(n-m+1)*(n+m)
end

# CEven
struct Gegenbauer{α, n} <: OP end
struct GegenbauerEven{α, n} <: EvenOP end
struct GegenbauerOdd{α, n} <: OddOP end
function Gegenbauer(α, n :: Int)
     n >= 0 && α >= 0 ? nothing : throw("The degree n must be non-negative")
     iseven(n) ? GegenbauerEven{α, n ÷ 2}() : GegenbauerOdd{α, (n-1) ÷ 2}()
end
Gegenbauer(n :: Int) = Gegenbauer(0, n)

# We need to define a pochhammer symbol here!
function poch(a,::Val{N}) where N
            res = a; for i in Base.OneTo(N-1)
                res = res * (a+i)
            end
        return res
end
poch(a, ::Val{0}) = one(a)
poch(a, ::Val{1}) = a

function d(p :: T, x = 0) where T<:GegenbauerEven
    α, n  = params(p)
    return (-1)^n * (poch(α,Val(n))/factorial(n))
end

function b(p :: T, m) where T<:GegenbauerEven
    α, n = params(p)
    return 2*(n-m+1)*(α+n+m-1)
end

# COdd
function d(p :: T, x) where T<:GegenbauerOdd
    α, n = params(p)
    return (-1)^n * (poch(α,Val(n+1))/factorial(n))*2x
end

function b(p :: T, m) where T<:GegenbauerOdd
    α, n = params(p)
    return  2*(n-m+1)*(α + n + m)
end

# Jacobi
struct Jacobi{α, β, n} <: OP end
Jacobi(α, β, n :: Int) = α >= 0 && β >= 0 && n >= 0 ? Jacobi{α, β, n}() : throw("The parameters must be non-negative")

function d(p :: T, x) where T<:Jacobi
    α, β, n = params(p)
    return binomial(n + α, n)
end

function b(p :: T, m) where T<:Jacobi
    α, β, n = params(p)
    return (n - m + 1) * (α + β + n + m)
end

function c(p :: T, m) where T<:Jacobi
    α, β, n = params(p)
    return 2m * (α + m)
end

function f(p :: T, x) where T<:Jacobi
    return 1 - x
end

# Pass: Laguerre, ChebyFE, ChebySE,
# Check Jacobi-binomial, HermiteEven, HermiteOdd, LegendreE, LegendreO, ChebySO, ChebyFO, GegenbauerE, GegenbauerO
# Strat, look at the a(j,2,0) and k(j,.5) methods
export d,b,c,k,f,a
export Jacobi                                                                   # J(α,β,n)
export GegenbauerEven, GegenbauerOdd, Gegenbauer                                # C(α,n)
export ChebyshevSecondKindEven, ChebyshevSecondKindOdd, ChebyshevSecondKind     # U(n)
export ChebyshevFirstKindEven, ChebyshevFirstKindOdd, ChebyshevFirstKind        # T(n)
export Legendre                                                                 # L(n)
export HermiteOdd, HermiteEven, Hermite                                         # H(n)
export Laguerre

end # module


model = @reaction_network SSTest begin
    (d_x, d_y), (x, y) --> 0
    mm(y, 2, 1), 0 --> x
    1., 0 --> y
end d_x d_y

u0 = [0.1, 0.1]
p = [1., 1.]

prob = SteadyStateProblem{true}(model, u0, p)
sol = solve(prob, SSRootfind())
@test sol.u ≈ [1, 1]

u0 = [0.1, 0.1]
prob = SteadyStateProblem(model, u0, p)
sol = solve(prob, SSRootfind())
@test sol.u ≈ [1, 1]


#src # Differentiable programming
using Test: @test #src

# [Julia and its ecosystem provide some tools for differentiable programming](https://sinews.siam.org/Details-Page/scientific-machine-learning-how-julia-employs-differentiable-programming-to-do-it-best).
# Trixi.jl is designed to be flexible, extendable, and composable with Julia's growing ecosystem for
# scientific computing and machine learning. Thus, the ultimate goal is to have fast implementations
# that allow automatic differentiation (AD) without too much hassle for users. If some parts do not
# meet these requirements, please feel free to open an issue or propose a fix in a PR.

# In the following, we will walk through some examples demonstrating how to differentiate through
# Trixi.jl.

# ## Forward mode automatic differentiation

# Trixi integrates well with [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl)
# for forward mode AD.

# ### Computing the Jacobian
# The high-level interface to compute the Jacobian this way is [`jacobian_ad_forward`](@ref).
# First, we load the required packages and compute the Jacobian of a semidiscretization
# of the compressible Euler equations, a system of nonlinear conservation laws.

using Trixi, LinearAlgebra, Plots

equations = CompressibleEulerEquations2D(1.4)

solver = DGSEM(3, flux_central)
mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=2, n_cells_max=10^5)

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_density_wave, solver)

J = jacobian_ad_forward(semi);
size(J)
@test size(J) == (1024, 1024) #src

# Next, we compute the eigenvalues of the Jacobian.

λ = eigvals(J)
scatter(real.(λ), imag.(λ), label="central flux")

# As you can see here, the maximal real part is close to zero.

relative_maximum = maximum(real, λ) / maximum(abs, λ)
@test 3.0e-10 < relative_maximum < 8.0e-10 #src

# Interestingly, if we add dissipation by switching to the `flux_lax_friedrichs`
# at the interfaces, the maximal real part of the eigenvalues increases.

solver = DGSEM(3, flux_lax_friedrichs)
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_density_wave, solver)

J = jacobian_ad_forward(semi)
λ = eigvals(J)

scatter!(real.(λ), imag.(λ), label="Lax-Friedrichs flux")

# Although the maximal real part is still somewhat small, it's larger than for
# the purely central discretization.

relative_maximum = maximum(real, λ) / maximum(abs, λ)
@test 1.0e-5 < relative_maximum < 5.0e-5 #src

# However, we should be careful when using this analysis, since the eigenvectors
# are not necessarily well-conditioned.

λ, V = eigen(J)
condition_number = cond(V)
@test 1.0e6 < condition_number < 5.0e6 #src

# In one space dimension, the situation is a bit different.

equations = CompressibleEulerEquations1D(1.4)

solver = DGSEM(3, flux_central)
mesh = TreeMesh((-1.0,), (1.0,), initial_refinement_level=6, n_cells_max=10^5)

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_density_wave, solver)

J = jacobian_ad_forward(semi)

λ = eigvals(J)

scatter(real.(λ), imag.(λ), label="central flux")

# Here, the maximal real part is basically zero to machine accuracy.

relative_maximum = maximum(real, λ) / maximum(abs, λ)
@test 1.0e-17 < relative_maximum < 1.0e-15 #src

# Moreover, the eigenvectors are not as ill-conditioned as in 2D.

λ, V = eigen(J)
condition_number = cond(V)
@test 200 < condition_number < 4000 #src

# If we add dissipation, the maximal real part is still approximately zero.

solver = DGSEM(3, flux_lax_friedrichs)
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_density_wave, solver)

J = jacobian_ad_forward(semi)
λ = eigvals(J)

scatter!(real.(λ), imag.(λ), label="Lax-Friedrichs flux")

# As you can see from the plot generated above, the maximal real part is still
# basically zero to machine precision.

relative_maximum = maximum(real, λ) / maximum(abs, λ)
@test -1.0e-15 < relative_maximum < 1.0e-15 #src

# Let's check the condition number of the eigenvectors.

λ, V = eigen(J)

condition_number = cond(V)
@test 70_000 < condition_number < 200_000 #src

# Note that the condition number of the eigenvector matrix increases but is
# still smaller than for the example in 2D.

# ### Computing other derivatives

# It is also possible to compute derivatives of other dependencies using AD in Trixi. For example,
# you can compute the gradient of an entropy-dissipative semidiscretization with respect to the
# ideal gas constant of the compressible Euler equations as described in the following. This example
# is also available as the elixir
# [examples/special\_elixirs/elixir\_euler\_ad.jl](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/special_elixirs/elixir_euler_ad.jl)

# First, we create a semidiscretization of the compressible Euler equations.

using Trixi, LinearAlgebra, ForwardDiff

equations = CompressibleEulerEquations2D(1.4)

"""
    initial_condition_isentropic_vortex(x, t, equations::CompressibleEulerEquations2D)

The classical isentropic vortex test case of
- Chi-Wang Shu (1997)
  Essentially Non-Oscillatory and Weighted Essentially Non-Oscillatory
  Schemes for Hyperbolic Conservation Laws
  [NASA/CR-97-206253](https://ntrs.nasa.gov/citations/19980007543)
"""
function initial_condition_isentropic_vortex(x, t, equations::CompressibleEulerEquations2D)
  inicenter = SVector(0.0, 0.0) # size and strength of the vortex
  iniamplitude = 0.2

  rho = 1.0 # base flow
  v1 = 1.0
  v2 = 1.0
  vel = SVector(v1, v2)
  p = 10.0

  rt = p / rho                  # ideal gas equation
  cent = inicenter + vel*t      # advection of center
  cent = x - cent               # distance to centerpoint
  cent = SVector(-cent[2], cent[1])
  r2 = cent[1]^2 + cent[2]^2
  du = iniamplitude/(2*π)*exp(0.5*(1-r2)) # vel. perturbation
  dtemp = -(equations.gamma-1)/(2*equations.gamma*rt)*du^2 # isentrop

  rho = rho * (1+dtemp)^(1\(equations.gamma-1))
  vel = vel + du*cent
  v1, v2 = vel
  p = p * (1+dtemp)^(equations.gamma/(equations.gamma-1))

  prim = SVector(rho, v1, v2, p)
  return prim2cons(prim, equations)
end

mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=2, n_cells_max=10^5)

solver = DGSEM(3, flux_lax_friedrichs, VolumeIntegralFluxDifferencing(flux_ranocha))

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_isentropic_vortex, solver)

u0_ode = compute_coefficients(0.0, semi)
size(u0_ode)
@test size(u0_ode) == (1024,) #src

# Next, we compute the Jacobian using `ForwardDiff.jacobian`.

J = ForwardDiff.jacobian((du_ode, γ) -> begin
    equations_inner = CompressibleEulerEquations2D(first(γ))
    semi_inner = Trixi.remake(semi, equations=equations_inner, uEltype=eltype(γ))
    Trixi.rhs!(du_ode, u0_ode, semi_inner, 0.0)
end, similar(u0_ode), [1.4]); # γ needs to be an `AbstractArray`

round.(extrema(J), sigdigits=2)
@test round.(extrema(J), sigdigits=2) == (-5.6, 5.6) #src

# Note that we create a semidiscretization `semi` at first to determine the state `u0_ode` around
# which we want to perform the linearization. Next, we wrap the RHS evaluation inside a closure
# and pass that to `ForwardDiff.jacobian`. There, we need to make sure that the internal caches
# are able to store dual numbers from ForwardDiff.jl by setting `uEltype` appropriately. A similar
# approach is used by [`jacobian_ad_forward`](@ref).

# Note that the ideal gas constant does not influence the semidiscrete rate of change of the
# density, as demonstrated by

norm(J[1:4:end])
@test norm(J[1:4:end]) == 0.0 #src

# Here, we used some knowledge about the internal memory layout of Trixi, an array of structs
# with the conserved variables as fastest-varying index in memory.

# ## Differentiating through a complete simulation

# It is also possible to differentiate through a complete simulation. As an example, let's differentiate
# the total energy of a simulation using the linear scalar advection equation with respect to the
# wave number (frequency) of the initial data.

using Trixi, OrdinaryDiffEq, ForwardDiff, Plots

function energy_at_final_time(k) # k is the wave number of the initial condition
    equations = LinearScalarAdvectionEquation2D(1.0, -0.3)
    mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=3, n_cells_max=10^4)
    solver = DGSEM(3, flux_lax_friedrichs)
    initial_condition = (x, t, equation) -> begin
            x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t)
            return SVector(sinpi(k * sum(x_trans)))
    end
    semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
                                               uEltype=typeof(k))
    ode = semidiscretize(semi, (0.0, 1.0))
    sol = solve(ode, BS3(), save_everystep=false)
    Trixi.integrate(energy_total, sol.u[end], semi)
end

k_values = range(0.9, 1.1, length=101)

plot(k_values, energy_at_final_time.(k_values), label="Energy")

# You see a plot of a curve that resembles a parabola with local maximum around `k = 1.0`.
# Why's that? Well, the domain is fixed but the wave number changes. Thus, if the wave number is
# not chosen as an integer, the initial condition will not be a smooth periodic function in the
# given domain. Hence, the dissipative surface flux (`flux_lax_friedrichs` in this example)
# will introduce more dissipation. In particular, it will introduce more dissipation for "less smooth"
# initial data, corresponding to wave numbers `k` further away from integers.

# We can compute the discrete derivative of the energy at the final time with respect to the wave
# number `k` as follows.

round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits=2)
@test round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits=2) == 1.4e-5 #src

# This is rather small and we can treat it as zero in comparison to the value of this derivative at
# other wave numbers `k`.

dk_values = ForwardDiff.derivative.((energy_at_final_time,), k_values);

plot(k_values, dk_values, label="Derivative")

# If you remember basic calculus, a sufficient condition for a local maximum is that the first derivative
# vanishes and the second derivative is negative. We can also check this discretely.

second_derivative = round(ForwardDiff.derivative(
        k -> Trixi.ForwardDiff.derivative(energy_at_final_time, k), 1.0),
      sigdigits=2)
@test second_derivative ≈ -0.9 #src

# Having seen this application, let's break down what happens step by step.

function energy_at_final_time(k) # k is the wave number of the initial condition
    equations = LinearScalarAdvectionEquation2D(1.0, -0.3)
    mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=3, n_cells_max=10^4)
    solver = DGSEM(3, flux_lax_friedrichs)
    initial_condition = (x, t, equation) -> begin
        x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t)
        return SVector(sinpi(k * sum(x_trans)))
    end
    semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
                                               uEltype=typeof(k))
    ode = semidiscretize(semi, (0.0, 1.0))
    sol = solve(ode, BS3(), save_everystep=false)
    Trixi.integrate(energy_total, sol.u[end], semi)
end

k = 1.0
round(ForwardDiff.derivative(energy_at_final_time, k), sigdigits=2)
@test round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits=2) == 1.4e-5 #src

# When calling `ForwardDiff.derivative(energy_at_final_time, k)` with `k=1.0`, ForwardDiff.jl
# will basically use the chain rule and known derivatives of existing basic functions
# to calculate the derivative of the energy at the final time with respect to the
# wave number `k` at `k0 = 1.0`. To do this, ForwardDiff.jl uses dual numbers, which
# basically store the result and its derivative w.r.t. a specified parameter at the
# same time. Thus, we need to make sure that we can treat these `ForwardDiff.Dual`
# numbers everywhere during the computation. Fortunately, generic Julia code usually
# supports these operations. The most basic problem for a developer is to ensure
# that all types are generic enough, in particular the ones of internal caches.

# The first step in this example creates some basic ingredients of our simulation.
equations = LinearScalarAdvectionEquation2D(1.0, -0.3)
mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=3, n_cells_max=10^4)
solver = DGSEM(3, flux_lax_friedrichs);

# These do not have internal caches storing intermediate values of the numerical
# solution, so we do not need to adapt them. In fact, we could also define them
# outside of `energy_at_final_time` (but would need to take care of globals or
# wrap everything in another function).

# Next, we define the initial condition
initial_condition = (x, t, equation) -> begin
    x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t)
    return SVector(sinpi(k * sum(x_trans)))
end;
# as a closure capturing the wave number `k` passed to `energy_at_final_time`.
# If you call `energy_at_final_time(1.0)`, `k` will be a `Float64`. Thus, the
# return values of `initial_condition` will be `SVector`s of `Float64`s. When
# calculating the `ForwardDiff.derivative`, `k` will be a `ForwardDiff.Dual` number.
# Hence, the `initial_condition` will return `SVector`s of `ForwardDiff.Dual`
# numbers.

# The semidiscretization `semi` uses some internal caches to avoid repeated allocations
# and speed up the computations, e.g. for numerical fluxes at interfaces. Thus, we
# need to tell Trixi to allow `ForwardDiff.Dual` numbers in these caches. That's what
# the keyword argument `uEltype=typeof(k)` in
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
                                    uEltype=typeof(k));

# does. This is basically the only part where you need to modify your standard Trixi
# code to enable automatic differentiation. From there on, the remaining steps
ode = semidiscretize(semi, (0.0, 1.0))
sol = solve(ode, BS3(), save_everystep=false)
round(Trixi.integrate(energy_total, sol.u[end], semi), sigdigits=5)
@test round(Trixi.integrate(energy_total, sol.u[end], semi), sigdigits=5) == 0.24986 #src

# do not need any modifications since they are sufficiently generic (and enough effort
# has been spend to allow general types inside these calls).

# ## Propagating errors using Measurements.jl

# [![Error bars by Randall Munroe](https://imgs.xkcd.com/comics/error_bars.png)](https://xkcd.com/2110/)

# Similar to AD, Trixi also allows propagating uncertainties using linear error propagation
# theory via [Measurements.jl](https://github.com/JuliaPhysics/Measurements.jl).
# As an example, let's create a system representing the linear advection equation
# in 1D with an uncertain velocity. Then, we create a semidiscretization using a
# sine wave as initial condition, solve the ODE, and plot the resulting uncertainties
# in the primitive variables.

using Trixi, OrdinaryDiffEq, Measurements, Plots, LaTeXStrings

equations = LinearScalarAdvectionEquation1D(1.0 ± 0.1)

mesh = TreeMesh((-1.0,), (1.0,), n_cells_max=10^5, initial_refinement_level=5)

solver = DGSEM(3)

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test,
                                    solver, uEltype=Measurement{Float64})

ode = semidiscretize(semi, (0.0, 1.5))

sol = solve(ode, BS3(), save_everystep=false);

plot(sol)

# You should see a plot where small error bars are shown around the extrema
# and larger error bars are shown in the remaining parts.
# This result is in accordance with expectations. Indeed, the uncertain propagation
# speed will affect the extrema less since the local variation of the solution is
# relatively small there. In contrast, the local variation of the solution is large
# around the turning points of the sine wave, so the uncertainties will be relatively
# large there.

# All this is possible due to allowing generic types and having good abstractions
# in Julia that allow packages to work together seamlessly.

# ## Finite difference approximations

# Trixi provides the convenience function [`jacobian_fd`](@ref) to approximate the Jacobian
# via central finite differences.

using Trixi, LinearAlgebra

equations = CompressibleEulerEquations2D(1.4)

solver = DGSEM(3, flux_central)

mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=2, n_cells_max=10^5)

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_density_wave, solver)

J_fd = jacobian_fd(semi)

J_ad = jacobian_ad_forward(semi)

relative_difference = norm(J_fd - J_ad) / size(J_fd, 1)
@test relative_difference < 7.0e-7 #src

# This discrepancy is of the expected order of magnitude for central finite difference approximations.

# ## Linear systems

# When a linear PDE is discretized using a linear scheme such as a standard DG method,
# the resulting semidiscretization yields an affine ODE of the form

# ```math
# \partial_t u(t) = A u(t) + b,
# ```

# where `A` is a linear operator ("matrix") and `b` is a vector. Trixi allows you
# to obtain this linear structure in a matrix-free way by using [`linear_structure`](@ref).
# The resulting operator `A` can be used in multiplication, e.g. `mul!` from
# LinearAlgebra, converted to a sparse matrix using `sparse` from SparseArrays,
# or converted to a dense matrix using `Matrix` for detailed eigenvalue analyses.
# For example,

using Trixi, LinearAlgebra, Plots

equations = LinearScalarAdvectionEquation2D(1.0, -0.3)

solver = DGSEM(3, flux_lax_friedrichs)

mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=2, n_cells_max=10^5)

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, solver)

A, b = linear_structure(semi)

size(A), size(b)
@test (size(A), size(b)) == ((256, 256), (256,)) #src

# Next, we compute the eigenvalues of the linear operator.

λ = eigvals(Matrix(A))

scatter(real.(λ), imag.(λ))

# As you can see here, the maximal real part is close to machine precision.

λ = eigvals(Matrix(A))
relative_maximum = maximum(real, λ) / maximum(abs, λ)
@test relative_maximum < 1.0e-15 #src