From 2554522b8a707ffec22f00200165090a9fe83229 Mon Sep 17 00:00:00 2001 From: jarjarbinks02 Date: Thu, 13 Aug 2026 13:42:36 +0200 Subject: [PATCH 1/5] updated practical 1 (and deleted all the old ones) --- src/exercises/MCMC_1-intro.jl | 450 ------ src/exercises/MCMC_2-basics.jl | 888 ----------- src/exercises/MCMC_3-advanced.jl | 295 ---- src/exercises/MCMC_4-review.jl | 153 -- src/exercises/calib_fermenter_monod.jl | 300 ---- src/exercises/calib_intro.jl | 1033 ------------- src/exercises/calib_irrigation.jl | 337 ---- src/exercises/exercises.md | 42 - src/exercises/model_selection_intro.jl | 1356 ----------------- src/exercises/ode_model_XTRA_tank_T_h_mtk.jl | 342 +++++ .../ode_model_XTRA_temp_reactors_mtk.jl | 413 +++++ .../ode_model_XTRA_water_evap_infil_mtk.jl | 331 ++++ .../ode_model_anaerobic_fermentation.jl | 467 ------ src/exercises/ode_model_birth_death.jl | 265 ---- src/exercises/ode_model_catalyst_intro.jl | 812 ---------- src/exercises/ode_model_diver_mtk.jl | 886 +++++++++++ .../ode_model_fermenter_firstorder.jl | 206 --- src/exercises/ode_model_fermenter_monod.jl | 477 ------ src/exercises/ode_model_infection.jl | 751 --------- src/exercises/ode_model_irrigation.jl | 243 --- src/exercises/ode_model_irrigation_mtk.jl | 278 ++++ src/exercises/ode_model_mtk_intro.jl | 640 ++++++++ .../ode_model_soil_cont_plant_uptake.jl | 187 --- src/exercises/ode_model_tank_h_mtk.jl | 526 +++++++ src/exercises/ode_model_tractor_seat_mtk.jl | 329 ++++ src/exercises/ode_model_water_evap_infil.jl | 233 --- src/exercises/optim_wastewater_treatment.jl | 332 ---- src/exercises/probabilistic_selection.jl | 670 -------- src/exercises/probmod_1-intro.jl | 452 ------ src/exercises/probmod_2-basics.jl | 374 ----- src/exercises/probmod_3-advanced.jl | 269 ---- src/exercises/probmod_4-review.jl | 76 - src/exercises/sde_model_aging.jl | 343 ----- src/exercises/sde_model_catalyst_intro.jl | 382 ----- .../sde_model_fermenter_secondorder.jl | 174 --- src/exercises/sens_bitrophic_model.jl | 412 ----- src/exercises/sens_fermenter_monod.jl | 432 ------ src/exercises/sens_insuline.jl | 531 ------- src/exercises/sens_intro.jl | 811 ---------- src/exercises/ssa_model_catalyst_intro.jl | 392 ----- src/exercises/ssa_model_foxes_rabbits.jl | 263 ---- src/exercises/uncert_bitrophic_model.jl | 167 -- src/exercises/uncert_fermenter_monod.jl | 181 --- src/exercises/uncert_intro.jl | 421 ----- 44 files changed, 3745 insertions(+), 15177 deletions(-) delete mode 100755 src/exercises/MCMC_1-intro.jl delete mode 100755 src/exercises/MCMC_2-basics.jl delete mode 100755 src/exercises/MCMC_3-advanced.jl delete mode 100755 src/exercises/MCMC_4-review.jl delete mode 100644 src/exercises/calib_fermenter_monod.jl delete mode 100644 src/exercises/calib_intro.jl delete mode 100644 src/exercises/calib_irrigation.jl delete mode 100644 src/exercises/exercises.md delete mode 100644 src/exercises/model_selection_intro.jl create mode 100644 src/exercises/ode_model_XTRA_tank_T_h_mtk.jl create mode 100644 src/exercises/ode_model_XTRA_temp_reactors_mtk.jl create mode 100644 src/exercises/ode_model_XTRA_water_evap_infil_mtk.jl delete mode 100644 src/exercises/ode_model_anaerobic_fermentation.jl delete mode 100644 src/exercises/ode_model_birth_death.jl delete mode 100644 src/exercises/ode_model_catalyst_intro.jl create mode 100644 src/exercises/ode_model_diver_mtk.jl delete mode 100644 src/exercises/ode_model_fermenter_firstorder.jl delete mode 100644 src/exercises/ode_model_fermenter_monod.jl delete mode 100644 src/exercises/ode_model_infection.jl delete mode 100644 src/exercises/ode_model_irrigation.jl create mode 100644 src/exercises/ode_model_irrigation_mtk.jl create mode 100644 src/exercises/ode_model_mtk_intro.jl delete mode 100644 src/exercises/ode_model_soil_cont_plant_uptake.jl create mode 100644 src/exercises/ode_model_tank_h_mtk.jl create mode 100644 src/exercises/ode_model_tractor_seat_mtk.jl delete mode 100644 src/exercises/ode_model_water_evap_infil.jl delete mode 100644 src/exercises/optim_wastewater_treatment.jl delete mode 100644 src/exercises/probabilistic_selection.jl delete mode 100644 src/exercises/probmod_1-intro.jl delete mode 100644 src/exercises/probmod_2-basics.jl delete mode 100644 src/exercises/probmod_3-advanced.jl delete mode 100644 src/exercises/probmod_4-review.jl delete mode 100644 src/exercises/sde_model_aging.jl delete mode 100644 src/exercises/sde_model_catalyst_intro.jl delete mode 100644 src/exercises/sde_model_fermenter_secondorder.jl delete mode 100755 src/exercises/sens_bitrophic_model.jl delete mode 100755 src/exercises/sens_fermenter_monod.jl delete mode 100755 src/exercises/sens_insuline.jl delete mode 100755 src/exercises/sens_intro.jl delete mode 100644 src/exercises/ssa_model_catalyst_intro.jl delete mode 100644 src/exercises/ssa_model_foxes_rabbits.jl delete mode 100755 src/exercises/uncert_bitrophic_model.jl delete mode 100755 src/exercises/uncert_fermenter_monod.jl delete mode 100755 src/exercises/uncert_intro.jl diff --git a/src/exercises/MCMC_1-intro.jl b/src/exercises/MCMC_1-intro.jl deleted file mode 100755 index 1680f6c7..00000000 --- a/src/exercises/MCMC_1-intro.jl +++ /dev/null @@ -1,450 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "20" -#> title = "4. MCMC intro" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "MCMC intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 06bdc430-b965-11ef-36a4-3d863afbaf6e -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ ce9c8c34-3690-4241-b021-c08868157a55 -using Turing, StatsPlots - -# ╔═╡ eabed73e-19dc-4265-965a-cf762d630fb3 -md"# Inference notebook #1: Intro" - -# ╔═╡ daed8bc0-8a85-45ce-84ac-0d13ff1923f1 -md"## Problem" - -# ╔═╡ 6a79727c-5b5e-43cc-862f-7182c1ea878c -md""" -According to the [molecular clock hypothesis](https://en.wikipedia.org/wiki/Molecular_clock), the amount of mutations in a gene is proportional to how much time has passed, and identical for all species. While this is a bit of an oversimplification, the concept has become an important tool in evolutionary biology to estimate how long ago species have diverged. -""" - -# ╔═╡ 83aa4c69-c027-4ade-ac44-438d784b2b78 -md""" -Consider the below figure of a small slice of the [tree of life](https://en.wikipedia.org/wiki/Tree_of_life_(biology)). Every animal represents a (fossilized) individual living during some point in evolution. -""" - -# ╔═╡ 3ecd3ac7-2621-4ea6-8ff1-69962769d934 -md""" -![Evolution example](https://raw.githubusercontent.com/Kermit-UGent/ModSim/2a369561ce842cf079d7660a36d0d9308739dc69/examples/ProbMod/figures/treeoflife.excalidraw.svg) -""" - -# ╔═╡ 71bbd593-f2e4-40dd-b682-40e65305ebb3 -md""" -We start at time 0 with a common ancestor of fish and terrestrial animals. 30 million years (Ma) later it diverges into ray-finned fish, which will give rise to most modern fish species, and lob-finned fish, which will give rise to e.g. mammals and reptiles. - -The ray-finned fish fossil is also one of the individuals for which we have DNA for its *cytochrome C* gene. The number represents that it has 25 mutations in this gene compared to the gene's sequence from our starting organism, the ancient bony fish fossil. -""" - -# ╔═╡ 138538d4-f6b5-4b8e-af3a-273858cc463c -md""" -Taking into account all fossils, we can see that the number of mutations is roughly proportional with the time that has passed. -""" - -# ╔═╡ f49c6d3f-5870-4473-b142-799fa84dbfb7 -times = [30, 138, 375, 450] - -# ╔═╡ 4ea82c1e-490f-4d88-b60c-5c2118409408 -observed_mutations = [25, 94, 302, 335] - -# ╔═╡ 26128162-e355-4316-8d74-291fbca194a6 -scatter(times, observed_mutations, xlabel = "Time (My)", ylabel = "Number of mutations", legend = false, xlims = (0, 500)) - -# ╔═╡ 5d83f336-372b-4493-baf6-8efffb663ff1 -md""" -Consider now that you find a new fossil of an ancient ancestor of the **seahorses**. -""" - -# ╔═╡ 6666a388-1c45-4f6f-804f-eb3a260eae98 -md"![Sharkmoment](https://raw.githubusercontent.com/Kermit-UGent/ModSim/2a369561ce842cf079d7660a36d0d9308739dc69/examples/ProbMod/figures/treeoflife2.excalidraw.svg)" - -# ╔═╡ b0dbd481-ce36-4364-b995-b4fa8f36f76d -md""" -You don't know how old the fossil is, but you do find that the fossilized DNA contains **156** mutations in the _cytochrome c_ gene. How old should it be estimated as? -""" - -# ╔═╡ 442aa57f-79d8-4b22-93b2-466ac92c2b13 -md""" -!!! questions - - What is *cytochrome C*'s mutation rate `α`? - - What is the seahorse-ancestor fossil's age? -""" - -# ╔═╡ 1e152790-a277-4dba-8785-d6a6120cc34f -md"## Copy-paste example" - -# ╔═╡ 0e044dc4-2fc4-4c41-9efa-e586fd138a69 -md"This section contains the essential code for this practical. A detailed explanation is given in the next section." - -# ╔═╡ 2ef471da-3edb-44cc-b217-87c124d6d0b5 -α = rand(Exponential(10)) - -# ╔═╡ af79b79d-9926-4c20-ae1a-8d38bd7f1a88 -α*times[1] - -# ╔═╡ c1304055-b99a-4570-95cf-72724e64c1db -plot(Exponential(10)) - -# ╔═╡ dc38d9fb-2cc4-40d1-bcd7-956ec75f6b8f -rand(Poisson(α*times[1])) - -# ╔═╡ 85d75659-5294-46e8-ad85-dac357af072c -plot(Poisson(α*times[1])) - -# ╔═╡ e9d2f2ca-48ed-4cd8-bd0a-ff0ef640c8fb -let - @model function mutations(ts) - # Exponential(λ) -> λ is the average rate of mutations - α ~ Exponential(10) - # α ~ Exponential() # also works - # α ~ LogNormal() # also works - # α ~ Uniform(0, 100) # also works - - num_mutations = zeros(length(ts)) - for i in eachindex(ts) - # Poisson(α * ts[i]) -> the probability of having α*ts[i] mutations - num_mutations[i] ~ Poisson(α * ts[i]) - end - - return num_mutations - end - - mutation_model = mutations(times); - conditioned_model = mutation_model | (num_mutations = observed_mutations,) - # mind the `,` after `observed_mutations`! - mutation_chain = sample(conditioned_model, NUTS(), 2000); - α_sp = mutation_chain[:α] - histogram(α_sp, - title = "Posterior distribution of mutation rate α", - normalized=:probability - ) -end -#= -The Exponential Distribution is another important distribution and is typically used to model times between events or arrivals. The distribution has one parameter, λ which is assumed to be the average rate of arrivals or occurrences of an event in a given time interval. - -A Poisson distribution is a discrete probability distribution, meaning that it gives the probability of a discrete (i.e., countable) outcome. For Poisson distributions, the discrete outcome is the number of times an event occurs, represented by k. -=# - -# ╔═╡ 12f017e3-b7b8-408d-a677-52dd2ea900eb -md"## Explanation" - -# ╔═╡ 7900fd13-cc69-41c5-972b-16d5b6d5452a -md"""### Making the model""" - -# ╔═╡ 430927e8-7fb8-494c-b9da-d46f000c142f -md""" -We start again by defining a Turing model. Similar to the models of previous practical, it describes the *forward process*: how do you generate your observations (the amount of mutations) based on your inputs (age of fossil) and parameters (the mutation rate)? - -This may seem unintuitive, as we don't know the distribution of this gene's mutation rate `α`. However, we do have some **prior** knowledge about mutation rates of genes (in general): they don't tend to be much larger than a few bp/My. We can encode this information by giving `α` the prior distribution `Exponential(10)`. -""" - -# ╔═╡ 2034e550-84df-42ee-9f0d-2bf417563dd4 -prior_alpha = Exponential(10) -# Hence, a few mutations (bp = base pair) per My, is here 10 mutations per My on average. - -# ╔═╡ 3221f614-ef9a-4e09-a7da-b649fff0ab61 -plot(prior_alpha, title = "Prior belief of α", legend = false, xlabel = "α", ylabel = "Probability density") - -# ╔═╡ 97257007-fbfa-4065-8788-869801ce3730 -md""" -!!! note - Why use `Exponential(10)` for the prior and not `Exponential(1)`, or some other value? - - Choosing a prior distribution is largely subjective and a big reason why some people are not fond of Bayesian modeling. There is no "one correct prior distribution". - - However, different choices of reasonable priors often give very similar outcomes. Try running this notebook at the end with a different prior for `α`, such as `Exponential(1)` or `Uniform(0, 100)`. When are the results significantly different? -""" - -# ╔═╡ 2a652734-bf47-405b-abb3-00e3d453bf47 -md""" -The rest of the model is pretty straightforward: if the mutation rate `α` is constant, the number of mutations after `t` million years should be about `α*t`. - -Since the accumulation of mutations is a random process, we can't expect the number of mutations to be exactly this number. Rather, we define it to follow a probability distribution centered around this number. A Poisson distribution is chosen as a good fit for count data. -""" - -# ╔═╡ 950c03a9-0056-4760-81f8-dd10d1e55cea -@model function mutations(ts) - α ~ prior_alpha # Exponential(10) - - num_mutations = zeros(length(ts)) - for i in eachindex(ts) - num_mutations[i] ~ Poisson(α * ts[i]) - end - - return num_mutations -end - -# ╔═╡ 31378eb3-51a5-4ad6-a713-7f77c7ceafcc -md""" -The model is instantiated with the correct inputs and can be used to generate samples as per usual. -""" - -# ╔═╡ 48f6b7dc-13aa-4057-8468-97db047773ba -mutation_model = mutations(times); - -# ╔═╡ 8b0bf05f-92a0-4ce7-8042-08c790088688 -mutation_model() # random sample of num_mutations - -# ╔═╡ 3e4998e7-4981-4945-8bf2-ddb5afcb43b1 -chain = sample(mutation_model, Prior(), 2000); - -# ╔═╡ a9d54dfc-5337-412f-86b0-deeb5a0b6928 -histogram(chain[:α], normalized=:probability) - -# ╔═╡ 425b4b6c-76b8-4676-8d0a-fd26711400d6 -md"### Inference" - -# ╔═╡ b8adbdd4-2642-4375-9979-0cb8f52c5bc8 -md""" -The model so far has no extra information outside of our prior knowledge. -We can change this by **conditioning** the model on observed data as follows: -""" - -# ╔═╡ 70f9e94d-a4e6-47d6-8d19-b60f7011d572 -conditioned_model = mutation_model | (num_mutations = observed_mutations,) - -# ╔═╡ 2d0c969d-03a2-4e4c-ace4-e439f81c771b -md""" -!!! danger - Note the `,` at the end of `(num_mutations = observed_mutations,)`. This is important, as without it Julia thinks you simply put parentheses around a variable assignment and you'll get an error! See the below cell for an example. -""" - -# ╔═╡ a35a43e2-e6b0-47ce-80b2-48148336274c -# forgot_comma = mutation_model | (num_mutations = observed_mutations) - # errors because there is no `,` in the parentheses - -# ╔═╡ c5f0dbb3-fba1-41f2-b7d2-740012603555 -md""" -We can verify that for our conditioned model, the values of `num_mutations` has been set as constant: -""" - -# ╔═╡ d03cef36-3e82-4de4-89e7-af9f772edd8d -conditioned_model() # always returns `observed_mutations` - -# ╔═╡ 371a48d5-daea-4d0b-968b-7e3056a65494 -md""" -What we're after is our updated belief on the distribution of `α` given the observed data. We can do this by using the `sample` function on our model. We no longer use `Prior()` as second input, and instead choose one of the following sampling algorithms: -- `MH`: Metropolis-Hastings sampler -- `Gibbs`: Gibbs sampler -- `PG`: Particle Gibbs sampler -- `HMC`: Hamiltonian Monte Carlo sampler -- `NUTS`: No-U-Turn sampler - -You can find more information about them in the corresponding Julia docs. In practice, `NUTS` is often an excellent choice if all variables are continuous and `PG` is a good default choice in all other cases. (`MH` and `Gibbs` also have their uses, but usually it takes more effort to make them work well.) -""" - -# ╔═╡ 0d2c1359-434f-4f3d-8c04-c452c46d7ae8 -mutation_chain = sample(conditioned_model, NUTS(), 2000) -# mutation_chain = sample(conditioned_model, MH(), 2000) - -# ╔═╡ dfc7a6d6-715d-4b3f-ae58-7b930616c41c -mutation_chain - -# ╔═╡ 11199425-5e4f-4fc5-b984-5522321b3378 -# Gives a list of anonymous functions. The plot instruction will -# fill in the time t automatically. -[t -> αᵢ*t for αᵢ in mutation_chain[:α][1:10:end]] - -# ╔═╡ 7441c82c-8aad-4255-92fe-14cd8ee93262 -begin - scatter(times, observed_mutations, xlabel = "Time (My)", - ylabel = "Number of mutations", label = false, xlims = (0, 500), - title = "Predicted trend" - ); - # for α in mutation_chain[:α][1:10:end] - # plot!(x -> α*x, color = :purple, alpha = 0.05, label = false); - # end - # plot!() - plot!([t -> αᵢ*t for αᵢ in mutation_chain[:α][1:10:end]], color = :purple, opacity = 0.05, label = false) -end - -# ╔═╡ 1c00437c-e2f3-44f6-b020-ca213e321239 -md"It's always a good idea to check whether your sampling process has converged. You can do this by plotting the chain. It should look like a fuzzy caterpillar." - -# ╔═╡ 4c79adff-0640-4e69-815d-ab94ebd9c937 -plot(mutation_chain) # looks appropriately fuzzy! - -# ╔═╡ f620d591-7982-4b67-9524-45cfac27436b -md""" -!!! note - For an example of a non-converged chain, try using the `MH()` sampler instead of `NUTS()`. This sampling algorithm takes a lot of fiddling with its parameters (or a larger number of samples) for it to work well. -""" - -# ╔═╡ 251a1b0e-7efc-4ce2-b0ea-48a5c13d2c63 -md""" -The chain plot also shows the resulting **posterior distribution** of `α`. **It is the prior distribution updated with the information contained in the data**. -""" - -# ╔═╡ 6ff29c57-aca3-4ebe-a206-e733e81bcc20 -md""" -Taking the sampled values of the mutation rate from the chain and plotting a histogram will show us the exact same distribution. The one in the chain plot was simply smoothed to look continuous. -""" - -# ╔═╡ b657217a-6ccc-4a41-b852-df4e39a7a10a -sp_alpha = mutation_chain[:α]; - -# ╔═╡ 9cf08616-d599-42a3-82e8-e8c98853c1d8 -histogram(sp_alpha) - -# ╔═╡ e02c42dc-627c-4bc0-8ebb-fdb2b0f15b64 -md"Plotting some sampled mutation rates from this distribution onto our data shows that they fit well:" - -# ╔═╡ 87e70d5a-7a45-4a3e-b6c4-a894cc78621b -begin - scatter(times, observed_mutations, xlabel = "Time (My)", ylabel = "Number of mutations", label = false, xlims = (0, 500)) - plot!([x -> αᵢ*x for αᵢ in sp_alpha[1:10:end]], color = :blue, opacity = 0.1, label = false) -end - -# ╔═╡ 644cff58-68b9-4d4b-8896-617fcacc39c5 -mean(sp_alpha) - -# ╔═╡ 5f2f5a78-ca90-4baf-8bf0-ff1fae88d785 -sqrt(var(sp_alpha)) - -# ╔═╡ d9b6e99d-e483-4633-88eb-5a7824716eb8 -std(sp_alpha) - -# ╔═╡ bfbf811f-7b2f-473b-b132-0c7e965c8b0d -md"To answer our first question, α is ± normally distributed around 0.75 with a standard deviation of 0.025." - -# ╔═╡ d2d83d9a-0ed6-431b-8764-397c1bb019c2 -md"### Seahorses (extra)" - -# ╔═╡ 4e9dc370-6aca-40f7-807b-e85d412ab1a0 -md""" -To answer how old the ancestral seahorse fossil is, we need to update the model a little. -So far the fossil ages were considered to be known exactly and given as input to the model `ts`. Since the fossil's age is unknown, we add a parameter `fossil_age`. - -As prior knowledge we can use the fact that it must have evolved _after_ the ray-finned fish fossil (30 Ma after weird old fish), but _before_ modern seahorses (450 Ma after the bony fish fossil). -""" - -# ╔═╡ fd15afe1-72d7-4663-b2b6-afa0dd219db8 -@model function horsetations(ts) - α ~ Exponential(10) - - num_mutations = zeros(length(ts)) - for i in eachindex(ts) - num_mutations[i] ~ Poisson(α * ts[i]) - end - - fossil_age ~ Uniform(30, 450) - horse_mutations ~ Poisson(α * fossil_age) - - return (α, num_mutations, fossil_age, horse_mutations) -end - -# ╔═╡ 43509549-f926-478b-a4da-995de443b3a7 -md"Then we simply repeat model instantation, conditioning and sampling:" - -# ╔═╡ dbb8ad46-4ac1-443e-b39a-89ddf938ede2 -horse_model = horsetations(times) - -# ╔═╡ 8b380751-0e55-4691-bab0-247fa1b7c510 -horseditioned_model = horse_model | (num_mutations = observed_mutations, horse_mutations = 156,); - -# ╔═╡ 37856d0c-a378-47c0-b29a-967fd12982de -horseditioned_model() - -# ╔═╡ e4eda27b-cff2-4ef9-bbdc-75ce2b19b10b -horse_chain = sample(horseditioned_model, NUTS(), 2000) - -# ╔═╡ da82b71b-fdf5-4fba-8ef3-f2aac67d3494 -md"And we have our posterior distribution of `fossil_age`! It seems like the seahorse ancestor lived about 200-220 million years after the bony fish fossil, or about 240 million years ago." - -# ╔═╡ 7e5a39c7-cb66-4563-8f58-7245f88c9b85 -histogram(horse_chain[:fossil_age]) - -# ╔═╡ 960c95f8-4431-4e56-97fc-783bef811891 -mean(horse_chain[:fossil_age]) - -# ╔═╡ d308bff0-3ed5-4cb1-b81c-f53f7a1967a1 -std(horse_chain[:fossil_age]) - -# ╔═╡ Cell order: -# ╟─eabed73e-19dc-4265-965a-cf762d630fb3 -# ╠═06bdc430-b965-11ef-36a4-3d863afbaf6e -# ╠═ce9c8c34-3690-4241-b021-c08868157a55 -# ╟─daed8bc0-8a85-45ce-84ac-0d13ff1923f1 -# ╟─6a79727c-5b5e-43cc-862f-7182c1ea878c -# ╟─83aa4c69-c027-4ade-ac44-438d784b2b78 -# ╟─3ecd3ac7-2621-4ea6-8ff1-69962769d934 -# ╟─71bbd593-f2e4-40dd-b682-40e65305ebb3 -# ╟─138538d4-f6b5-4b8e-af3a-273858cc463c -# ╠═f49c6d3f-5870-4473-b142-799fa84dbfb7 -# ╠═4ea82c1e-490f-4d88-b60c-5c2118409408 -# ╟─26128162-e355-4316-8d74-291fbca194a6 -# ╟─5d83f336-372b-4493-baf6-8efffb663ff1 -# ╟─6666a388-1c45-4f6f-804f-eb3a260eae98 -# ╟─b0dbd481-ce36-4364-b995-b4fa8f36f76d -# ╟─442aa57f-79d8-4b22-93b2-466ac92c2b13 -# ╟─1e152790-a277-4dba-8785-d6a6120cc34f -# ╟─0e044dc4-2fc4-4c41-9efa-e586fd138a69 -# ╠═2ef471da-3edb-44cc-b217-87c124d6d0b5 -# ╠═af79b79d-9926-4c20-ae1a-8d38bd7f1a88 -# ╠═c1304055-b99a-4570-95cf-72724e64c1db -# ╠═dc38d9fb-2cc4-40d1-bcd7-956ec75f6b8f -# ╠═85d75659-5294-46e8-ad85-dac357af072c -# ╠═e9d2f2ca-48ed-4cd8-bd0a-ff0ef640c8fb -# ╠═dfc7a6d6-715d-4b3f-ae58-7b930616c41c -# ╠═11199425-5e4f-4fc5-b984-5522321b3378 -# ╠═7441c82c-8aad-4255-92fe-14cd8ee93262 -# ╟─12f017e3-b7b8-408d-a677-52dd2ea900eb -# ╟─7900fd13-cc69-41c5-972b-16d5b6d5452a -# ╟─430927e8-7fb8-494c-b9da-d46f000c142f -# ╠═2034e550-84df-42ee-9f0d-2bf417563dd4 -# ╠═3221f614-ef9a-4e09-a7da-b649fff0ab61 -# ╟─97257007-fbfa-4065-8788-869801ce3730 -# ╟─2a652734-bf47-405b-abb3-00e3d453bf47 -# ╠═950c03a9-0056-4760-81f8-dd10d1e55cea -# ╟─31378eb3-51a5-4ad6-a713-7f77c7ceafcc -# ╠═48f6b7dc-13aa-4057-8468-97db047773ba -# ╠═8b0bf05f-92a0-4ce7-8042-08c790088688 -# ╠═3e4998e7-4981-4945-8bf2-ddb5afcb43b1 -# ╠═a9d54dfc-5337-412f-86b0-deeb5a0b6928 -# ╟─425b4b6c-76b8-4676-8d0a-fd26711400d6 -# ╟─b8adbdd4-2642-4375-9979-0cb8f52c5bc8 -# ╠═70f9e94d-a4e6-47d6-8d19-b60f7011d572 -# ╟─2d0c969d-03a2-4e4c-ace4-e439f81c771b -# ╠═a35a43e2-e6b0-47ce-80b2-48148336274c -# ╟─c5f0dbb3-fba1-41f2-b7d2-740012603555 -# ╠═d03cef36-3e82-4de4-89e7-af9f772edd8d -# ╟─371a48d5-daea-4d0b-968b-7e3056a65494 -# ╠═0d2c1359-434f-4f3d-8c04-c452c46d7ae8 -# ╟─1c00437c-e2f3-44f6-b020-ca213e321239 -# ╠═4c79adff-0640-4e69-815d-ab94ebd9c937 -# ╟─f620d591-7982-4b67-9524-45cfac27436b -# ╟─251a1b0e-7efc-4ce2-b0ea-48a5c13d2c63 -# ╟─6ff29c57-aca3-4ebe-a206-e733e81bcc20 -# ╠═b657217a-6ccc-4a41-b852-df4e39a7a10a -# ╠═9cf08616-d599-42a3-82e8-e8c98853c1d8 -# ╟─e02c42dc-627c-4bc0-8ebb-fdb2b0f15b64 -# ╠═87e70d5a-7a45-4a3e-b6c4-a894cc78621b -# ╠═644cff58-68b9-4d4b-8896-617fcacc39c5 -# ╠═5f2f5a78-ca90-4baf-8bf0-ff1fae88d785 -# ╠═d9b6e99d-e483-4633-88eb-5a7824716eb8 -# ╟─bfbf811f-7b2f-473b-b132-0c7e965c8b0d -# ╟─d2d83d9a-0ed6-431b-8764-397c1bb019c2 -# ╟─4e9dc370-6aca-40f7-807b-e85d412ab1a0 -# ╠═fd15afe1-72d7-4663-b2b6-afa0dd219db8 -# ╟─43509549-f926-478b-a4da-995de443b3a7 -# ╠═dbb8ad46-4ac1-443e-b39a-89ddf938ede2 -# ╠═8b380751-0e55-4691-bab0-247fa1b7c510 -# ╠═37856d0c-a378-47c0-b29a-967fd12982de -# ╠═e4eda27b-cff2-4ef9-bbdc-75ce2b19b10b -# ╟─da82b71b-fdf5-4fba-8ef3-f2aac67d3494 -# ╠═7e5a39c7-cb66-4563-8f58-7245f88c9b85 -# ╠═960c95f8-4431-4e56-97fc-783bef811891 -# ╠═d308bff0-3ed5-4cb1-b81c-f53f7a1967a1 diff --git a/src/exercises/MCMC_2-basics.jl b/src/exercises/MCMC_2-basics.jl deleted file mode 100755 index 7bd2ef68..00000000 --- a/src/exercises/MCMC_2-basics.jl +++ /dev/null @@ -1,888 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "21" -#> title = "4. MCMC basics" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "MCMC basics" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 94c6f31d-1a43-4221-b60c-1fa0ef8738b8 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 45bc5b66-c81b-4afb-8a7e-51aff9609c62 -using Turing, StatsPlots - -# ╔═╡ 41dc8060-cf5e-11ef-26f9-892577e77af0 -md"# Inference notebook #2: Basics" - -# ╔═╡ 299ba93b-0fc0-4bb3-9a2c-a571ce571f1b -md"## 1: Mole burrow" - -# ╔═╡ 957386c5-775c-47f7-9a38-e1630e548689 -md""" -Consider a mole's underground tunnel network of length `X` (in m). Now and then the mole makes a new molehill somewhere randomly above its tunnel, the locations of which we denote `Y`. - -We can formulate this as `X ~ Exponential(100)` and `Y ~ Uniform(0, X)`. - -!!! questions - 1. Plot the prior of `X`. Is it diffuse or informative? - 1. Estimate `E[Y]`. - 1. Estimate `E[X|Y = 3]` and compare it with the prior expected value `E[X]`. - 1. Plot the histogram of `X` given `Y = 3.0`. - 1. Plot the histogram of `X` again, but now given the following values for `Y`: `[3.0, 1.5, 0.9, 5.7]`. -""" - -# ╔═╡ ca8057cc-6d70-4a88-88aa-c494af9659ef -md""" -**Questions:** -- How should I understand this? -- If the mole digs a tunnels that is X long, then at position Y there is one hole? -- Or, is there a hole at every position Y for an X meters long tunnel? -""" - -# ╔═╡ 47aa2304-d312-40e9-b9c6-7c79a7d64de4 -md"### 1" - -# ╔═╡ 78c9ce62-e375-48ac-8083-55ee085c61de -plot(Exponential(100)) # The prior only incorporates the knowledge that a mole's tunnel is probably less long than a few km - this is a diffuse to weakly informative prior - -# ╔═╡ 4ab8aaba-010c-4844-9cf6-627a746c2492 -mijn_X = rand(Exponential(100)) - -# ╔═╡ 82289137-9c20-4d7a-97b2-56f417831409 -mijn_Y = rand(Uniform(0, mijn_X)) - -# ╔═╡ 78eb7779-f182-4419-b5d8-79a2f5c5d6da -md"### 2" - -# ╔═╡ 3decb2ec-210a-4b2f-842d-6fd40dd3f77b -@model function mole() - X ~ Exponential(100) - Y ~ Uniform(0, X) - return Y -end - -# ╔═╡ 76dd814d-0d9b-4f7e-aff8-990da57d052b -molemodel = mole() - -# ╔═╡ 5c989060-40cd-4b27-b96f-3f98a85f5122 -molemodel() # returns Y - -# ╔═╡ 4e8b9000-cb61-4f01-9ba9-17276ad0335e -E_Y = mean([molemodel() for i in 1:2000]) - -# ╔═╡ 8777b133-7d7c-4a85-b89c-2f00093e9984 -md"### 3" - -# ╔═╡ 9695ee7e-359b-489c-962b-1bf84b052371 -cond_mole = molemodel | (Y = 3.0,); - -# ╔═╡ 10deca95-9f88-45ff-a22f-30280776acef -md""" -**Questions:** -- Why doesn't this work for much higher values of Y? E.g. for Y = 8 or 10? -- Because if you sample `X = rand(Exponential(100))` and `Y = rand(Uniform(0, X))`, you usually get much larger numbers for Y than 3. -""" - -# ╔═╡ 014538da-b5ef-41c8-b799-2c000b4c9134 -molechain = sample(cond_mole, NUTS(), 2000) - -# ╔═╡ b02dc714-e2bb-4ae2-acf9-c37a4389f953 -plot(molechain) - -# ╔═╡ fd540765-95e5-4071-81f7-e689b06cad0c -E_Xcond3 = mean(molechain[:X]) - -# ╔═╡ 5521933a-a42e-4a67-94b9-84eab52ddf07 -E_X = mean(Exponential(100)) - -# ╔═╡ ca3e730c-a940-4c76-93eb-70ae4aa0e008 -md"### 4" - -# ╔═╡ 159caa6a-2ebf-44bd-87a5-b4ab8b085354 -histogram(molechain[:X], normalized=:probability) - -# ╔═╡ eda00c08-de49-4d2d-acc4-ba6e21ff0b11 -md"### 5" - -# ╔═╡ b2b16c34-e12a-4bea-8098-313d01913bbf -@model function mole2() - X ~ Exponential(100) - Ys = zeros(4) # now we have 4 holes Ys (Ysample) - for i in eachindex(Ys) - Ys[i] ~ Uniform(0, X) - end -end - -# ╔═╡ 1a78d96a-fe57-42a4-9785-003266117ddb -Y_obs = [3.0, 1.5, 0.9, 5.7] - -# ╔═╡ 2f5c14e9-709a-40ec-a6cb-ddd870cc1a60 -mole_cond2 = mole2() | (Ys = Y_obs,) - -# ╔═╡ 7f90e18d-9af8-42fa-984b-aaa7c8c458b5 -molechain2 = sample(mole_cond2, NUTS(), 2000) - -# ╔═╡ caf0db69-a83f-433a-b848-7d7d8c2fa25e -plot(molechain2) - -# ╔═╡ 3c1a508b-6d2c-4507-ab9f-752ee93709c9 -histogram(molechain2[:X], normalized=:probability) - -# ╔═╡ 2d35375a-cd92-48d1-8779-761641e7b0af -mean(molechain2[:X]) - -# ╔═╡ 951d0913-1a52-4d5b-b5bb-168487e50ab2 -md"## 2: Potatoes" - -# ╔═╡ 49035a16-c419-4531-b157-a5ab357b44fe -md""" -Consider a number of potatoes `N` each with an average weight `W`. You weigh them together on an old balance to get an estimate of their total weight `T`. - -We can formulate this as `N ~ Poisson(10)`, `W ~ Uniform(150, 250)` and `T ~ Normal(N*W, 50)`. - -!!! questions - 1. Plot a histogram of `N` given `T = 1200`. - 1. Estimate `P(N > 6, W > 175 | T = 1200)`. - 1. Estimate `P(N = 5 | T = 1200, W = 220)`. -""" - -# ╔═╡ d5f544a4-ba51-4103-82ce-259ba20af11a -let - N = rand(Poisson(10)); - W = rand(Uniform(150, 250)); # in grams - T = rand(Normal(N*W, 50)) # in grams -end - -# ╔═╡ 5f8322ca-0ded-4750-8ad6-e66e8280daca -md"### 1" - -# ╔═╡ ec479be9-0d8a-4c8d-97c9-6f64d861924c -@model function potatoes() - N ~ Poisson(10) - W ~ Uniform(150, 250) - T ~ Normal(N*W, 50) -end - -# ╔═╡ 4e92d825-481f-430a-94a4-fbdf31b679eb -potato_model = potatoes() - -# ╔═╡ 0ced04f8-322a-42f6-a1b7-1b30faf9024b -potato_cond = potato_model | (T = 1200,) - -# ╔═╡ e48a2d96-6d7b-4861-b315-0ae580479eda -potato_chain = sample(potato_cond, PG(10), 2000) - -# ╔═╡ ceb2b8fd-aa47-443b-8e5c-74a48eda1174 -plot(potato_chain) - -# ╔═╡ 8c1eb826-6023-4852-aa45-789f6d4b7051 -histogram(potato_chain[:N], normalized=:probability) - -# ╔═╡ 205efb15-e28a-418a-a1af-892afdf3c188 -md""" -The above gives of the individual probabilities of having T=1200 with 5, 6, 7 and 8 potatoes. -""" - -# ╔═╡ ad571d35-639c-4239-9e62-42952ddb48d0 -# Probability of having T=1200 with N=7. -mean(potato_chain[:N] .== 7) - -# ╔═╡ 0fddec58-67d9-4dec-a72d-445675fa46a6 -md"### 2" - -# ╔═╡ 9dd6fa35-37d5-4ab4-ac2c-f4eefdabadd2 -p_potato1 = mean(potato_chain[:N] .> 6 .&& potato_chain[:W] .> 175) - -# ╔═╡ 57ec05ca-bf37-4012-be9a-d83c1d9f0f8a -mean(potato_chain[:N] .<= 6 .|| potato_chain[:W] .<= 175) - -# ╔═╡ 5bde987a-3f5c-4ff7-96a0-9e174f73fdfb -md"### 3" - -# ╔═╡ 6e3d4f8f-1766-49a6-9e4a-610671d8aa63 -potato_cond2 = potato_model | (T = 1200, W = 220,) -# potato_cond2 = potato_model | (T = 1100, W = 220,) - -# ╔═╡ 4cd99dce-12df-4e3a-aa91-ff00c61544a8 -pota2_chain = sample(potato_cond2, PG(10), 2000) - -# ╔═╡ 48ea9893-16f7-457e-93b0-9f168a2f72f4 -plot(pota2_chain) - -# ╔═╡ c3d36685-e1dd-4734-bee9-f7aeebed3473 -p_potato2 = mean(pota2_chain[:N] .== 5) - -# ╔═╡ bdc40079-de7b-489f-9d9b-6e1e2130109f -mean(pota2_chain[:N] .== 4) - -# ╔═╡ 22d009f0-390f-412d-9af8-33d169ac7371 -mean(pota2_chain[:N] .== 6) - -# ╔═╡ ed7c5547-bd82-4ca7-bf51-dd1c201d88af -md"## 3: Lights out" - -# ╔═╡ a3c26839-2acc-49d8-98c4-3a7142dd6512 -md""" -You use 4 of the same LED light in your room. Let `μ` be the **average** lifespan of your LED lights (in khr or 1000 hours) and `L`ᵢ the lifespan of the `i`-th LED light. - -Assume that `μ ~ LogNormal(log(40), 0.5)`. -""" - -# ╔═╡ dac55632-6e17-479e-8090-5cf8eaa67dad -md""" -!!! questions - 1. What is `E[μ]` (given no information about `Lᵢ`)? - 1. What is a sensible distribution for `Lᵢ`? (requires no code) - 1. What is `E[μ | L = [16, 20, 23, 41]]`? - 1. 🌟🌟🌟 (EXTRA DIFFICULT BONUS QUESTION): After 30 khr, two lights have died: one at 16 khr and one at 20 khr. The two other lights are still working. What is the expected value of `μ` given this information? -""" - -# ╔═╡ 7df97000-5cf0-4a29-b3c3-f867403f4318 -md"### 1" - -# ╔═╡ 0fe475dd-411e-474e-9524-ef9fcceed7af -lights_prior = LogNormal(log(40), 0.5) - -# ╔═╡ 382db9a6-edcb-497b-9535-b576cd6badb0 -plot(lights_prior) # not asked but a visualisation can always be useful - -# ╔═╡ fce3e814-97a7-4e12-a7cc-b5b086dc8340 -E_mu = mean(lights_prior) - -# ╔═╡ a7811e57-2820-4662-8c20-a4d2d7efd642 -rand(lights_prior) - -# ╔═╡ 308cb1f5-e26b-43b2-be4d-9eabd68b3670 -md"### 2" - -# ╔═╡ 490cbc67-3e0c-4c79-a83d-52cf493854a2 -md""" -The exponential distribution is often used to model the waiting time for an event. This makes it a natural fit for a lamp's lifespan, which is the waiting time until it breaks. We know it needs to have a mean value of μ, so `Exponential(μ)` is a good choice. - -One could also argue for a LogNormal distribution with mean μ or a Normal distribution with mean μ restricted to only the positive values. Both would need a large variance to reflect the lack of additional information outside of the mean lifespan. -""" - -# ╔═╡ dbadf227-1399-45f7-ad48-df9d59e18343 -# Average waiting time before it breaks is 45 khr. The longer we wait, the -# higher the chance that it breaks! -plot(Exponential(45)) - -# ╔═╡ 5d812594-733e-4b18-88dc-c28867cf0933 -μ = 27 - -# ╔═╡ 0373b6a5-bb2c-47b9-a137-5a484b0c7bf2 -plot(Normal(μ, sqrt(μ))) - -# ╔═╡ ec06259f-67b5-4920-8635-35c52c080751 -begin - plot([LogNormal(log(μ), 1), Exponential(45)]; xlim=[0,400]) - vline!([mean(LogNormal(log(μ), 1)), mean(Exponential(45))]) -end - -# ╔═╡ a628bd3f-5d07-435e-80cc-b72662e13e7b -plot(cdf(Exponential(45), 0:400)) - -# ╔═╡ e2fd7500-277a-4d52-982c-5aea8421d756 -cdf(Exponential(45), 100) # probability that it breaks before 100 khr - -# ╔═╡ 96ee6380-719e-47c1-a429-4fdd1931c9ce -1 - cdf(Exponential(45), 100) # probability that it is still working after 100 khr - -# ╔═╡ cd37e5fc-ae9a-429d-844d-4b450b187b5e -md"### 3" - -# ╔═╡ 126d3954-c77d-4c98-abe0-fd87d14e6265 -@model function lights() - μ ~ lights_prior - lifespans = zeros(4) - for i in 1:length(lifespans) - lifespans[i] ~ LogNormal(log(μ), 1) # Normal(μ, sqrt(μ)) #Exponential(μ) - end -end - -# ╔═╡ 15fe25d9-5314-4056-8021-cf259ba27c94 -lightmodel = lights() | (lifespans = [16, 20, 23, 41],) - -# ╔═╡ 2a142576-aec2-4311-9c0e-cb68665d59f6 -lightschain = sample(lightmodel, NUTS(), 2000) - -# ╔═╡ 0130c903-e3dd-415e-aa17-1b705f9e4ccc -plot(lightschain) - -# ╔═╡ 11676f8a-d4fe-4999-9ea2-486fca461f65 -E_mu_cond = mean(lightschain[:μ]) - -# ╔═╡ f6bc2fad-24c7-48a7-9328-34b616fa106f -histogram(lightschain[:μ], normalize=true) - -# ╔═╡ 347a4533-244f-4e93-b125-cdd56a5a08ad -begin - plot([LogNormal(log(μ), 1), Exponential(45)]; xlim=[0,400]) - vline!([mean(LogNormal(log(μ), 1)), mean(Exponential(45))]) -end - -# ╔═╡ 9a168680-9d96-466f-ba75-122d6a391501 -md"### 4 🌟🌟🌟" - -# ╔═╡ 1f35d962-a249-4be7-9a96-17eb83fca7d8 -md""" -!!! hint - You can model the number of lights that still work as a `Binomial` distribution, the success rate of which depends on `μ`. -""" - -# ╔═╡ 3ad29b60-375b-400b-b1e5-8918da6497ff -1-cdf(Exponential(40), 30) - -# ╔═╡ 0cce9dee-43ac-42d3-9113-8b1503c7a73c -rand(Binomial(2 + 2, 0.47)) - -# ╔═╡ 8d501c17-58d2-43d3-92c2-0d21bc9113e3 -md""" -After 30 khr, two lights have died: one at 16 khr and one at 20 khr. The two other lights are still working. What is the expected value of `μ` given this information? -""" - -# ╔═╡ c626d3bf-4abe-4bdc-991a-363bee27b25b -md""" -Here you need to provide two arguments to the model function: -- How many lights still working? `n` -- At what time they are still working? `time_observed` - -""" - -# ╔═╡ 8fc58fa4-b005-4f32-9eae-a8143582a1ae -@model function lights_censored(n, time_observed) - μ ~ lights_prior - - lifespans = zeros(2) - for i in 1:length(lifespans) - lifespans[i] ~ Exponential(μ) - end - - # Given the observation time, what is the probability that - # a single light still works: - p_stillworking = 1 - cdf(Exponential(μ), time_observed) - # cdf(Exponential(μ), time_observed) is the probability that it broke - # in [0, time_observed] - # Number of lights still working with the above probability: - n ~ Binomial(n + length(lifespans), p_stillworking) - - return (μ, lifespans, p_stillworking, n) -end - -# ╔═╡ fe2958a7-e9dd-4eca-979d-a80df12f8735 -lightmodel_cens = lights_censored(2, 30) | (lifespans = [16, 20],) - -# ╔═╡ 93f6b680-aece-45d7-804e-a27073130d94 -lightmodel_cens() - -# ╔═╡ 8abafb6a-dc83-422c-82d1-a721a0e1eca0 -lightschain_cens = sample(lightmodel_cens, NUTS(), 2000) - -# ╔═╡ 5ba2886c-b2e1-49f4-90c6-549acc808f77 -plot(lightschain_cens) - -# ╔═╡ ed022247-5959-481f-a81e-41e5ee5a1448 -E_mu_cond🌟 = mean(lightschain_cens[:μ]) - -# ╔═╡ c57046cd-5f53-43a2-9de4-9bfad27dcab9 -@model function lights_censored2(time_observed) - μ ~ lights_prior - - lifespans = zeros(2) - for i in 1:length(lifespans) - lifespans[i] ~ Exponential(μ) - end - - p_stillworking = 1 - cdf(Exponential(μ), time_observed) - n ~ Binomial(4, p_stillworking) - - return (μ, lifespans, p_stillworking, n) -end - -# ╔═╡ a2ebe90c-1006-4fc3-b8bd-bdf9c9e9fed5 -plot(cdf(Exponential(10), 0:100)) - -# ╔═╡ 5b505f97-e776-4cc3-99f8-721dc241ae2a -lightmodel_cens2 = lights_censored2(30) | (lifespans = [16, 20], n = 2,) - -# ╔═╡ deaf3485-9bc1-48da-809c-61327cce5ad9 -lightmodel_cens2() - -# ╔═╡ 56a91508-29fa-4e5f-8dcc-0bb9d67db1fe -lightschain_cens2 = sample(lightmodel_cens2, NUTS(), 2000) - -# ╔═╡ 878e5eaf-2f1c-4c09-a9dd-47821e9295e5 -plot(lightschain_cens2) - -# ╔═╡ 7366d15b-9c03-4200-8720-6d879bb65476 -mean(lightschain_cens2[:μ]) - -# ╔═╡ 9dc0456b-7fd2-4120-8f9e-3de1984ff516 -md"## 4: Fish" - -# ╔═╡ 225cd579-1e0d-4680-8d3f-5a737a656eb8 -md""" -There are two populations of fish living in the same pond. Let `fs1` be the fraction of fish belonging to species 1, `L1` the length of a fish of species 1 and `L2` the length of a fish of species 2. - -Assume: -- You have no prior information about `fs1` except that it logically needs to be in `[0, 1]`. -- `L1 ~ Normal(90, 15)`. -- `L2 ~ Normal(60, 10)`. -""" - -# ╔═╡ 3ffac5ca-3635-4aa1-bab7-7c28e7a801cb -md""" -!!! questions - 1. If `fs1 = 0.3`, what is the prior distribution of the lengths of **all** fish in the pond? Make a plot. - 1. Estimate `fs1` if you observe fish of the following lengths: - `[94.0, 88.7, 89.6, 69.8, 52.8, 84.0, 89.3, 66.4, 95.1, 81.6]`. - 1. 🌟(BONUS QUESTION): What is the chance fish 4 belongs to species 1? -""" - -# ╔═╡ b477b212-83a2-42f0-a616-52516e152d48 -md"### 1" - -# ╔═╡ 23056f1e-128e-463e-a80f-56299397022e -md""" -!!! hint - The distribution of fish lengths can be modelled as a `MixtureModel`. -""" - -# ╔═╡ 3cd0c888-9f11-47f1-a293-b96aa80ea3b0 -lengthdist = MixtureModel([Normal(90, 15), Normal(60, 10)], [0.3, 0.7]) - -# ╔═╡ afcf1260-db4d-4ff1-ac07-978161874e6c -histogram(rand(lengthdist, 10000)) - -# ╔═╡ 8e030a06-5104-4c5b-b1f2-f86464e66502 -md"### 2" - -# ╔═╡ 6ebf3a16-0e6a-491f-8280-b4327ed52cf0 -len_obs = [94.0, 88.7, 89.6, 69.8, 52.8, 84.0, 89.3, 66.4, 95.1, 81.6] - -# ╔═╡ b15de91a-fc48-4cdc-a35f-6453a9a59982 -@model function fishmixture() - fs1 ~ Uniform(0, 1) # fraction of species 1 - # fish length distribution - fishlendist = MixtureModel([Normal(90, 15), Normal(60, 10)], [fs1, 1-fs1]) - - fishlens = zeros(10) # fish lengths - for i in eachindex(fishlens) - fishlens[i] ~ fishlendist - end -end - -# ╔═╡ 4f6bfecd-43a3-44c1-a20c-224c01b8469d -fishmodel = fishmixture() | (fishlens = len_obs,) - -# ╔═╡ 41c1e279-4d0b-4447-a9ab-015863df8e91 -fishchain = sample(fishmodel, NUTS(), 2000) - -# ╔═╡ a786c349-34e3-4dda-b139-808259495753 -plot(fishchain) - -# ╔═╡ 4f5c0761-12ef-4997-9e40-050c30ec84ab -fs1_est = mean(fishchain[:fs1]) - -# ╔═╡ 952a941a-8703-45a3-aac1-a290a181e8c5 -md"### 3🌟" - -# ╔═╡ 8629d049-b9fd-4e9e-9b55-401a3069e956 -@model function fishmixture🌟() - fs1 ~ Uniform(0, 1) # fraction of species 1 - # or probability of belonging to species 1 - - fishlens = zeros(10) # samples with fish lengths - isspecies1 = zeros(10) # samples with 1's meaning belonging to species 1 - # samples with 0's meaning belonging to species 2 - for i in eachindex(fishlens) - isspecies1[i] ~ Bernoulli(fs1) # samples belonging to species 1 or not - if isspecies1[i] == 1.0 # if belongs to species 1 - fishlens[i] ~ Normal(90, 15) # sample from distribution of species 1 - else - fishlens[i] ~ Normal(60, 10) # sample from distribution of species 1 - end - end -end - -# ╔═╡ 75d4d482-2f30-4c21-be04-0b821635346f -fishmodel🌟 = fishmixture🌟() | (fishlens = len_obs,) - -# ╔═╡ bdb0d902-3056-43fb-abb0-15f2494fcf9d -fishchain🌟 = sample(fishmodel🌟, PG(20), 2000) - -# ╔═╡ 7372c616-05a8-428d-ab04-a7be9f65653d -plot(fishchain🌟) - -# ╔═╡ a60315f2-92f5-4f3b-b154-c3428816bfb5 -# fish species 1 -> large fish -# fish species 2 -> small fish -# [94.0, 88.7, 89.6, 69.8, 52.8, 84.0, 89.3, 66.4, 95.1, 81.6] -# fish: 1 2 3 4 5 6 7 8 9 10 -# species: 1 1 1 1or2 2 1 1 1or2 1 1 - -# ╔═╡ 05c700e8-f24a-4c82-9d15-cb3450de9e2a -# We expect a very high chance here because 94.0 is a large fish -p_fish1_is_species1 = mean(fishchain🌟["isspecies1[1]"]) - -# ╔═╡ 40cc67c1-8627-4f1f-b135-a9770f916b53 -# We expect medium chance here because 69.8 is between small and large -p_fish4_is_species1 = mean(fishchain🌟["isspecies1[4]"]) - -# ╔═╡ 8b9d7708-4a67-457a-9fe1-de12fb2a2de9 -# We expect a very small chance here because 52.8 is a small fish -p_fish5_is_species1 = mean(fishchain🌟["isspecies1[5]"]) - -# ╔═╡ 5d8929fd-5aa4-4186-9fd0-e0a64ac06cbe -# We expect medium chance here because 66.4 is between small and large -p_fish8_is_species1 = mean(fishchain🌟["isspecies1[8]"]) - -# ╔═╡ 8e09d7f1-f8db-42a1-beb0-ed3afb043c19 -mean(fishchain🌟[:fs1]) # should be the same as before - -# ╔═╡ 30088664-5157-4d99-8584-7a42d0acdfb8 -md"## 5: Circleference" - -# ╔═╡ 7dd2c189-79c0-4d29-9e17-9c24a78b5791 -md""" -Given three (noisy) points $P_1=(x_1,y_1)$, $P_1=(x_2,y_2)$ and $P_3=(x_3,y_3)$, you want to infer the corresponding circle. - -You can assume that the circle center can appear anywhere in the $[-20, 20]\times [-20, 20]$ square and the radius is between 0 and 50. Points are sampled randomly on the circle and have a slight amount of Gaussian noise ($\sigma=0.25$ works well). -""" - -# ╔═╡ dcbf405c-786c-4226-b35c-dc718452bb61 -md""" -!!! questions - 1. Write a small probabilistic program that can infer the center and radius of the circle. - 1. What does the inferred circle look like if you condition on only one or two of the circle points? -""" - -# ╔═╡ 791ff4ed-c9d1-48e2-9dd0-4bf3979c6167 -x1, y1 = 18.0, 2.1 - -# ╔═╡ cb189957-f9d4-480b-a492-92cfc2a8c2aa -x2, y2 = -7.3, 8.1 - -# ╔═╡ 81eca3b3-12d9-43d7-af14-e9aeb73f2471 -x3, y3 = -13.0, -23.0 - -# ╔═╡ 28d28034-e999-4e34-b6b2-63c762094c59 -begin - - function plotcircle!(p, R, xC, yC; dθ=0.01) - θ = 0:dθ:2pi+0.1 - plot!(p, xC .+ R .* cos.(θ), yC .+ R .* sin.(θ), label="", alpha=0.5, color=:blue) - return p - end - - function plotsample(R=missing, xC=missing, yC=missing; kwargs...) - p = plot(xlab="x", ylab="y", aspect_ratio=:equal; - xlims=[-40, 40], ylims=[-40, 40], kwargs...) - - scatter!([x1], [y1], label="P1") - scatter!([x2], [y2], label="P2") - scatter!([x3], [y3], label="P3") - ismissing(R) || plotcircle!(p, R, xC, yC; dθ=0.1) - return p - end - - function plotsample!(p, R=missing, xC=missing, yC=missing) - scatter!([x1], [y1], label=false) - scatter!([x2], [y2], label=false) - scatter!([x3], [y3], label=false) - ismissing(R) || plotcircle!(p, R, xC, yC; dθ=0.1) - end - -end - -# ╔═╡ 4e96908a-4fc9-429d-bf37-7a569194a038 -scatter([x1, x2, x3], [y1, y2, y3], aspect_ratio=:equal, xlim=[-40, 40], ylim=[-40, 40]) - -# ╔═╡ 7eedb74d-eee1-4cf0-b2bf-5febf474edd2 -md"### 1" - -# ╔═╡ abbb225e-fdcd-435e-9f81-c1e6fa1c8f5d -# Flat() -> p134 in syllabus -rand(Flat()) - -# ╔═╡ f1086281-ba2d-461d-bcfe-82e3d09aaae0 -rand(2*π*Flat()) - -# ╔═╡ c4a2335d-5926-4da4-a46f-41da7b67fa01 -rand(Uniform(0, 2*π)) - -# ╔═╡ 84d27c98-9513-4ae3-8101-621c083a1b01 -@model function circle(σ=0.25) - # generate a circle center - xC ~ Uniform(-20, 20) - yC ~ Uniform(-20, 20) - - # generate a radius - R ~ Uniform(0, 50) - - # three random points in polar coordinates - θ1 ~ 2*π*Flat() - # `Uniform(0, 2*pi)` is also possible but can get the sampler stuck - # at 0 or 2π! - θ2 ~ 2*π*Flat() - θ3 ~ 2*π*Flat() - - # P1 - x1 ~ Normal(xC + R * cos(θ1), σ) - y1 ~ Normal(yC + R * sin(θ1), σ) - # P2 - x2 ~ Normal(xC + R * cos(θ2), σ) - y2 ~ Normal(yC + R * sin(θ2), σ) - # P3 - x3 ~ Normal(xC + R * cos(θ3), σ) - y3 ~ Normal(yC + R * sin(θ3), σ) -end - -# ╔═╡ 543c40d5-e8a7-492d-a0b8-e7e73e5953e2 -circlemodel = circle() | (x1=x1, y1=y1, x2=x2, y2=y2, x3=x3, y3=y3); - -# ╔═╡ 22173937-25a1-4ec0-877d-f9669653e43e -circlechain = sample(circlemodel, NUTS(), 2000) - -# ╔═╡ 0b59a6bc-a886-49fd-a3f5-9b20e60882b9 -plot(circlechain) - -# ╔═╡ 121c291f-ec34-47cb-a9fc-01309b0f42c5 -# Circle center at: -(mean(circlechain[:xC]), mean(circlechain[:yC])) - -# ╔═╡ 7efa1868-08b9-49fb-b799-76f1c99d1a0e -mean.([circlechain[:xC], circlechain[:yC]]) - -# ╔═╡ 374d7cea-2dc6-4cb3-b929-ddaba1f4c6fb -# Radius: -mean(circlechain[:R]) - -# ╔═╡ 95d223a0-748b-4656-8777-e151c0dc8e93 -mean(circlechain[:θ1])*180.0/π - -# ╔═╡ 2668e03a-91eb-420f-af3f-b74d83126f76 -mean(circlechain[:θ2])*180.0/π - -# ╔═╡ 87be71c4-2bc3-432b-a977-f3d46424a68f -mean(circlechain[:θ3])*180.0/π - -# ╔═╡ 0d04b0e8-3d1a-4281-b175-570148569ef2 -begin - p = plot( - xlab="x", ylab="y", aspect_ratio=:equal, xlims=[-40, 40], ylims=[-40, 40], - title="Samples of P(circle|P1,P2,P3)" - ) - for i in 1:100 - plotsample!(p, circlechain[:R][i], circlechain[:xC][i], circlechain[:yC][i]) - end - p -end - -# ╔═╡ 0a140d2a-a24b-48df-9af8-7fa5d586a26f -md"### 2" - -# ╔═╡ b7649523-fd40-4fe3-8d86-fc2cb2c8c488 -circle1 = circle() | (x1=x1, y1=y1) -# This can be any circle through (x1, y1) with centrer -# in [-20, 20]x[-20, 20] and radius between 0 and 50. - -# ╔═╡ b7ba8f91-f440-4166-af9e-11fcb5f1755f -circle2 = circle1 | (x2=x2, y2=y2) -# This can be any circle through (x1, y1) and (x2, y2) with centrer -# in [-20, 20]x[-20, 20] and radius between 0 and 50. - -# ╔═╡ 3b5f3623-3d00-4ba4-9182-5bddacc56567 -chain1 = sample(circle1, NUTS(), 100); - -# ╔═╡ c11452bc-f404-4e51-8f68-292f5b538c88 -chain2 = sample(circle2, NUTS(), 100); - -# ╔═╡ c1174a5c-a6a1-46a0-96be-6997e3201dfc -begin - p1 = plot( - xlab="x", ylab="y", aspect_ratio=:equal, - xlims=[-40, 40], ylims=[-40, 40], title="Samples of P(circle|P1)" - ) - for i in 1:100 - plotsample!(p1, chain1[:R][i], chain1[:xC][i], chain1[:yC][i]) - end - p1 -end - -# ╔═╡ 793fc102-fc64-4041-a04e-e0b1b0741437 -begin - p2 = plot( - xlab="x", ylab="y", aspect_ratio=:equal, - xlims=[-40, 40], ylims=[-40, 40], title="Samples of P(circle|P1,P2)" - ) - for i in 1:100 - plotsample!(p2, chain2[:R][i], chain2[:xC][i], chain2[:yC][i]) - end - p2 -end - -# ╔═╡ Cell order: -# ╟─41dc8060-cf5e-11ef-26f9-892577e77af0 -# ╠═94c6f31d-1a43-4221-b60c-1fa0ef8738b8 -# ╠═45bc5b66-c81b-4afb-8a7e-51aff9609c62 -# ╟─299ba93b-0fc0-4bb3-9a2c-a571ce571f1b -# ╟─957386c5-775c-47f7-9a38-e1630e548689 -# ╟─ca8057cc-6d70-4a88-88aa-c494af9659ef -# ╟─47aa2304-d312-40e9-b9c6-7c79a7d64de4 -# ╠═78c9ce62-e375-48ac-8083-55ee085c61de -# ╠═4ab8aaba-010c-4844-9cf6-627a746c2492 -# ╠═82289137-9c20-4d7a-97b2-56f417831409 -# ╟─78eb7779-f182-4419-b5d8-79a2f5c5d6da -# ╠═3decb2ec-210a-4b2f-842d-6fd40dd3f77b -# ╠═76dd814d-0d9b-4f7e-aff8-990da57d052b -# ╠═5c989060-40cd-4b27-b96f-3f98a85f5122 -# ╠═4e8b9000-cb61-4f01-9ba9-17276ad0335e -# ╟─8777b133-7d7c-4a85-b89c-2f00093e9984 -# ╠═9695ee7e-359b-489c-962b-1bf84b052371 -# ╟─10deca95-9f88-45ff-a22f-30280776acef -# ╠═014538da-b5ef-41c8-b799-2c000b4c9134 -# ╠═b02dc714-e2bb-4ae2-acf9-c37a4389f953 -# ╠═fd540765-95e5-4071-81f7-e689b06cad0c -# ╠═5521933a-a42e-4a67-94b9-84eab52ddf07 -# ╟─ca3e730c-a940-4c76-93eb-70ae4aa0e008 -# ╠═159caa6a-2ebf-44bd-87a5-b4ab8b085354 -# ╟─eda00c08-de49-4d2d-acc4-ba6e21ff0b11 -# ╠═b2b16c34-e12a-4bea-8098-313d01913bbf -# ╠═1a78d96a-fe57-42a4-9785-003266117ddb -# ╠═2f5c14e9-709a-40ec-a6cb-ddd870cc1a60 -# ╠═7f90e18d-9af8-42fa-984b-aaa7c8c458b5 -# ╠═caf0db69-a83f-433a-b848-7d7d8c2fa25e -# ╠═3c1a508b-6d2c-4507-ab9f-752ee93709c9 -# ╠═2d35375a-cd92-48d1-8779-761641e7b0af -# ╟─951d0913-1a52-4d5b-b5bb-168487e50ab2 -# ╟─49035a16-c419-4531-b157-a5ab357b44fe -# ╠═d5f544a4-ba51-4103-82ce-259ba20af11a -# ╟─5f8322ca-0ded-4750-8ad6-e66e8280daca -# ╠═ec479be9-0d8a-4c8d-97c9-6f64d861924c -# ╠═4e92d825-481f-430a-94a4-fbdf31b679eb -# ╠═0ced04f8-322a-42f6-a1b7-1b30faf9024b -# ╠═e48a2d96-6d7b-4861-b315-0ae580479eda -# ╠═ceb2b8fd-aa47-443b-8e5c-74a48eda1174 -# ╠═8c1eb826-6023-4852-aa45-789f6d4b7051 -# ╟─205efb15-e28a-418a-a1af-892afdf3c188 -# ╠═ad571d35-639c-4239-9e62-42952ddb48d0 -# ╟─0fddec58-67d9-4dec-a72d-445675fa46a6 -# ╠═9dd6fa35-37d5-4ab4-ac2c-f4eefdabadd2 -# ╠═57ec05ca-bf37-4012-be9a-d83c1d9f0f8a -# ╟─5bde987a-3f5c-4ff7-96a0-9e174f73fdfb -# ╠═6e3d4f8f-1766-49a6-9e4a-610671d8aa63 -# ╠═4cd99dce-12df-4e3a-aa91-ff00c61544a8 -# ╠═48ea9893-16f7-457e-93b0-9f168a2f72f4 -# ╠═c3d36685-e1dd-4734-bee9-f7aeebed3473 -# ╠═bdc40079-de7b-489f-9d9b-6e1e2130109f -# ╠═22d009f0-390f-412d-9af8-33d169ac7371 -# ╟─ed7c5547-bd82-4ca7-bf51-dd1c201d88af -# ╟─a3c26839-2acc-49d8-98c4-3a7142dd6512 -# ╟─dac55632-6e17-479e-8090-5cf8eaa67dad -# ╟─7df97000-5cf0-4a29-b3c3-f867403f4318 -# ╠═0fe475dd-411e-474e-9524-ef9fcceed7af -# ╠═382db9a6-edcb-497b-9535-b576cd6badb0 -# ╠═fce3e814-97a7-4e12-a7cc-b5b086dc8340 -# ╠═a7811e57-2820-4662-8c20-a4d2d7efd642 -# ╟─308cb1f5-e26b-43b2-be4d-9eabd68b3670 -# ╟─490cbc67-3e0c-4c79-a83d-52cf493854a2 -# ╠═dbadf227-1399-45f7-ad48-df9d59e18343 -# ╠═5d812594-733e-4b18-88dc-c28867cf0933 -# ╠═0373b6a5-bb2c-47b9-a137-5a484b0c7bf2 -# ╠═ec06259f-67b5-4920-8635-35c52c080751 -# ╠═a628bd3f-5d07-435e-80cc-b72662e13e7b -# ╠═e2fd7500-277a-4d52-982c-5aea8421d756 -# ╠═96ee6380-719e-47c1-a429-4fdd1931c9ce -# ╟─cd37e5fc-ae9a-429d-844d-4b450b187b5e -# ╠═126d3954-c77d-4c98-abe0-fd87d14e6265 -# ╠═15fe25d9-5314-4056-8021-cf259ba27c94 -# ╠═2a142576-aec2-4311-9c0e-cb68665d59f6 -# ╠═0130c903-e3dd-415e-aa17-1b705f9e4ccc -# ╠═11676f8a-d4fe-4999-9ea2-486fca461f65 -# ╠═f6bc2fad-24c7-48a7-9328-34b616fa106f -# ╠═347a4533-244f-4e93-b125-cdd56a5a08ad -# ╟─9a168680-9d96-466f-ba75-122d6a391501 -# ╟─1f35d962-a249-4be7-9a96-17eb83fca7d8 -# ╠═3ad29b60-375b-400b-b1e5-8918da6497ff -# ╠═0cce9dee-43ac-42d3-9113-8b1503c7a73c -# ╟─8d501c17-58d2-43d3-92c2-0d21bc9113e3 -# ╟─c626d3bf-4abe-4bdc-991a-363bee27b25b -# ╠═8fc58fa4-b005-4f32-9eae-a8143582a1ae -# ╠═fe2958a7-e9dd-4eca-979d-a80df12f8735 -# ╠═93f6b680-aece-45d7-804e-a27073130d94 -# ╠═8abafb6a-dc83-422c-82d1-a721a0e1eca0 -# ╠═5ba2886c-b2e1-49f4-90c6-549acc808f77 -# ╠═ed022247-5959-481f-a81e-41e5ee5a1448 -# ╠═c57046cd-5f53-43a2-9de4-9bfad27dcab9 -# ╠═a2ebe90c-1006-4fc3-b8bd-bdf9c9e9fed5 -# ╠═5b505f97-e776-4cc3-99f8-721dc241ae2a -# ╠═deaf3485-9bc1-48da-809c-61327cce5ad9 -# ╠═56a91508-29fa-4e5f-8dcc-0bb9d67db1fe -# ╠═878e5eaf-2f1c-4c09-a9dd-47821e9295e5 -# ╠═7366d15b-9c03-4200-8720-6d879bb65476 -# ╟─9dc0456b-7fd2-4120-8f9e-3de1984ff516 -# ╟─225cd579-1e0d-4680-8d3f-5a737a656eb8 -# ╟─3ffac5ca-3635-4aa1-bab7-7c28e7a801cb -# ╟─b477b212-83a2-42f0-a616-52516e152d48 -# ╟─23056f1e-128e-463e-a80f-56299397022e -# ╠═3cd0c888-9f11-47f1-a293-b96aa80ea3b0 -# ╠═afcf1260-db4d-4ff1-ac07-978161874e6c -# ╟─8e030a06-5104-4c5b-b1f2-f86464e66502 -# ╠═6ebf3a16-0e6a-491f-8280-b4327ed52cf0 -# ╠═b15de91a-fc48-4cdc-a35f-6453a9a59982 -# ╠═4f6bfecd-43a3-44c1-a20c-224c01b8469d -# ╠═41c1e279-4d0b-4447-a9ab-015863df8e91 -# ╠═a786c349-34e3-4dda-b139-808259495753 -# ╠═4f5c0761-12ef-4997-9e40-050c30ec84ab -# ╟─952a941a-8703-45a3-aac1-a290a181e8c5 -# ╠═8629d049-b9fd-4e9e-9b55-401a3069e956 -# ╠═75d4d482-2f30-4c21-be04-0b821635346f -# ╠═bdb0d902-3056-43fb-abb0-15f2494fcf9d -# ╠═7372c616-05a8-428d-ab04-a7be9f65653d -# ╠═a60315f2-92f5-4f3b-b154-c3428816bfb5 -# ╠═05c700e8-f24a-4c82-9d15-cb3450de9e2a -# ╠═40cc67c1-8627-4f1f-b135-a9770f916b53 -# ╠═8b9d7708-4a67-457a-9fe1-de12fb2a2de9 -# ╠═5d8929fd-5aa4-4186-9fd0-e0a64ac06cbe -# ╠═8e09d7f1-f8db-42a1-beb0-ed3afb043c19 -# ╟─30088664-5157-4d99-8584-7a42d0acdfb8 -# ╟─7dd2c189-79c0-4d29-9e17-9c24a78b5791 -# ╟─dcbf405c-786c-4226-b35c-dc718452bb61 -# ╟─28d28034-e999-4e34-b6b2-63c762094c59 -# ╠═791ff4ed-c9d1-48e2-9dd0-4bf3979c6167 -# ╠═cb189957-f9d4-480b-a492-92cfc2a8c2aa -# ╠═81eca3b3-12d9-43d7-af14-e9aeb73f2471 -# ╠═4e96908a-4fc9-429d-bf37-7a569194a038 -# ╟─7eedb74d-eee1-4cf0-b2bf-5febf474edd2 -# ╠═abbb225e-fdcd-435e-9f81-c1e6fa1c8f5d -# ╠═f1086281-ba2d-461d-bcfe-82e3d09aaae0 -# ╠═c4a2335d-5926-4da4-a46f-41da7b67fa01 -# ╠═84d27c98-9513-4ae3-8101-621c083a1b01 -# ╠═543c40d5-e8a7-492d-a0b8-e7e73e5953e2 -# ╠═22173937-25a1-4ec0-877d-f9669653e43e -# ╠═0b59a6bc-a886-49fd-a3f5-9b20e60882b9 -# ╠═121c291f-ec34-47cb-a9fc-01309b0f42c5 -# ╠═7efa1868-08b9-49fb-b799-76f1c99d1a0e -# ╠═374d7cea-2dc6-4cb3-b929-ddaba1f4c6fb -# ╠═95d223a0-748b-4656-8777-e151c0dc8e93 -# ╠═2668e03a-91eb-420f-af3f-b74d83126f76 -# ╠═87be71c4-2bc3-432b-a977-f3d46424a68f -# ╠═0d04b0e8-3d1a-4281-b175-570148569ef2 -# ╟─0a140d2a-a24b-48df-9af8-7fa5d586a26f -# ╠═b7649523-fd40-4fe3-8d86-fc2cb2c8c488 -# ╠═b7ba8f91-f440-4166-af9e-11fcb5f1755f -# ╠═3b5f3623-3d00-4ba4-9182-5bddacc56567 -# ╠═c11452bc-f404-4e51-8f68-292f5b538c88 -# ╠═c1174a5c-a6a1-46a0-96be-6997e3201dfc -# ╠═793fc102-fc64-4041-a04e-e0b1b0741437 diff --git a/src/exercises/MCMC_3-advanced.jl b/src/exercises/MCMC_3-advanced.jl deleted file mode 100755 index 2296ea53..00000000 --- a/src/exercises/MCMC_3-advanced.jl +++ /dev/null @@ -1,295 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "22" -#> title = "4. MCMCM advanced" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "MCMC advanced" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). -macro bind(def, element) - #! format: off - quote - local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end - local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) - el - end - #! format: on -end - -# ╔═╡ 75581580-2fb2-4112-b397-2b775eb64630 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ e07a1ae5-43b7-4c12-831d-43e1738eeac0 -using Turing, StatsPlots - -# ╔═╡ d12cdd62-f90a-4ba8-8610-5e86e922e881 -using PlutoUI - -# ╔═╡ f84d9259-69c0-4165-8bc0-d924fef18182 -md"# Inference notebook #3: Advanced" - -# ╔═╡ aa2b6263-7d13-4683-bc92-25663ed02604 -md"## GPS" - -# ╔═╡ f3510387-1ab6-4aa2-bed9-9c8297a8b3c5 -md""" -GPS systems need to decide what road a car is following based on noisy positional data. We consider here a simplified example. - -At some known timepoints `ts`, we get noisy observations on the car's vertical position `ys_obs` (imagine it as a lattitude of sorts). There are two parallel roads (lines) the car can actually be on, which both have a constant vertical position. If the car is on road 1, then `y = 0`. If it is on road 2, then `y = 1`. The problem is visualized below. -""" - -# ╔═╡ 90f185f3-91d4-4ee7-8de3-b76251c0c169 -ts = 1:10; - -# ╔═╡ 852a8d4e-3374-4805-b372-da3e93f4c664 -collect(ts) - -# ╔═╡ 599ae818-fb28-4803-b720-9fcf2bb162b7 -ys_obs = [0.6, 0.0, 0.8, -0.7, -0.5, 0.2, 1.0, 1.2, 1.8, 1.1]; - -# ╔═╡ b2da9dc3-006c-49d0-81a7-6375a2dc6872 -begin - p_cardata = scatter(ts, ys_obs, label = "Observed car positions", - xlabel = "Time", ylabel = "Vertical position" - ) - hline!([0.0], color = :orange, label = "Road 1", linewidth = 2) - hline!([1.0], color = :blue, label = "Road 2", linewidth = 2) -end - -# ╔═╡ 46d3214c-e724-4b98-bc1f-5b9913d2b14a -md""" -At some point `t_switch` ∈ [0, 10], the car switches from lane 1 to lane 2. We can describe the model as follows: -- If `t <= t_switch`, then `y ~ Normal(0.0, σ)`, -- If `t > t_switch`, then `y ~ Normal(1.0, σ)`, -with `σ` some (small) noise parameter. -""" - -# ╔═╡ 0bdac14c-db71-47b5-95d5-d83fb1e68cab -md""" -Below is a plot showing the car's trajectory for some value of `t_switch`. You can adjust the slider to change this guess value. -""" - -# ╔═╡ 9e3db8b6-5466-48b6-9ca9-0d24515b9de3 -@bind switchtime Slider(0:0.1:10, default = 5.0, show_value = true) - -# ╔═╡ cc0d6b32-6f12-46b8-915f-8a471317c35e -begin - plot!( - deepcopy(p_cardata), [0.0, switchtime, switchtime, 10.0], - [0.0, 0.0, 1.0, 1.0], label = "Car trajectory", color = :black, - linewidth = 2, xticks = ([0, 10, switchtime], ["0", "10", "t_switch"]) - ) -end - -# ╔═╡ 96fc1412-83fc-4f67-8995-065b163d739c -md""" -!!! question - Infer the posterior probability of `t_switch` given the data. -""" - -# ╔═╡ 70fd793a-3ce3-446f-adc1-65ce0a68e48a -@model function cars(ts) - t_switch ~ Uniform(0, 10) - σ ~ Exponential(1.0) - - ys_obs = zeros(length(ts)) - for pointidx in 1:length(ts) - if ts[pointidx] <= t_switch - ys_obs[pointidx] ~ Normal(0.0, σ) - else - ys_obs[pointidx] ~ Normal(1.0, σ) - end - end -end - -# ╔═╡ 3db9343e-9fb1-4f5c-bbfd-81e5a3623b44 -carmodel = cars(ts) | (ys_obs = ys_obs,) - -# ╔═╡ b39b4b09-95ee-49db-924c-0280847b408e -carchain = sample(carmodel, NUTS(), 2000) - -# ╔═╡ 36c7809e-7597-491d-a79e-100cca700597 -plot(carchain) - -# ╔═╡ 261e426a-60d6-4dbd-aad5-88a96f408b1d -histogram(carchain[:t_switch], normalized=:probability) - -# ╔═╡ 0c389fd7-35ba-41e1-9154-0c374d996156 -mean(carchain[:t_switch]) - -# ╔═╡ 34bf815a-3bd3-49b5-b06d-d4297ca213a8 -md"## Petridish peril (inference edition)" - -# ╔═╡ 78571fb5-c44f-4e7f-afde-4436db6c945b -md""" -We continue with the "petridish peril" question from the previous practical. - -You've made a model to predict bacterial population levels at certain timepoints based on your knowledge of how the species in question grows. You'd now like to update the model with information about the specific strain you're using, so you inoculate a petri dish and count the number of bacteria after a short incubation period. - -Incorporate the following information into the model to make it more accurate: -- The population level after 5 hours of incubating was 21000. -- You expect the number of bacteria you count to be Poisson distributed around the actual number. -""" - -# ╔═╡ 2a84472c-cb6f-4607-9b98-c88cc2744e3d -md""" -!!! questions - 1. Now taking into account the measurement, what are the chances of your petridish being in a splittable state after 8 hours? - 1. Visualise the updated growth curves. - 1. 🌟(BONUS): The prior for P0 being discrete doesn't allow for the use of a continuous sampler. Change the prior with a sufficiently similar continuous one to fix this. How does this affect the results? -""" - -# ╔═╡ 34120240-6fac-42b8-b5c8-bc3271927248 -md""" -!!! tip - Just like in the previous version of the question, `return`ing the estimated logistic function can be useful. -""" - -# ╔═╡ 4b94e5a6-1ed6-4095-ab18-06b76d4fec99 -logistic(t, P0, r, K) = K / (1 + (K - P0)/P0 * exp(-r*t)) - -# ╔═╡ 234b2c87-fe91-4be4-bf0c-a6f20dcc38fe -md"### 1" - -# ╔═╡ f080f708-a457-40a3-936c-b82d5159975d -dropletdist = MixtureModel([Poisson(10), Poisson(30)], [0.75, 0.25]); - -# ╔═╡ 7b4aef69-10ec-4935-b7fd-4c1d49aa9b3d -@model function petrigrowth(t_obs) - P0 ~ dropletdist - r ~ LogNormal(0.0, 0.3) - K ~ Normal(1e5, 1e4) - - logfun = t -> logistic(t, P0, r, K) - - Pt = logfun(t_obs) # Number of bacteria at the observed time - P_obs ~ Poisson(Pt) # The observed number is Poisson distributed - - return logfun -end - -# ╔═╡ a5b5a65e-2bac-49fa-b4ca-2fcaf64e4ada -petrimodel = petrigrowth(5) | (P_obs = 21_000,) - -# ╔═╡ b579ccc6-993e-451b-a137-6fff6b630b49 -petrichain = sample(petrimodel, MH(), 100_000) # better and faster than PG(40) - -# ╔═╡ 9633f07a-d583-4680-b3cc-f5701540968f -plot(petrichain) - -# ╔═╡ 7d0e5f0b-2cdf-4946-a186-f70774e363bb -logfuns = generated_quantities(petrimodel, petrichain); - -# ╔═╡ c642574c-c01b-4398-aac9-43e514d7fa25 -sp_petri = [logfun(8.0) for logfun in logfuns] - -# ╔═╡ d15fa091-839c-4239-96e2-eaf7335ce620 -prob_splittable = mean((sp_petri .>= 1e4) .&& (sp_petri .<= 1e5)) - -# ╔═╡ 0ea95b67-d4da-4c5a-ad2e-05024ad074a3 -md"### 2" - -# ╔═╡ 3d9b73ef-2aae-4c19-bddc-4081927ec92d -plot(logfuns[1:10:1000], xlims = (0, 12), legend = false, color = :skyblue, alpha = 0.5) - -# ╔═╡ 9ab88be4-4cf8-4747-ac36-3f1b82899be0 -md"### 3🌟" - -# ╔═╡ 113d8311-7bdc-461c-b077-920e23b33d39 -dropletdist🌟 = MixtureModel( - [ - truncated(Normal(10, sqrt(10)), lower = 0.0), - truncated(Normal(30, sqrt(30)), lower = 0.0) - ], - [0.75, 0.25] -); - -# ╔═╡ 4e730df9-f619-464a-b8a3-57448132404b -begin - plot(dropletdist, label = ["Original prior" ""], color = :orange) - plot!(dropletdist🌟, label = ["Continuous alternative" ""], color = :blue) - # With mixture models it takes some fiddling to make the labels look nice - don't worry about this, it's not important for the course -end - -# ╔═╡ d9b50958-20ae-4085-80d6-19420c7d89df -@model function petrigrowth🌟(t_obs) - P0 ~ dropletdist🌟 - r ~ LogNormal(0.0, 0.3) - K ~ Normal(1e5, 1e4) - - logfun = t -> logistic(t, P0, r, K) - Pt = logfun(t_obs) - P_obs ~ Poisson(Pt) - - return logfun -end - -# ╔═╡ 9ac7aa12-d945-4078-ae63-d598d7171112 -let # so we dont need to rename all variables - petrimodel = petrigrowth🌟(5) | (P_obs = 21_000,) - petrichain = sample(petrimodel, NUTS(), 2_000) - logfuns = generated_quantities(petrimodel, petrichain); - sp_petri = [logfun(8.0) for logfun in logfuns] - prob_splittable = mean((sp_petri .>= 1e4) .&& (sp_petri .<= 1e5)) - println("The new `prob_splittable` is ", prob_splittable) - plot(petrichain) -end - -# ╔═╡ 2f4308df-4451-4ac2-8a31-65cd49a275af -md"Changing to all continuous distributions made the inference much higher quality in this case, as can be seen from the chain plots. This also means this result is more reliable!" - -# ╔═╡ Cell order: -# ╟─f84d9259-69c0-4165-8bc0-d924fef18182 -# ╠═75581580-2fb2-4112-b397-2b775eb64630 -# ╠═e07a1ae5-43b7-4c12-831d-43e1738eeac0 -# ╠═d12cdd62-f90a-4ba8-8610-5e86e922e881 -# ╟─aa2b6263-7d13-4683-bc92-25663ed02604 -# ╟─f3510387-1ab6-4aa2-bed9-9c8297a8b3c5 -# ╠═90f185f3-91d4-4ee7-8de3-b76251c0c169 -# ╠═852a8d4e-3374-4805-b372-da3e93f4c664 -# ╠═599ae818-fb28-4803-b720-9fcf2bb162b7 -# ╟─b2da9dc3-006c-49d0-81a7-6375a2dc6872 -# ╟─46d3214c-e724-4b98-bc1f-5b9913d2b14a -# ╟─0bdac14c-db71-47b5-95d5-d83fb1e68cab -# ╟─9e3db8b6-5466-48b6-9ca9-0d24515b9de3 -# ╟─cc0d6b32-6f12-46b8-915f-8a471317c35e -# ╟─96fc1412-83fc-4f67-8995-065b163d739c -# ╠═70fd793a-3ce3-446f-adc1-65ce0a68e48a -# ╠═3db9343e-9fb1-4f5c-bbfd-81e5a3623b44 -# ╠═b39b4b09-95ee-49db-924c-0280847b408e -# ╠═36c7809e-7597-491d-a79e-100cca700597 -# ╠═261e426a-60d6-4dbd-aad5-88a96f408b1d -# ╠═0c389fd7-35ba-41e1-9154-0c374d996156 -# ╟─34bf815a-3bd3-49b5-b06d-d4297ca213a8 -# ╟─78571fb5-c44f-4e7f-afde-4436db6c945b -# ╟─2a84472c-cb6f-4607-9b98-c88cc2744e3d -# ╟─34120240-6fac-42b8-b5c8-bc3271927248 -# ╠═4b94e5a6-1ed6-4095-ab18-06b76d4fec99 -# ╟─234b2c87-fe91-4be4-bf0c-a6f20dcc38fe -# ╠═f080f708-a457-40a3-936c-b82d5159975d -# ╠═7b4aef69-10ec-4935-b7fd-4c1d49aa9b3d -# ╠═a5b5a65e-2bac-49fa-b4ca-2fcaf64e4ada -# ╠═b579ccc6-993e-451b-a137-6fff6b630b49 -# ╠═9633f07a-d583-4680-b3cc-f5701540968f -# ╠═7d0e5f0b-2cdf-4946-a186-f70774e363bb -# ╠═c642574c-c01b-4398-aac9-43e514d7fa25 -# ╠═d15fa091-839c-4239-96e2-eaf7335ce620 -# ╟─0ea95b67-d4da-4c5a-ad2e-05024ad074a3 -# ╠═3d9b73ef-2aae-4c19-bddc-4081927ec92d -# ╟─9ab88be4-4cf8-4747-ac36-3f1b82899be0 -# ╠═113d8311-7bdc-461c-b077-920e23b33d39 -# ╠═4e730df9-f619-464a-b8a3-57448132404b -# ╠═d9b50958-20ae-4085-80d6-19420c7d89df -# ╠═9ac7aa12-d945-4078-ae63-d598d7171112 -# ╟─2f4308df-4451-4ac2-8a31-65cd49a275af diff --git a/src/exercises/MCMC_4-review.jl b/src/exercises/MCMC_4-review.jl deleted file mode 100755 index 7d7594c1..00000000 --- a/src/exercises/MCMC_4-review.jl +++ /dev/null @@ -1,153 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "23" -#> title = "4. MCMC review" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "MCMC review" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ ef127ffc-1e2e-4c30-945b-d6cded4d6515 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ deb237f9-f0ff-4dfc-8627-43053ccdeb20 -using Turing, StatsPlots - -# ╔═╡ 2d709e7b-3350-47ec-a3ed-8aa47ad5a8c2 -function generate_data(n_wasps = 10; minbound = 0, maxbound = 1000) - - # position of the nest - x_n, y_n = rand(DiscreteUniform(minbound, maxbound), 2) - # positions of the feeder stations where the wasps get marked - xs, ys = [rand(DiscreteUniform(minbound, maxbound), n_wasps) for _ in 1:2] - # speed of the wasps - v_wasps = rand(Uniform(5, 10), n_wasps) - # back and forth time of the wasps - ts = [2*sqrt((x-x_n)^2 + (y-y_n)^2)/v_wasp for (x, y, v_wasp) in zip(xs, ys, v_wasps)] - - return xs, ys, ts, [x_n, y_n] -end; - -# ╔═╡ 789acc88-314e-449e-abf2-8ba9c95bc322 -let - x_n, y_n = rand(DiscreteUniform(0, 1000), 2) - println([x_n, y_n]) - - xs, ys = [rand(DiscreteUniform(0, 1000), 5) for _ in 1:2] - println(xs) - println(ys) - - v_wasps = rand(Uniform(5, 10), 5) - println(round.(v_wasps, digits=2)) - - ts = [2*sqrt((x-x_n)^2 + (y-y_n)^2)/v_wasp for (x, y, v_wasp) in zip(xs, ys, v_wasps)] - println(round.(ts, digits=2)) -end - -# ╔═╡ af96af94-d969-4e9a-93f4-e205f8b7f576 -md"# Review exercise: Hornet nests" - -# ╔═╡ 07f05baa-1336-4e1a-9cbc-5f4506e5b34a -md""" -In recent years, the Asian giant hornet (_Vespa mandarinia_) has become an invasive species in a number of countries, including Belgium. Since they become aggressive when people get close to their nests, the nests often need to be removed when they appear in residential areas. Finding the nests, however, can be a difficult task: the hornets can go hunting over a kilometer from their nest. -""" - -# ╔═╡ 90aee617-84d5-4915-afb4-e7d422cfb4b7 -md""" -One method for finding the nest is to set up a feeder station, mark any hornets gathering food, and record how long it takes for them to fly back to their nest with it and return for more. Making an estimate of their flight speed, the return time can be used to infer the distance of that location to the nest. Repeated measurements in other locations gives enough information for a triangulation of sorts. -""" - -# ╔═╡ 082a4cc0-b151-4f8b-87da-278484218e6e -md""" -![The Asian giant hornet](https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Vespa_mandarinia_japonica1.jpg/1280px-Vespa_mandarinia_japonica1.jpg) -*The Asian giant hornet (credit: Picture by KENPEI on Wikipedia)* -""" - -# ╔═╡ be7dbb0f-299a-4925-b4c4-20eb6ce6e4af -md""" -Consider below the coordinates of marked hornets, as well as their return times. -""" - -# ╔═╡ c1850e64-9e2c-46fb-b7cd-22e8af81d3aa -xs, ys, ts, true_location = generate_data(20); - -# ╔═╡ 28cb3363-6856-4164-b60e-36ec2e88ed56 -scatter(xs, ys, label = "wasp locations", marker_z = ts, title = "Locations of wasps colored by return time", xlims = (0, 1000), ylims = (0, 1000)) - -# ╔═╡ 484b56ec-57b2-496d-a5cf-1b1c0da97c58 -md""" -!!! question - Where is the hornet nest located? You may assume the nest is somewhere within the plot's boundaries. -""" - -# ╔═╡ 365469ba-35ce-4519-a0b7-c396caddc339 -plot(Gamma(8, 1)) - -# ╔═╡ ac0496d3-aa62-4c07-8233-16fe67c52b24 -@model function horenaars(xs, ys, ts) - x_nest ~ Uniform(0, 1000) - y_nest ~ Uniform(0, 1000) - # v_wasp ~ Uniform(5, 10) - v_wasp ~ Gamma(8) - - for i in eachindex(ts) - dist = sqrt((xs[i] - x_nest)^2 + (ys[i] - y_nest)^2) - ts[i] ~ Normal(2*dist / v_wasp, 10) - end -end - -# ╔═╡ 88e95234-4e43-4ead-bd0f-f32f9fc109c2 -n_samples = 1000 - -# ╔═╡ c5a94b3e-a4b8-42e6-a8c7-79e942212852 -chain = sample(horenaars(xs, ys, ts), NUTS(), n_samples) - -# ╔═╡ 1c84ab96-b3db-494e-860e-80e2d178da43 -plot(chain) - -# ╔═╡ 4eb71114-fd0d-4968-b7a3-c12290d5eb40 -x_nest_sp = chain[:x_nest]; - -# ╔═╡ 927c8514-8c4f-4582-9c40-a2d7d7bb3452 -y_nest_sp = chain[:y_nest]; - -# ╔═╡ 0c6be1fa-bff5-495b-90e0-90dabe03105a -begin - scatter(x_nest_sp, y_nest_sp, opacity = 0.1, color = :blue, label = "Estimated nest locations", xlims = (0, 1000), ylims = (0, 1000)); - scatter!(xs, ys, color = :orange, label = "wasp locations", marker_z = ts) - scatter!(true_location[1:1], true_location[2:2], color = RGB(1, 1, 1), label = "True nest location", markershape=:rect) -end - -# ╔═╡ 6725a638-5738-49f3-801a-90389b4b66be -true_location - -# ╔═╡ Cell order: -# ╠═ef127ffc-1e2e-4c30-945b-d6cded4d6515 -# ╠═deb237f9-f0ff-4dfc-8627-43053ccdeb20 -# ╠═2d709e7b-3350-47ec-a3ed-8aa47ad5a8c2 -# ╠═789acc88-314e-449e-abf2-8ba9c95bc322 -# ╟─af96af94-d969-4e9a-93f4-e205f8b7f576 -# ╟─07f05baa-1336-4e1a-9cbc-5f4506e5b34a -# ╟─90aee617-84d5-4915-afb4-e7d422cfb4b7 -# ╟─082a4cc0-b151-4f8b-87da-278484218e6e -# ╟─be7dbb0f-299a-4925-b4c4-20eb6ce6e4af -# ╠═c1850e64-9e2c-46fb-b7cd-22e8af81d3aa -# ╟─28cb3363-6856-4164-b60e-36ec2e88ed56 -# ╟─484b56ec-57b2-496d-a5cf-1b1c0da97c58 -# ╠═365469ba-35ce-4519-a0b7-c396caddc339 -# ╠═ac0496d3-aa62-4c07-8233-16fe67c52b24 -# ╠═88e95234-4e43-4ead-bd0f-f32f9fc109c2 -# ╠═c5a94b3e-a4b8-42e6-a8c7-79e942212852 -# ╠═1c84ab96-b3db-494e-860e-80e2d178da43 -# ╠═4eb71114-fd0d-4968-b7a3-c12290d5eb40 -# ╠═927c8514-8c4f-4582-9c40-a2d7d7bb3452 -# ╠═0c6be1fa-bff5-495b-90e0-90dabe03105a -# ╠═6725a638-5738-49f3-801a-90389b4b66be diff --git a/src/exercises/calib_fermenter_monod.jl b/src/exercises/calib_fermenter_monod.jl deleted file mode 100644 index 38730ff5..00000000 --- a/src/exercises/calib_fermenter_monod.jl +++ /dev/null @@ -1,300 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "25" -#> title = "5. Calibration fermenter monod" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Calibration fermenter monod" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ c54dae10-60af-4141-b56d-ed61cb0ced8a -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 245ca9d0-10f9-11ef-0ef6-a73594e96db9 -using Markdown - -# ╔═╡ 78a25bef-31e5-45ef-b0ba-b9a8c8a9edeb -using InteractiveUtils - -# ╔═╡ 16438e07-1b2b-467e-822a-081d19cae92b -using Catalyst, OrdinaryDiffEq, StatsPlots - -# ╔═╡ 295caa68-db27-4c9b-bc34-86ab088fec24 -using Turing, StatsBase - -# ╔═╡ dc6e9bdc-dae0-43aa-b624-f9314d1d9884 -using LinearAlgebra, Optim - -# ╔═╡ 2f0a4c62-3441-4c63-9bb9-383e7f554eb5 -md""" -# Exercise: Fermenter - Monod kinetics - Calibration -""" - -# ╔═╡ 595ea8ee-bc67-4696-9232-982612fb554d -md""" -In one of the previous practicals we were introduced to a fermenter in which biomass $X$ [$g/L$] grows by breaking down substrate $S$ [$g/L$]. The reactor is fed with an inlet flow rate $Q_{in}$ [$L/h$], which consists of a (manipulable) inlet concentration of substrate $S_{in}$ [$g/L$]. This process was modelled using Monod kinetics, resulting in the model below: - -$$\begin{eqnarray*} -S + X \xrightarrow[\quad\quad]{k} (1 + Y) \, X \quad\quad\quad\quad \textrm{with} \quad k = \cfrac{\mu_{max}}{S + K_s} -\end{eqnarray*}$$ -""" -# $$\begin{eqnarray*} -# %S \xrightarrow[\quad\quad]{\beta} Y \, X -# S \xrightarrow[\quad\quad]{r} Y \, X \quad\quad\quad\quad r = \mu \, X \quad \textrm{with} \quad \mu = \mu_{max} \, \cfrac{S}{S + K_s} -# \end{eqnarray*}$$ - - -# ╔═╡ 824db995-7a66-4719-a534-7e0f6dec90b5 -md""" -The *reaction network object* for this model could be set-up as: -""" - -# ╔═╡ 245c2636-95da-4c76-8b03-c4d20bbabb48 -fermenter_monod = @reaction_network begin - μmax/(S+Ks), S + X --> (1 + Y)*X - # Alternatives: - # X * mm(S, μmax, Ks), S => Y*X - # mm(S, μmax, Ks)*X, S + X => (1 + Y)*X - Q/V, (S, X) --> 0 - Q/V*Sin, 0 --> S -end - -# ╔═╡ 956790e2-6cac-46c9-886e-24d5aceae1c5 -convert(ODESystem, fermenter_monod, combinatoric_ratelaws=false) - -# ╔═╡ 68f9ecb3-15b0-4a53-8864-5dac13a89e95 -parameters(fermenter_monod) - -# ╔═╡ de8ddc14-8f82-403d-8f42-29673ef2a722 -md""" -which resulted in the following differential equations: - -$$\begin{eqnarray*} -\cfrac{dS}{dt} &=& \cfrac{Q}{V} \left(S_{in} - S \right) - \mu_{max}\cfrac{S}{S + K_s} X\\ -\cfrac{dX}{dt} &=& -\cfrac{Q}{V} X + Y \mu_{max}\cfrac{S}{S + K_s} X -\end{eqnarray*}$$ -""" - -# ╔═╡ b7b7d58f-d406-4596-b834-ced6d8fada83 -md""" -Suppose that during an experiment measurement data have been collected of the substrate $S$ and biomass $X$ concentration at an interval of $5\;h$ within $100\;h$: -""" - -# ╔═╡ 99c6f31a-0968-4804-9980-71fcc1af1f49 -S_meas = [1.0e-5, 0.0047, 0.00796, 0.01056, 0.01214, 0.01325, 0.01344, 0.01338, 0.0115, 0.00917, 0.00604, 0.00458, 0.00438, 0.00342, 0.00323, 0.00329, 0.00312, 0.00314, 0.00319, 0.00299, 0.00311] - -# ╔═╡ bf4ad873-e0fe-415c-9e78-fe0b5ac1414e -X_meas = [0.00052, 0.00042, 0.00074, 0.00078, 0.00122, 0.00159, 0.00242, 0.00372, 0.00534, 0.0077, 0.00935, 0.00997, 0.01114, 0.01144, 0.01264, 0.01276, 0.01183, 0.01319, 0.01256, 0.01277, 0.01377] - -# ╔═╡ 1dae5875-f405-4ecb-8b7b-3c3f22b549bb -t_meas = 0.0:5.0:100.0 - -# ╔═╡ 6c481447-28c6-4530-bf2c-64762121bc71 -md""" -Make a scatter plot of the measured data for both $S$ and $X$. Use the following options: -- `label=\"S meas\", color=:blue` for $S$, and -- `label=\"X meas\", color=:red` for $X$. -""" - -# ╔═╡ 918fd524-81fa-4aff-a403-37402e47235b -# Uncomment and complete the instruction -# begin -# missing -# missing -# end - -# ╔═╡ ef977370-06ee-4a73-85e2-609a744167d3 -md""" -We have previously used the following parameter values: - -- $\mu_{max} = 0.40\;h^{-1}$, $K_s = 0.015\;g/L$, $S_{in} = 0.022\;g/L$ -- $Y = 0.67$, $Q = 2.0\;L/h$, $V = 40.0\;L$ - -Furthermore, suppose that at $t = 0\;h$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concentration of $0.0005\;g/L$. - -Calibrate the parameter values for $\mu_{max}$ and $K_s$ using the aforementioned measurement data for $S$ and $X$ in a timespan of $[0, 100]\,h$. Take the values above as initial values for $\mu_{max}$ and $K_s$. -""" - -# ╔═╡ 7a227eaf-18d0-44f4-ac4b-f529e81c7471 -md""" -Create an `ODEProblem`. Use the aforementioned values as initial values for the problem. -""" - -# ╔═╡ 6375478f-1af9-4fd2-b6f3-101a6f796f2d -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 38fe8304-af61-40a7-ac86-480dfb892185 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 87482f88-8413-4820-9613-7941f3d61bd7 -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 94f3bd7b-5c2c-4661-a0ab-2cdaf2cd6743 -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ f6a8f134-6db0-4d74-8af5-82826347d8f0 -md""" -Declare the Turing model. Use `InverseGamma` for the standard deviations of the measurements and `LogNormal` for `μmax` and `K`. -""" - -# ╔═╡ 4c28a66a-ee2c-42a2-95c7-ea4ddb6a232d -# Uncomment and complete the instruction -# @model function fermenter_fun(t_meas) - # σ_S ~ missing - # σ_X ~ missing - # μmax ~ missing - # Ks ~ missing - # params = missing - # oprob = missing - # osol = missing - # S_s ~ missing - # X_s ~ missing -# end - -# ╔═╡ 3136b15d-5078-4bcd-954b-e89bcb8aed1b -md""" -Provide the time measurements to the defined function and instantly condition the model with the measurements of $S$ and $X$: -""" - -# ╔═╡ 6a508a62-61b9-4273-8e45-b26f594e8da9 -# fermenter_cond_mod = missing # Uncomment and complete the instruction - -# ╔═╡ 63420055-55f8-4def-8b0e-11ea61483010 -md""" -Optimize the priors ($\sigma_S$, $\sigma_X$, $\mu_{max}$ and $K_s$). Do this with `MLE` method and Nelder-Mead. Store the optimization results in `results_mle`. If necessary, run the optimization again if you get any errors. -""" - -# ╔═╡ d52c9da8-d8a4-4db0-ac6d-6d16ccf4775c -# results_map = missing # Uncomment and complete the instruction - -# ╔═╡ e1b0ee01-f16c-40e9-a0f9-80072d690936 -md""" -Visualize a summary of the optimized parameters. -""" - -# ╔═╡ f2d7daf8-8218-446d-b1d2-e9e05aeadfd9 -# missing # Uncomment and complete the instruction - -# ╔═╡ 23d58bb1-d077-402e-8bee-3866c68e069a -md""" -Get the optimized values and assign them to `μmax_opt` and `Ks_opt`. -""" - -# ╔═╡ 7b3a3677-b251-43c1-b125-6d6ff1a11ea3 -# μmax_opt = missing # Uncomment and complete the instruction - -# ╔═╡ fa77bcbe-2ddc-4113-8f6a-4a18d219da9e -# Ks_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 05d13a48-adc8-4e24-a6e4-be24af2c7a59 -md""" -Make a plot of $S$ and $X$ simulated with the optimized parameter values. -""" - -# ╔═╡ 57ef3824-7c20-4876-8d83-665cb4f97a58 -md""" -Set up parameter values with optimized parameter values: -""" - -# ╔═╡ 75cf59ed-af8e-4e8a-8ed2-1f3bf4d386d0 -# params_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 4e8870dc-2da6-4b80-82d6-26c7ceedad7d -md""" -Create an ODEProblem and solve it. Use `Tsit5()` and `saveat=0.5`. -""" - -# ╔═╡ 853c1a92-d50f-4b05-9ed3-d3ee1656665a -# oprob_opt = missing # Uncomment and complete the instruction - -# ╔═╡ f45e8124-e942-438e-99c5-3032ccc01454 -# osol_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 5a39b0e0-1ea1-4854-8e68-66d0d4bbf25c -md""" -Plot $S$ and $X$ simulated with the optimal and initial parameter values together with the measured data. We can do this to compare the found values with the initial ones and detect possible errors. -""" - -# ╔═╡ d0156099-ad03-4711-ac0f-94882fb78266 -# Uncomment and complete the instruction -# begin -# missing -# missing -# missing -# missing -# missing -# end - -# ╔═╡ 257ae1a9-6264-4122-80be-8022b4b7500c -md""" -!!! question - How do the found optimal parameter values compare to the original values? *Or in other words*: what is the impact to be expected when we simulate the fermenter with the optimal values? -""" - -# ╔═╡ 43cdfb9d-1874-4b42-b350-545029a5f725 -md""" -- Answer: -""" - -# ╔═╡ 22a6aeb4-559c-4f69-82fd-d021f68e1f17 -md""" -!!! hint - Think of the meaning of the estimated parameters and their impact on the variables $S$ and $X$. -""" - -# ╔═╡ Cell order: -# ╠═245ca9d0-10f9-11ef-0ef6-a73594e96db9 -# ╠═78a25bef-31e5-45ef-b0ba-b9a8c8a9edeb -# ╠═c54dae10-60af-4141-b56d-ed61cb0ced8a -# ╠═16438e07-1b2b-467e-822a-081d19cae92b -# ╠═295caa68-db27-4c9b-bc34-86ab088fec24 -# ╠═dc6e9bdc-dae0-43aa-b624-f9314d1d9884 -# ╟─2f0a4c62-3441-4c63-9bb9-383e7f554eb5 -# ╟─595ea8ee-bc67-4696-9232-982612fb554d -# ╟─824db995-7a66-4719-a534-7e0f6dec90b5 -# ╠═245c2636-95da-4c76-8b03-c4d20bbabb48 -# ╠═956790e2-6cac-46c9-886e-24d5aceae1c5 -# ╠═68f9ecb3-15b0-4a53-8864-5dac13a89e95 -# ╟─de8ddc14-8f82-403d-8f42-29673ef2a722 -# ╟─b7b7d58f-d406-4596-b834-ced6d8fada83 -# ╠═99c6f31a-0968-4804-9980-71fcc1af1f49 -# ╠═bf4ad873-e0fe-415c-9e78-fe0b5ac1414e -# ╠═1dae5875-f405-4ecb-8b7b-3c3f22b549bb -# ╟─6c481447-28c6-4530-bf2c-64762121bc71 -# ╠═918fd524-81fa-4aff-a403-37402e47235b -# ╟─ef977370-06ee-4a73-85e2-609a744167d3 -# ╟─7a227eaf-18d0-44f4-ac4b-f529e81c7471 -# ╠═6375478f-1af9-4fd2-b6f3-101a6f796f2d -# ╠═38fe8304-af61-40a7-ac86-480dfb892185 -# ╠═87482f88-8413-4820-9613-7941f3d61bd7 -# ╠═94f3bd7b-5c2c-4661-a0ab-2cdaf2cd6743 -# ╟─f6a8f134-6db0-4d74-8af5-82826347d8f0 -# ╠═4c28a66a-ee2c-42a2-95c7-ea4ddb6a232d -# ╟─3136b15d-5078-4bcd-954b-e89bcb8aed1b -# ╠═6a508a62-61b9-4273-8e45-b26f594e8da9 -# ╟─63420055-55f8-4def-8b0e-11ea61483010 -# ╠═d52c9da8-d8a4-4db0-ac6d-6d16ccf4775c -# ╟─e1b0ee01-f16c-40e9-a0f9-80072d690936 -# ╠═f2d7daf8-8218-446d-b1d2-e9e05aeadfd9 -# ╟─23d58bb1-d077-402e-8bee-3866c68e069a -# ╠═7b3a3677-b251-43c1-b125-6d6ff1a11ea3 -# ╠═fa77bcbe-2ddc-4113-8f6a-4a18d219da9e -# ╟─05d13a48-adc8-4e24-a6e4-be24af2c7a59 -# ╟─57ef3824-7c20-4876-8d83-665cb4f97a58 -# ╠═75cf59ed-af8e-4e8a-8ed2-1f3bf4d386d0 -# ╟─4e8870dc-2da6-4b80-82d6-26c7ceedad7d -# ╠═853c1a92-d50f-4b05-9ed3-d3ee1656665a -# ╠═f45e8124-e942-438e-99c5-3032ccc01454 -# ╟─5a39b0e0-1ea1-4854-8e68-66d0d4bbf25c -# ╠═d0156099-ad03-4711-ac0f-94882fb78266 -# ╟─257ae1a9-6264-4122-80be-8022b4b7500c -# ╠═43cdfb9d-1874-4b42-b350-545029a5f725 -# ╟─22a6aeb4-559c-4f69-82fd-d021f68e1f17 diff --git a/src/exercises/calib_intro.jl b/src/exercises/calib_intro.jl deleted file mode 100644 index 766e2631..00000000 --- a/src/exercises/calib_intro.jl +++ /dev/null @@ -1,1033 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "24" -#> title = "5. Calibration intro" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Calibration intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ f8a92690-990b-4341-89e1-322adbcb8d1b -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ a09f814a-0c6a-11ef-0e79-a50b01287d63 -using Markdown - -# ╔═╡ 7f521435-63ac-4178-a4aa-93d9c45fe820 -using InteractiveUtils - -# ╔═╡ 015050b3-3339-4b1a-ad7d-c358cce73675 -using Catalyst, OrdinaryDiffEq, StatsPlots - -# ╔═╡ dbfe4800-0974-4ca1-bb0a-d8803409a98b -using Turing, StatsBase - -# ╔═╡ 6e227e07-166a-41ce-839a-4c4c72addb23 -using LinearAlgebra, Optim - -# ╔═╡ b992c080-a0ce-4188-b632-e734a141e67d -using PlutoUI; TableOfContents() - -# ╔═╡ 37da8786-fea0-4c2f-a76f-6e6c68325a78 -md""" -# Introduction to calibration -""" - -# ╔═╡ 4623369d-8c5a-422d-9e40-0f1dd7586260 -md""" -## Goal of this practicum -""" - -# ╔═╡ 3cb0a166-ac53-4c3f-9832-e93742040cfb -md""" -In the models discussed in the previous sessions, i.e., the values of all parameters were known. In reality, the value of a parameter often needs to be calibrated, i.e., estimated from experimental data. During this parameter estimation one attempts to find the set of parameter values for which the model predictions are as close as possible to the collected experimental data. -""" - -# ╔═╡ 987f0a4d-e416-4ceb-adbe-3dcdca9d0996 -md""" -The search of optimal parameter values usually involves a function, such as a loss function, a (log) likelihood function or a posterior distribution function. In this session we will be (mainly) using the MLE (Maximum Likelihood Estimation) and MAP (Maximum A Posteriori estimation) methods. - -In the MLE method, a likelihood function of a given probability density function is maximized during the search of optimal parameter values of a model in order to fit experimental data. The parameter values are considered unknown but viewed as fixed points. - -In the MAP method, a posterior distribution function is maximized. Instead of viewing the parameter values as fixed points, they are now treated as random variables in the model which follow a prior distribution. In other words, we have prior belief in which distribution these parameters come from (Normal, Beta, etc). Once new data comes in, we update our prior belief, leading to a posterior belief. Hence, we now have a better idea from which distribution these parameters come. - -One caveat with the MAP method is that it only considers the most likely point without taking into account other values of parameters from posterior distribution, which leads to a huge loss of useful information in posterior distribution. A better, yet computationally exhaustive method is using the MCMC (Markov chain Monte Carlo) sampling methods. Here we will use the NUTS sampler in this session. -""" - -# ╔═╡ 75efff36-8da7-4d04-afa2-a2f8324bc103 -md""" -In this notebook we will calibrate the different parameters involved in the grass growth models with either MLE, MAP or MCMC. To illustrate this concept, we first go through three simple models for grass growth yield. -""" - -# ╔═╡ 3dcb9c9d-370b-4031-b7c0-cee80742557a -md""" -## Grass growth models -""" - -# ╔═╡ 7a14aa59-6e6f-4266-a0b3-84ab55f2efc5 -md""" -In this notebook, three different models will be used, each modelling the yield of grass in a grassland: - -- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ -- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ -- Gompertz growth model: $\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$ - -with output $W$ the grass yield, and $W_f$, $\mu$ and $D$ parameters. The table below shows the parameter values and the initial condition that will be used as initial values for the optimization algorithm: - -| | $\mu$ | $W_f$ | $D$ | $W_0$ | -|:----------- |:----------:|:-----------:|:------------:|:------------:| -| Logistic | 0.07 | 10.0 | | 2.0 | -| Exponential | 0.02 | 10.0 | | 2.0 | -| Gompertz | 0.09 | | 0.040 | 2.0 | - -Hence, for each grass growth model, we will optimize the parameter values together with the initial value. -""" - -# ╔═╡ 85cd60a8-b448-4375-9b6d-399c4336c319 -md""" -In each of the three models we will use the following timespan: -""" - -# ╔═╡ 5b320989-3e0b-447b-bc9a-25fb221ce609 -tspan = (0.0, 100.0) # this will be the same for the three models - -# ╔═╡ 9a5bc72b-346d-4e95-a873-783037ed98bc -md""" -### Logistic growth model - -We will illustrate the calibration with the logistic growth model: -""" - -# ╔═╡ ba56adb1-9405-40d5-be48-4273b42ab145 -growth_log = @reaction_network begin - μ*(1-W/Wf), W --> 2W -end - -# ╔═╡ 6e3e53ea-4fe7-4a34-8b00-cbf8d63a3203 -osys_log = convert(ODESystem, growth_log) - -# ╔═╡ f4748167-b635-47a1-9015-32e1258c0afa -md""" -Check the order of the parameters: -""" - -# ╔═╡ a022b2ab-68a0-40ca-b914-7a2adcf4ae39 -parameters(growth_log) - -# ╔═╡ acccb2fa-12b2-4fc7-91e3-58a4b1a02892 -md""" -Next, we will need to create an `ODEProblem` in advance before we can optimize some of its parameters. We will provide the values in the aforementioned table as initial values for the problem. -""" - -# ╔═╡ e54ea8d1-0854-44fa-aed8-45d106e921e4 -u0_log = [:W => 2.0] - -# ╔═╡ 8d96eb17-ce19-4523-916f-3cd0441a16ca -params_log = [:μ => 0.07, :Wf => 10.0] - -# ╔═╡ 5c9db9df-0cbd-41ac-afe9-fb5616c967be -oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log) - -# ╔═╡ 1aa44f2b-6f33-437f-b9dd-89762d9f28ea -md""" -### The measurement data -""" - -# ╔═╡ b2b433ed-0266-4bea-a7e8-32adba542d4c -md""" -Assume that the measured grass yields (of a certain plant type) over time are the following: -""" - -# ╔═╡ 7c966a66-0091-4b81-9a7e-02ccd0d3db10 -W_meas = [1.87, 2.45, 3.72, 4.32, 5.28, 7.01, 6.83, 8.62, 9.45, 10.31, 10.56, 11.72, 11.05, 11.53, 11.39, 11.7, 11.15, 11.49, 12.04, 11.95, 11.68] - -# ╔═╡ 3edd2acc-a865-4675-afef-8868c68256f1 -md""" -They have been measured at the following corresponding time instances: -""" - -# ╔═╡ 877298e8-b61b-4c3a-ba2c-2827acdcfb50 -t_meas = 0:5:100 - -# ╔═╡ acb7b123-831f-47e9-92cc-2dd3e57f86fd -md""" -If you literally want to see the time values, you can use the `collect` function to create a dense vector: -""" - -# ╔═╡ bab8119a-7fe7-4c6b-abb9-5885a88b55ae -collect(t_meas) - -# ╔═╡ ef06cc43-510b-4ff9-b0b7-1c7fc267e9b1 -md""" -We can make a scatter plot of this data (including a title, a legend label, an X-axis label, X- and Y-axis limits) in the following way: -""" - -# ╔═╡ cb2bc6ee-4211-47e1-9956-5cf1b0c0671d -scatter(t_meas, W_meas, title="Grass growth data", - label="Yield", - xlabel="t", - xlims=(0, 100), - ylims=(0, 14)) - -# ╔═╡ ee32ecfe-519a-4603-bdd9-a1452593e31d -savefig("calibration.png") - -# ╔═╡ d75246d4-e03b-4684-be7d-4bcfb61ed7ef -md""" -### Declaration of the Turing model function -""" - -# ╔═╡ dd3a32f1-bdb6-44a3-acbe-f4269725c9e4 -md""" -In the Turing model function we will define our priors for the following magnitudes: -- the measurement error (standard deviation) $\sigma_W$, -- the initial condition $W_0$, and -- the parameters $\mu$ and $W_f$. - -We will thereby take an `InverseGamma` prior distribution for $\sigma_W$ and `LogNormal` prior distributions for the initial condition and the parameters. You can always plot the priors to have a look at them, e.g., with `plot(LogNormal())`. -""" - -# ╔═╡ 88dd0342-3678-40a2-a432-661e5410e5cf -plot(LogNormal()) # Log-normal with 0 log-mean and unit scale -# plot(LogNormal(log(1), 1.0)) # Log-normal with 0 log-mean and unit scale -# plot(LogNormal(log(10), 0.50)) # Log-normal with 1 log-mean and 0.5 scale - -# ╔═╡ 70ff9f12-e924-4daf-ab32-2394feccc953 -plot(InverseGamma()) # Inverse Gamma with shape 1 and scale 1 -# plot(InverseGamma(3, 1)) # Inverse Gamma with shape 3 and scale 1 -# plot(InverseGamma(5, 0.5)) # Inverse Gamma with shape 5 and scale 0.5 - -# ╔═╡ 0ccf1f1d-20be-4c19-bfc6-2748970547a1 -md""" -Here is our Turing model function: -""" - -# ╔═╡ 6d9d7792-fffa-482d-b785-33482bf2b0ea -@model function growth_log_fun(t_meas) - σ_W ~ InverseGamma() - W0 ~ LogNormal() - μ ~ LogNormal() - Wf ~ LogNormal() - u0_log = [:W => W0] - params_log = [:μ => μ, :Wf => Wf] - oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log) - osol_log = solve(oprob_log, Tsit5(), saveat=t_meas) - W_s ~ MvNormal(osol_log[:W], σ_W^2 * I) - return osol_log # optionally, to be used with MCMC -end - -# ╔═╡ 48c9f616-d298-40da-b917-225abd39b3d9 -md""" -!!! note "Some remarks" - - The time points are the ones from the measurements, therefore, we set: `saveat=t_meas`. - - We need to solve the ODE problem inside the Turing model function with values for $W_0$, $\mu$ and $W_f$ sampled from the distributions. Therefore, we need to remake our ODE problem with the appropriate initial and parameter values and solve it. - - We will consider our solution vector for $W$ as being multivariate normally distributed with mean (vector) the actual solution for $W$ and covariance (matrix) a diagonal matrix with variances $\sigma_W^2$. The function `MvNormal(μ, Σ)` will construct a *multivariate normal distribution* with mean vector μ and covariance matrix Σ. - - Consider the following (small) example. The mean vector here is `[1.7, 4.5, 3.6]` and the variance on the diagonal are `0.5^2 * I` (`I` is the identity matrix). When you sample from it, you get a vector of values with mean `[1.7, 4.5, 3.6]` and each value with variance `0.5^2`. -""" - -# ╔═╡ 4987f361-fcce-4453-8872-11d6faf16e86 -rand(Uniform(0, 20)) - -# ╔═╡ 3d3db44c-db49-436a-9b6e-9f6dd5d6178b -rand(MvNormal([1.7, 4.5, 3.6], 0.5^2 * I)) - -# ╔═╡ 35f158c1-858d-4e4d-ac3d-bf4807dad9a0 -md""" -We now provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: -""" - -# ╔═╡ 98a71080-e662-4f1a-88fe-3c575386ec77 -growth_log_cond_mod = growth_log_fun(t_meas) | (W_s = W_meas,) - -# ╔═╡ a6972aef-63ad-401c-acf5-6d59f9fc6698 -md""" -We are now ready to optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). This is done by calling the `optimize` function, providing the previously created object `growth_log_inf`, the method for estimating the parameters and (optionally) an algorithm (default: Nelder-Mead) to implement the method. -""" - -# ╔═╡ 73e35289-6dc0-4e2e-83eb-b56f83cdbbbf -md""" -### Method - Maximum Likelihood Estimation -""" - -# ╔═╡ 47bd729c-4851-42f7-a03f-6ceacd3c717e -md""" -We will use the MLE (Maximum Likelihood Estimation) method here and store the optimization results in `results_log_mle`. If you get an error the first time, try running the optimization again. -""" - -# ╔═╡ dff0f8c6-1fed-4ef7-a6d3-2c8f345ed724 -results_log_mle = optimize(growth_log_cond_mod, MLE(), NelderMead()) - -# ╔═╡ e55404ab-6762-4f39-bb42-9c195334a214 -md""" -You can visualize a summary of the optimized parameters by piping them to `coeftable`: -""" - -# ╔═╡ 75ee0ba9-c9f9-496e-bb80-d013328c3ad7 -coeftable(results_log_mle) - -# ╔═╡ a1ca7d0e-639c-42d4-be09-5c61a2008f29 -md""" -You can obtain the actual optimized values using the function `coef` on the results object in conjunction by calling the parameters by name preceded by a colon. Here we assign the optimized parameter values to some suitable variable names: -""" - -# ╔═╡ 30399b9a-1d77-4140-9ad3-5eed636a5b99 -W0_opt1_log = coef(results_log_mle)[:W0] - -# ╔═╡ e1b8e4ba-c1f1-48d5-87a2-edce19c9fe7a -μ_opt1_log = coef(results_log_mle)[:μ] - -# ╔═╡ a943e0fe-1376-4ef1-9c45-25c6f95e3b96 -Wf_opt1_log = coef(results_log_mle)[:Wf] - -# ╔═╡ 72e065d4-7b1b-4f46-b373-935be8d801fc -md""" -Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ 0b2ffd6f-01cd-4f11-9062-d38b3c13a5b1 -md""" -Setting up initial condition with optimized initial condition: -""" - -# ╔═╡ 590b1006-0e37-4668-9b4a-3588fab45696 -u0_opt1_log = [:W => W0_opt1_log] - -# ╔═╡ 8ff28a2e-185d-4dca-ad3b-a0b1507646d6 -md""" -Setting up parameter values with optimized parameter values: -""" - -# ╔═╡ 14d24cbd-3259-41bb-9013-b4fe25a3be4c -params_opt1_log = [:μ => μ_opt1_log, :Wf => Wf_opt1_log] - -# ╔═╡ 6c134677-3ec1-4e0a-88b5-01341a096675 -md""" -Next, we create an ODEProblem and solve it: -""" - -# ╔═╡ 7a81f4a0-8f7c-4e05-9c3f-2438eab9b691 -oprob_opt1_log = ODEProblem(growth_log, u0_opt1_log, tspan, params_opt1_log) - -# ╔═╡ ac80099b-d8f0-4eba-809d-d482bd354d35 -osol_opt1_log = solve(oprob_opt1_log, Tsit5(), saveat=0.5) - -# ╔═╡ b58f2c24-e0ea-48a8-b0b7-d0faf9642340 -md""" -Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ 55eba435-6ca4-4f4f-b08a-be700d5bda91 -begin - plot(osol_opt1_log, label="Logistic growth", xlabel="t", - xlims=(0, 100), ylims=(0, 14), lw=2.0, title="MLE") - scatter!(t_meas, W_meas, label="Yield") -end - -# ╔═╡ 5d386b00-93b5-4a88-b4e3-e5c3eebd6dd5 -md""" -### Method - Maximum A Posterior -""" - -# ╔═╡ 6f0e91d0-6b99-4cf1-8145-523589a21e89 -md""" -We will use the MAP (Maximum A Posterior) method here and store the optimization results in `results_log_map`. Try running the optimization once again if you get an error. -""" - -# ╔═╡ 209742ca-36bb-42a5-bf8a-291a40c47757 -results_log_map = optimize(growth_log_cond_mod, MAP(), NelderMead()) - -# ╔═╡ 07efb21a-0dac-46d5-b9e1-8f4757c6cedf -md""" -You can visualize a summary of the optimized parameters by piping them to `coeftable`: -""" - -# ╔═╡ a93a7eeb-aad1-49f3-a05f-1512adb08b6b -coeftable(results_log_map) - -# ╔═╡ d408be0d-2522-4a4e-8a36-33ab42ed68a0 -md""" -You can compare the optimized values by both methods now and find that results are quite similar: -""" - -# ╔═╡ c1737a69-dc3a-4e08-90cc-9dc499dc4e93 -coeftable(results_log_mle) - -# ╔═╡ ccc66805-efce-4f25-b76b-7cff23901ba4 -md""" -Next, you can obtain the actual optimized values using the function `coef` on the results object in conjunction by calling the parameters by name preceded by a colon. Here we assign the optimized parameter values to some suitable variable names: -""" - -# ╔═╡ 57ef7d03-a6dc-40da-858d-5f9b9be613cd -W0_opt2_log = coef(results_log_map)[:W0] - -# ╔═╡ 62bd2e58-343a-4155-a65e-cf326d0975f4 -μ_opt2_log = coef(results_log_map)[:μ] - -# ╔═╡ 4d6cba88-be68-412d-aeec-28300c26a9da -Wf_opt2_log = coef(results_log_map)[:Wf] - -# ╔═╡ 4bc97af7-debc-4170-a2d5-4e6c928e5183 -md""" -Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ a0033986-4946-45a2-a53e-29c38f35ed5c -md""" -Setting up initial condition with optimized initial condition: -""" - -# ╔═╡ a570ebab-64de-4934-b887-77a8e2fb42e8 -u0_opt2_log = [:W => W0_opt2_log] - -# ╔═╡ feee9cc1-ebe2-4c77-a11e-53ca00864ccf -md""" -Setting up parameter values with optimized parameter values: -""" - -# ╔═╡ 73e86176-f314-463c-8821-51c0f3cc1a56 -params_opt2_log = [:μ => μ_opt2_log, :Wf => Wf_opt2_log] - -# ╔═╡ c17c4e00-8688-4f9c-b0ae-741d70601aba -md""" -Next, we create an ODEProblem and solve it: -""" - -# ╔═╡ f751b962-1810-429c-b34b-658b63fa9ba0 -oprob_opt2_log = ODEProblem(growth_log, u0_opt2_log, tspan, params_opt2_log) - -# ╔═╡ 0fd058cc-9bd8-4e12-b5dd-79818519165b -osol_opt2_log = solve(oprob_opt2_log, Tsit5(), saveat=0.5) - -# ╔═╡ 57c95382-69cc-47e7-aa94-4767136bb23b -md""" -Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ 4e07c39b-502d-4b38-ae7f-f103cb4bae16 -begin - plot(osol_opt2_log, label="Logistic growth", xlabel="t", - xlims=(0, 100), ylims=(0, 14), lw=2.0, title="MAP") - scatter!(t_meas, W_meas, label="Yield") -end - -# ╔═╡ 29170e2a-9916-438e-92ca-9f4783397b5e -md""" -### Method - MCMC with NUTS -""" - -# ╔═╡ 7ff9fe52-156b-4a92-9058-781670de3abb -md""" -We will use Markov chain Monte Carlo (MCMC) method in combination with the No U-Turn Sampler (NUTS) here and store the optimization results in `results_log_nuts`. -""" - -# ╔═╡ 0c047043-3284-422a-9c88-2f4f4c170edf -results_log_nuts = sample(growth_log_cond_mod, NUTS(), 1000) - -# ╔═╡ 3d7b518d-82f2-4894-a964-0b9c76f0f3e6 -md""" -You can plot the sampled chain results to verify the form of the pdf for the estimated parameters: -""" - -# ╔═╡ 866402ca-47ce-4a24-a963-34cd15f8483b -plot(results_log_nuts) - -# ╔═╡ 19c362cb-2764-41c9-a571-2e8e2bfcde93 -summarize(results_log_nuts) - -# ╔═╡ 93db47b2-34e8-43b4-beac-b5620fd444e7 -# Get element at row 'W0', column 'mean' in the summary table: -W0_opt3_log = summarize(results_log_nuts)[:W0, :mean] -# Alternative: -# W0_opt3_log = mean(results_log_nuts[:W0]) - -# ╔═╡ 68c71cd2-6cdc-4b80-b6e7-75bfea344295 -# Get element at row 'μ', column 'mean' in the summary table: -μ_opt3_log = summarize(results_log_nuts)[:μ ,:mean] -# Alternative: -# μ_opt3_log = mean(results_log_nuts[:μ]) - -# ╔═╡ d6b9eaba-1d43-4e56-8fa1-bb2f87f6fe79 -# Get element at row 'Wf', column 'mean' in the summary table: -Wf_opt3_log = summarize(results_log_nuts)[:Wf ,:mean] -# Alternative: -# Wf_opt3_log = mean(results_log_nuts[:Wf]) - -# ╔═╡ b843ee5e-9618-4b50-93db-77e19b4be366 -md""" -Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ 58014411-128c-41f4-b192-b815a3a8cc60 -md""" -Setting up initial condition with optimized initial condition: -""" - -# ╔═╡ ecf4b951-9b5f-441a-89f5-b0ccd040ba02 -u0_opt3_log = [:W => W0_opt3_log] - -# ╔═╡ b92bf532-5610-4ce8-b469-154b20cc5956 -md""" -Setting up parameter values with optimized parameter values: -""" - -# ╔═╡ e629fbd1-a7c7-4dc3-8b7d-bdca380fe5ad -params_opt3_log = [:μ => μ_opt3_log, :Wf => Wf_opt3_log] - -# ╔═╡ a48df980-4847-418e-813d-1875d032ffd9 -md""" -Next, we create an ODEProblem and solve it: -""" - -# ╔═╡ 348e7454-c6dc-4d1e-b18b-6b34bc2fc0fc -oprob_opt3_log = ODEProblem(growth_log, u0_opt3_log, tspan, params_opt3_log) - -# ╔═╡ f71f0cf4-b63e-452c-b971-f30d7503f1e5 -osol_opt3_log = solve(oprob_opt3_log, Tsit5(), saveat=0.5) - -# ╔═╡ 8a382a8e-e433-4b17-9649-aac81ebc20f4 -md""" -Optionally, you can get 200 sampled solutions from the posterior parameter distributions in the following way. We will plot these together with the solution based on the mean (optimized) values for the parameters. -""" - -# ╔═╡ ba62d7fe-e30e-4e7b-a9c1-0ddd39abc42c -osol_log_sampled = generated_quantities(growth_log_cond_mod, results_log_nuts[1:5:1000]); - -# ╔═╡ 7346a003-aa05-41e3-a420-c97c1994cf3c -md""" -Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ 20a6f165-449a-42cc-9565-f342a7535422 -begin - h = plot(title="Fit + posterior") # Make empty plot and return handle - # We first plot the 200 sampled solutions (this is optional!) - for i in eachindex(osol_log_sampled) - plot!(h, osol_log_sampled[i], color=:skyblue, alpha=0.2, label=false) - end - # Now we plot the solution based on the mean (optimized) values - plot!(h, osol_opt3_log, label="Logistic growth", xlabel="t", - xlims=(0, 100), ylims=(0, 14), lw=1.5, color=:black) - # Finally, we add the measured values - scatter!(h, t_meas, W_meas, label="Yield") -end - -# ╔═╡ 137bde23-76f2-4ebf-8bc2-ea8640001436 -md""" -## Exercises -""" - -# ╔═╡ 4aa71200-006b-4a15-ae75-67e36aa81522 -md""" -### Exercise 1 - Calibration of the exponential growth model - -Calibrate the initial condition and both parameters of the exponential growth model. Use the values mentioned in the *Table* as initials values for the optimization of the parameters. -""" - -# ╔═╡ cdab3079-04b0-4a44-b770-468c20e321e4 -md""" -We have seen before that a possible *reaction network object* for the exponential growth model can be implemented as follows: -""" - -# ╔═╡ cf1a144e-09e9-42a3-b2a3-b8676a200a39 -growth_exp = @reaction_network begin - μ*Wf, 0 --> W - μ, W --> 0 -end - -# ╔═╡ 85c57cd3-bf00-437e-937b-f0c3b62f74ff -parameters(growth_exp) - -# ╔═╡ c6d373f4-f13c-4135-823d-ee8fbeb71b56 -md""" -Create an `ODEProblem`. Use the values in the aforementioned table as initial values for the problem. Use the same `tspan` as before. -""" - -# ╔═╡ a97abaa7-b642-4201-86f1-5c8995b07536 -# u0_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 387730b4-bd06-492f-94e6-231bd68b3436 -# params_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 290a7fe8-3b1e-423f-8b30-9bd8903d2e8f -# oprob_exp = missing # Uncomment and complete the instruction - -# ╔═╡ febe2b67-2a8f-4575-946d-30877bd5f2d4 -md""" -Use the same measurement data (`W_meas`, `t_meas`) as before. -""" - -# ╔═╡ b4300e8a-8052-419b-98c8-0508ebee2393 -md""" -Declare the Turing model function. -""" - -# ╔═╡ 2c6ae74c-2da4-4867-ad8a-f4e835101d63 -# Uncomment and complete the instruction -# @model function growth_exp_fun(t_meas) -# σ_W ~ missing -# W0 ~ missing -# μ ~ missing -# Wf ~ missing -# u0_exp = missing -# params_exp = missing -# oprob_exp = missing -# osol_exp = missing -# W_s ~ missing -# end - -# ╔═╡ c6a5d453-d610-4f65-847c-c878dd41726c -md""" -Provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: -""" - -# ╔═╡ fff17cf7-173d-4f64-94a9-4bf46acc882d -# growth_exp_cond_mod = missing # Uncomment and complete the instruction - -# ╔═╡ eee55784-a641-445e-be75-0b19e2a94754 -md""" -Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). Do this with both the `MLE` and `MAP` methods and the Nelder-Mead algorithm. Store the optimization results in `results_exp_mle` and `results_exp_map`. -""" - -# ╔═╡ 7844e4f5-3c7d-4b4b-beee-970c998c67a6 -# results_exp_mle = missing # Uncomment and complete the instruction - -# ╔═╡ a7fc5f89-0341-4de2-962b-935c6d2f1f9d -# results_exp_map = missing - -# ╔═╡ c81d0140-3f4e-4eb4-8a77-1f48c5e0ecbf -md""" -Visualize a summary of the MLE and MAP optimized parameters. -""" - -# ╔═╡ 7456455b-4f31-488f-990f-6ce534038e08 -# missing # For MLE - -# ╔═╡ 4b057098-4dd8-438a-8699-f0f792f70370 -# missing # For MAP - -# ╔═╡ b10c2ce4-d363-429c-a64c-ec29652137a5 -md""" -Get the MLE optimized values and assign them to `W0_opt_mle_exp`, `μ_opt_mle_exp` and `Wf_opt_mle_exp`. -""" - -# ╔═╡ 23b629a6-6866-416a-a768-9617ce6301db -# W0_opt_mle_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 8788082d-f5d1-4385-8037-a0d360a841c7 -# μ_opt_mle_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 03a4fa85-08db-46d4-bb53-c0ccea90a211 -# Wf_opt_mle_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 1c86db02-4135-4696-8584-00281cc680ab -md""" -Do the same for the MAP optimized values. -""" - -# ╔═╡ eadede4f-7be7-4d58-8809-fee113efc192 -# begin -# W0_opt_map_exp = missing -# μ_opt_map_exp = missing -# Wf_opt_map_exp = missing -# end; - -# ╔═╡ 8f5e1413-227c-44fa-bb2d-3653cbc27e38 -md""" -Make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ 8a7f7aab-878e-41b5-b9da-d06747df042e -md" -Set up initial condition with the MLE optimized initial condition: -" - -# ╔═╡ 30602ff1-041b-4fca-bf8e-55ff57df9e37 -# u0_opt_mle_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 881011be-6434-416f-915b-3333e8dea32f -md""" -Set up parameter values with the MLE optimized parameter values: -""" - -# ╔═╡ 5602b88a-07f8-438b-994c-65f11e17a0ba -# params_opt_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 96c03efb-4424-4e43-9f50-2d48cb0b7caa -md""" -Do the same for the MAP optimized values. -""" - -# ╔═╡ 8e2a0d2d-bb0a-4e59-ab3e-ccf8a4bd180f -# begin -# u0_opt_map_exp = missing -# params_opt_map_exp = missing -# end; - -# ╔═╡ 25be4255-0888-4ecd-a2fd-d66402c5cb50 -md""" -Create an ODEProblem and solve it. Solve it using `Tsit5()` and `saveat=0.5`. -""" - -# ╔═╡ 36e8a174-d526-45ee-b3c6-88d698ad5d5f -# oprob_opt_mle_exp = missing; # Uncomment and complete the instruction - -# ╔═╡ cb343602-7cf8-4025-8be0-1c63ebee488b -# oprob_opt_map_exp = missing; - -# ╔═╡ 7594147d-b3da-4e0d-896d-41baacb6d7be -# osol_opt_mle_exp = missing; # Uncomment and complete the instruction - -# ╔═╡ 28d04c4c-82ba-4166-90c7-c676e9c7c483 -# osol_opt_map_exp = missing; - -# ╔═╡ 8e047be9-f0f0-4a75-91b5-c523f55f8c67 -md""" -Plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ 1a9587aa-2356-48df-abcc-2ce874fa5d24 -# Uncomment and complete the instruction -# begin -# missing -# missing -# missing -# missing -# end - -# ╔═╡ 785d500b-f8ea-446a-9952-2a5fd5d83d24 -md""" -### Exercise 2 - Calibration of the Gompertz growth model - -Calibrate the initial condition and both parameters of the Gompertz growth model. Use the values mentioned in the *Table* as initials values for the optimization of the parameters. -""" - -# ╔═╡ e754826a-7411-4072-b0dc-a4bad7a15f98 -md""" -We have seen before that a possible *reaction network object* for the Gompertz growth model can be implemented as follows: -""" - -# ╔═╡ bc1edcbe-46eb-4531-9c5f-dee8d5dc2ff9 -growth_gom = @reaction_network begin - μ-D*log(W), W --> 2W -end - -# ╔═╡ 47fb9e4c-df6a-4811-9980-99d595a34908 -md""" -Create an `ODEProblem`. Use the values in the aforementioned table as initial values for the problem. Use the same `tspan` as before. -""" - -# ╔═╡ bbd150de-ff9a-4127-a0dd-2f9762f92b07 -# u0_gom = missing # Uncomment and complete the instruction - -# ╔═╡ da5a0cbb-b033-46f1-a300-3954de138835 -# params_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 73da8f53-c3af-43b0-9b23-60471f1e3587 -# oprob_gom = missing # Uncomment and complete the instruction - -# ╔═╡ b0e67564-efe8-4fb2-bcf2-a711b770244e -md""" -Use the same measurement data (`W_meas`, `t_meas`) as before. -""" - -# ╔═╡ e5081280-d226-4834-8932-c89becd8313c -md""" -Declare the Turing model. Take the same priors as before. -""" -# Take for $\sigma_W$ and $W_0$ the same priors (and distributions) as before, but take for $\mu$ a Uniform prior distribution in the range $[0, 2]$ and the same for $D$ but in the range $[0, 1]$. - -# ╔═╡ c739a908-2353-4e7a-8fbd-f640dc8cabe0 -# Uncomment and complete the instruction -# @model function growth_gom_fun(t_meas) -# σ_W ~ missing -# W0 ~ missing -# μ ~ missing -# D ~ missing -# u0_gom = missing -# params_gom = missing -# oprob_gom = missing -# osol_gom = missing -# W_s ~ missing -# end - -# ╔═╡ 1d0383ad-54d6-4ff2-8555-def83bfff0e6 -md""" -Provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: -""" - -# ╔═╡ cd1cf2f8-9f7f-4ed4-9cb7-1a6efee68ab4 -# growth_gom_cond_mod = missing # Uncomment and complete the instruction - -# ╔═╡ aba74ee0-0163-4e15-8b49-d8dcad4839f7 -md""" -Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $D$). Do this now with `MAP` method and Nelder-Mead. Store the optimization results in `results_gom_map`. -""" - -# ╔═╡ 0eda4142-1aaf-4e17-bd78-857e13e94acd -# results_gom_map = missing # Uncomment and complete the instruction - -# ╔═╡ 50629194-98ed-4d45-86a2-95ac22daac29 -md""" -Visualize a summary of the optimized parameters. -""" - -# ╔═╡ 9c239fc6-275c-4d64-9fa2-6fd57295b757 -# missing # Uncomment and complete the instruction - -# ╔═╡ dede17f2-655d-4871-b6de-5a32804947dd -md""" -Get the optimized values and assign them to `W0_opt_gom`, `μ_opt_gom` and `D_opt_gom`. -""" - -# ╔═╡ e8c3f042-6058-4040-a67e-18563c04ee93 -# W0_opt_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 665f4d03-3521-475a-a195-f861fd26bb69 -# μ_opt_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 13775d58-ac61-431c-a6c9-447c1eec7942 -# D_opt_gom = missing # Uncomment and complete the instruction - -# ╔═╡ bc92f996-b626-4965-a286-d2c848eb1a21 -md""" -Make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ e2fde8e9-1f87-4ffe-8dae-2794664bfaa4 -md""" -Set up initial condition with optimized initial condition: -""" - -# ╔═╡ 23fd9fd9-a13c-4c56-a0b4-daec6f1d2cd8 -# u0_opt_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 6f414d15-af0a-452a-98a1-dc0b7e54d617 -md""" -Set up parameter values with optimized parameter values: -""" - -# ╔═╡ 57ee8a12-24df-4598-935c-f5e259b504cb -# params_opt_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 48bc085c-9ce6-4752-a5a9-a814f803f571 -md""" -Create an ODEProblem and solve it. Use the solver `Tsit5()` and `saveat=0.5`. -""" - -# ╔═╡ 5b9b2e5b-9deb-4ff6-a923-b15b8b08f0c9 -# oprob_opt_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 66ee9655-a006-4c0f-b1f1-5576efa8f896 -# osol_opt_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 52d7975a-5346-447f-9aad-4ecd11b6460a -md""" -Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ e96efd6a-a666-4120-8480-9423e5d82ae1 -# Uncomment and complete the instruction -# begin -# missing -# missing -# end - -# ╔═╡ f0b4772d-a72b-44e0-a3a1-ba9ad4c4dfeb -md""" -!!! question - Which grass growth model fits best these data? How can you prove this numerically? -""" - -# ╔═╡ 7e98a771-52bb-484e-82ab-2e42e7cb4053 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═a09f814a-0c6a-11ef-0e79-a50b01287d63 -# ╠═7f521435-63ac-4178-a4aa-93d9c45fe820 -# ╠═f8a92690-990b-4341-89e1-322adbcb8d1b -# ╠═015050b3-3339-4b1a-ad7d-c358cce73675 -# ╠═dbfe4800-0974-4ca1-bb0a-d8803409a98b -# ╠═6e227e07-166a-41ce-839a-4c4c72addb23 -# ╠═b992c080-a0ce-4188-b632-e734a141e67d -# ╟─37da8786-fea0-4c2f-a76f-6e6c68325a78 -# ╟─4623369d-8c5a-422d-9e40-0f1dd7586260 -# ╟─3cb0a166-ac53-4c3f-9832-e93742040cfb -# ╟─987f0a4d-e416-4ceb-adbe-3dcdca9d0996 -# ╟─75efff36-8da7-4d04-afa2-a2f8324bc103 -# ╟─3dcb9c9d-370b-4031-b7c0-cee80742557a -# ╟─7a14aa59-6e6f-4266-a0b3-84ab55f2efc5 -# ╟─85cd60a8-b448-4375-9b6d-399c4336c319 -# ╠═5b320989-3e0b-447b-bc9a-25fb221ce609 -# ╟─9a5bc72b-346d-4e95-a873-783037ed98bc -# ╠═ba56adb1-9405-40d5-be48-4273b42ab145 -# ╠═6e3e53ea-4fe7-4a34-8b00-cbf8d63a3203 -# ╟─f4748167-b635-47a1-9015-32e1258c0afa -# ╠═a022b2ab-68a0-40ca-b914-7a2adcf4ae39 -# ╟─acccb2fa-12b2-4fc7-91e3-58a4b1a02892 -# ╠═e54ea8d1-0854-44fa-aed8-45d106e921e4 -# ╠═8d96eb17-ce19-4523-916f-3cd0441a16ca -# ╠═5c9db9df-0cbd-41ac-afe9-fb5616c967be -# ╟─1aa44f2b-6f33-437f-b9dd-89762d9f28ea -# ╟─b2b433ed-0266-4bea-a7e8-32adba542d4c -# ╠═7c966a66-0091-4b81-9a7e-02ccd0d3db10 -# ╟─3edd2acc-a865-4675-afef-8868c68256f1 -# ╠═877298e8-b61b-4c3a-ba2c-2827acdcfb50 -# ╟─acb7b123-831f-47e9-92cc-2dd3e57f86fd -# ╠═bab8119a-7fe7-4c6b-abb9-5885a88b55ae -# ╟─ef06cc43-510b-4ff9-b0b7-1c7fc267e9b1 -# ╠═cb2bc6ee-4211-47e1-9956-5cf1b0c0671d -# ╠═ee32ecfe-519a-4603-bdd9-a1452593e31d -# ╟─d75246d4-e03b-4684-be7d-4bcfb61ed7ef -# ╟─dd3a32f1-bdb6-44a3-acbe-f4269725c9e4 -# ╠═88dd0342-3678-40a2-a432-661e5410e5cf -# ╠═70ff9f12-e924-4daf-ab32-2394feccc953 -# ╟─0ccf1f1d-20be-4c19-bfc6-2748970547a1 -# ╠═6d9d7792-fffa-482d-b785-33482bf2b0ea -# ╟─48c9f616-d298-40da-b917-225abd39b3d9 -# ╠═4987f361-fcce-4453-8872-11d6faf16e86 -# ╠═3d3db44c-db49-436a-9b6e-9f6dd5d6178b -# ╟─35f158c1-858d-4e4d-ac3d-bf4807dad9a0 -# ╠═98a71080-e662-4f1a-88fe-3c575386ec77 -# ╟─a6972aef-63ad-401c-acf5-6d59f9fc6698 -# ╟─73e35289-6dc0-4e2e-83eb-b56f83cdbbbf -# ╟─47bd729c-4851-42f7-a03f-6ceacd3c717e -# ╠═dff0f8c6-1fed-4ef7-a6d3-2c8f345ed724 -# ╟─e55404ab-6762-4f39-bb42-9c195334a214 -# ╠═75ee0ba9-c9f9-496e-bb80-d013328c3ad7 -# ╟─a1ca7d0e-639c-42d4-be09-5c61a2008f29 -# ╠═30399b9a-1d77-4140-9ad3-5eed636a5b99 -# ╠═e1b8e4ba-c1f1-48d5-87a2-edce19c9fe7a -# ╠═a943e0fe-1376-4ef1-9c45-25c6f95e3b96 -# ╟─72e065d4-7b1b-4f46-b373-935be8d801fc -# ╟─0b2ffd6f-01cd-4f11-9062-d38b3c13a5b1 -# ╠═590b1006-0e37-4668-9b4a-3588fab45696 -# ╟─8ff28a2e-185d-4dca-ad3b-a0b1507646d6 -# ╠═14d24cbd-3259-41bb-9013-b4fe25a3be4c -# ╟─6c134677-3ec1-4e0a-88b5-01341a096675 -# ╠═7a81f4a0-8f7c-4e05-9c3f-2438eab9b691 -# ╠═ac80099b-d8f0-4eba-809d-d482bd354d35 -# ╟─b58f2c24-e0ea-48a8-b0b7-d0faf9642340 -# ╠═55eba435-6ca4-4f4f-b08a-be700d5bda91 -# ╟─5d386b00-93b5-4a88-b4e3-e5c3eebd6dd5 -# ╟─6f0e91d0-6b99-4cf1-8145-523589a21e89 -# ╠═209742ca-36bb-42a5-bf8a-291a40c47757 -# ╟─07efb21a-0dac-46d5-b9e1-8f4757c6cedf -# ╠═a93a7eeb-aad1-49f3-a05f-1512adb08b6b -# ╟─d408be0d-2522-4a4e-8a36-33ab42ed68a0 -# ╠═c1737a69-dc3a-4e08-90cc-9dc499dc4e93 -# ╟─ccc66805-efce-4f25-b76b-7cff23901ba4 -# ╠═57ef7d03-a6dc-40da-858d-5f9b9be613cd -# ╠═62bd2e58-343a-4155-a65e-cf326d0975f4 -# ╠═4d6cba88-be68-412d-aeec-28300c26a9da -# ╟─4bc97af7-debc-4170-a2d5-4e6c928e5183 -# ╟─a0033986-4946-45a2-a53e-29c38f35ed5c -# ╠═a570ebab-64de-4934-b887-77a8e2fb42e8 -# ╟─feee9cc1-ebe2-4c77-a11e-53ca00864ccf -# ╠═73e86176-f314-463c-8821-51c0f3cc1a56 -# ╟─c17c4e00-8688-4f9c-b0ae-741d70601aba -# ╠═f751b962-1810-429c-b34b-658b63fa9ba0 -# ╠═0fd058cc-9bd8-4e12-b5dd-79818519165b -# ╟─57c95382-69cc-47e7-aa94-4767136bb23b -# ╠═4e07c39b-502d-4b38-ae7f-f103cb4bae16 -# ╟─29170e2a-9916-438e-92ca-9f4783397b5e -# ╟─7ff9fe52-156b-4a92-9058-781670de3abb -# ╠═0c047043-3284-422a-9c88-2f4f4c170edf -# ╟─3d7b518d-82f2-4894-a964-0b9c76f0f3e6 -# ╠═866402ca-47ce-4a24-a963-34cd15f8483b -# ╠═19c362cb-2764-41c9-a571-2e8e2bfcde93 -# ╠═93db47b2-34e8-43b4-beac-b5620fd444e7 -# ╠═68c71cd2-6cdc-4b80-b6e7-75bfea344295 -# ╠═d6b9eaba-1d43-4e56-8fa1-bb2f87f6fe79 -# ╟─b843ee5e-9618-4b50-93db-77e19b4be366 -# ╟─58014411-128c-41f4-b192-b815a3a8cc60 -# ╠═ecf4b951-9b5f-441a-89f5-b0ccd040ba02 -# ╟─b92bf532-5610-4ce8-b469-154b20cc5956 -# ╠═e629fbd1-a7c7-4dc3-8b7d-bdca380fe5ad -# ╟─a48df980-4847-418e-813d-1875d032ffd9 -# ╠═348e7454-c6dc-4d1e-b18b-6b34bc2fc0fc -# ╠═f71f0cf4-b63e-452c-b971-f30d7503f1e5 -# ╟─8a382a8e-e433-4b17-9649-aac81ebc20f4 -# ╠═ba62d7fe-e30e-4e7b-a9c1-0ddd39abc42c -# ╟─7346a003-aa05-41e3-a420-c97c1994cf3c -# ╠═20a6f165-449a-42cc-9565-f342a7535422 -# ╟─137bde23-76f2-4ebf-8bc2-ea8640001436 -# ╟─4aa71200-006b-4a15-ae75-67e36aa81522 -# ╟─cdab3079-04b0-4a44-b770-468c20e321e4 -# ╠═cf1a144e-09e9-42a3-b2a3-b8676a200a39 -# ╠═85c57cd3-bf00-437e-937b-f0c3b62f74ff -# ╟─c6d373f4-f13c-4135-823d-ee8fbeb71b56 -# ╠═a97abaa7-b642-4201-86f1-5c8995b07536 -# ╠═387730b4-bd06-492f-94e6-231bd68b3436 -# ╠═290a7fe8-3b1e-423f-8b30-9bd8903d2e8f -# ╟─febe2b67-2a8f-4575-946d-30877bd5f2d4 -# ╟─b4300e8a-8052-419b-98c8-0508ebee2393 -# ╠═2c6ae74c-2da4-4867-ad8a-f4e835101d63 -# ╟─c6a5d453-d610-4f65-847c-c878dd41726c -# ╠═fff17cf7-173d-4f64-94a9-4bf46acc882d -# ╟─eee55784-a641-445e-be75-0b19e2a94754 -# ╠═7844e4f5-3c7d-4b4b-beee-970c998c67a6 -# ╠═a7fc5f89-0341-4de2-962b-935c6d2f1f9d -# ╟─c81d0140-3f4e-4eb4-8a77-1f48c5e0ecbf -# ╠═7456455b-4f31-488f-990f-6ce534038e08 -# ╠═4b057098-4dd8-438a-8699-f0f792f70370 -# ╟─b10c2ce4-d363-429c-a64c-ec29652137a5 -# ╠═23b629a6-6866-416a-a768-9617ce6301db -# ╠═8788082d-f5d1-4385-8037-a0d360a841c7 -# ╠═03a4fa85-08db-46d4-bb53-c0ccea90a211 -# ╟─1c86db02-4135-4696-8584-00281cc680ab -# ╠═eadede4f-7be7-4d58-8809-fee113efc192 -# ╟─8f5e1413-227c-44fa-bb2d-3653cbc27e38 -# ╟─8a7f7aab-878e-41b5-b9da-d06747df042e -# ╠═30602ff1-041b-4fca-bf8e-55ff57df9e37 -# ╟─881011be-6434-416f-915b-3333e8dea32f -# ╠═5602b88a-07f8-438b-994c-65f11e17a0ba -# ╟─96c03efb-4424-4e43-9f50-2d48cb0b7caa -# ╠═8e2a0d2d-bb0a-4e59-ab3e-ccf8a4bd180f -# ╟─25be4255-0888-4ecd-a2fd-d66402c5cb50 -# ╠═36e8a174-d526-45ee-b3c6-88d698ad5d5f -# ╠═cb343602-7cf8-4025-8be0-1c63ebee488b -# ╠═7594147d-b3da-4e0d-896d-41baacb6d7be -# ╠═28d04c4c-82ba-4166-90c7-c676e9c7c483 -# ╟─8e047be9-f0f0-4a75-91b5-c523f55f8c67 -# ╠═1a9587aa-2356-48df-abcc-2ce874fa5d24 -# ╟─785d500b-f8ea-446a-9952-2a5fd5d83d24 -# ╟─e754826a-7411-4072-b0dc-a4bad7a15f98 -# ╠═bc1edcbe-46eb-4531-9c5f-dee8d5dc2ff9 -# ╟─47fb9e4c-df6a-4811-9980-99d595a34908 -# ╠═bbd150de-ff9a-4127-a0dd-2f9762f92b07 -# ╠═da5a0cbb-b033-46f1-a300-3954de138835 -# ╠═73da8f53-c3af-43b0-9b23-60471f1e3587 -# ╟─b0e67564-efe8-4fb2-bcf2-a711b770244e -# ╟─e5081280-d226-4834-8932-c89becd8313c -# ╠═c739a908-2353-4e7a-8fbd-f640dc8cabe0 -# ╟─1d0383ad-54d6-4ff2-8555-def83bfff0e6 -# ╠═cd1cf2f8-9f7f-4ed4-9cb7-1a6efee68ab4 -# ╟─aba74ee0-0163-4e15-8b49-d8dcad4839f7 -# ╠═0eda4142-1aaf-4e17-bd78-857e13e94acd -# ╟─50629194-98ed-4d45-86a2-95ac22daac29 -# ╠═9c239fc6-275c-4d64-9fa2-6fd57295b757 -# ╟─dede17f2-655d-4871-b6de-5a32804947dd -# ╠═e8c3f042-6058-4040-a67e-18563c04ee93 -# ╠═665f4d03-3521-475a-a195-f861fd26bb69 -# ╠═13775d58-ac61-431c-a6c9-447c1eec7942 -# ╟─bc92f996-b626-4965-a286-d2c848eb1a21 -# ╟─e2fde8e9-1f87-4ffe-8dae-2794664bfaa4 -# ╠═23fd9fd9-a13c-4c56-a0b4-daec6f1d2cd8 -# ╟─6f414d15-af0a-452a-98a1-dc0b7e54d617 -# ╠═57ee8a12-24df-4598-935c-f5e259b504cb -# ╟─48bc085c-9ce6-4752-a5a9-a814f803f571 -# ╠═5b9b2e5b-9deb-4ff6-a923-b15b8b08f0c9 -# ╠═66ee9655-a006-4c0f-b1f1-5576efa8f896 -# ╟─52d7975a-5346-447f-9aad-4ecd11b6460a -# ╠═e96efd6a-a666-4120-8480-9423e5d82ae1 -# ╟─f0b4772d-a72b-44e0-a3a1-ba9ad4c4dfeb -# ╟─7e98a771-52bb-484e-82ab-2e42e7cb4053 diff --git a/src/exercises/calib_irrigation.jl b/src/exercises/calib_irrigation.jl deleted file mode 100644 index e54d5e1c..00000000 --- a/src/exercises/calib_irrigation.jl +++ /dev/null @@ -1,337 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "26" -#> title = "5. Calibration irrigation" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Calibration irrigation" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 55675f3d-2fae-4a97-a0a0-ead29a6352e6 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 2b010e5c-1121-11ef-16fe-a5e3317122e4 -using Markdown - -# ╔═╡ 6750d246-8e7a-4cfb-810e-d1100aa4fdef -using InteractiveUtils - -# ╔═╡ 4947b0fd-13be-4f6a-b605-ed35b509d7ff -using Catalyst, OrdinaryDiffEq, StatsPlots - -# ╔═╡ 61d14819-ba44-40fe-95a9-9d7b0bf3dc33 -using Turing - -# ╔═╡ f6e77c8d-de11-4b9d-93c6-45bdcfbbbf9b -using StatsBase - -# ╔═╡ 9345dd8f-0a60-4aaf-a27f-ef8bf860f495 -using LinearAlgebra - -# ╔═╡ ea02aff2-7fb8-4b8f-8d0f-0bb3c6150708 -using Optim - -# ╔═╡ 55d5400d-1777-4918-a030-b94cb9a59f63 -md" -# Exercise: Irrigation experiment - Calibration -" - -# ╔═╡ 8f1afdec-b78d-4aba-a74f-cd3e4b35fab1 -md""" -In one of the previous practica we were introduced to an irrigation experiment carried out on a soil column consisting of two layers of soil, each with specific soil characteristics. However, here the volume of water per unit of time, $r$, irrigated evenly over the soil column, will be kept constant at $5\;mm\,h^{-1}$ in these new experiments. - -The water falls on the upper layer and percolates to the lower layer. The relative moisture content in both layers (i.e., relative to their residual moisture contents) is denoted by $S_1$ and $S_2$. - -A model description of the relative moisture content in both soil layers is given by: - -$$\begin{align} -\frac{dS_1}{dt} &= r\left(1-\cfrac{S_{1,res}}{S_{max}}\right) - \cfrac{r}{S_{max}}S_1 - \cfrac{k}{S_{max}}S_1 \\ -\frac{dS_2}{dt} &= \cfrac{k}{S_{max}}S_1 - v \,S_2^2 -\end{align}$$ - -where $v = 10^{-3}\;h^{-1}\,mm^{-1}$ and $S_{1,res}=10 \;mm$. Previously, we also assumed $k = 3\;mm\,h^{-1}$ and $S_{max} = 150\;mm$. -""" - -# ╔═╡ ad42d3a7-6d83-4362-aa3f-31628a1db9b2 -md""" -The *reaction network object* for this model could be set up as: -""" - -# ╔═╡ dc26abff-f8ab-4881-9acf-7b325b386a16 -irrigation_mod = @reaction_network begin - k/Smax, S1 --> S2 - v, 2S2 --> 0 - r * (1 - S1res / Smax), 0 --> S1 - r/Smax, S1 --> 0 -end - -# ╔═╡ e5d7520d-fd8c-48c0-bd36-826766212217 -md""" -In order to have better estimates the parameters $k$ and $S_{max}$, two experiments were conducted, each with a different initial condition: - -1. Starting from zero relative moisture content in both soil layers. -2. Starting from a relative moisture content of $140\;mm$ in the top layer, and $135\;mm$ in the bottom layer. - -The measurement data consist of measurements of the relative moisture contents $S_1$ and $S_2$ measured at intervals of $10\;h$ within a timespan of $150\;h$. -""" - -# ╔═╡ 73c9b5fb-4f56-4bde-beb4-387651409c1b -md""" -The measurement data for the 1st experiment are: -""" - -# ╔═╡ 9f94c63e-628f-4ff3-ad29-0f90d32dfcb1 -S1_meas1 = [0.2, 35.94, 52.49, 66.86, 60.66, 67.81, 73.22, 71.31, 72.94, 64.08, 70.11, 68.53, 70.54, 63.63, 67.39, 62.84] - -# ╔═╡ 3d406f41-62fa-4a31-8e6b-a621a06a118c -S2_meas1 = [0.63, 6.2, 17.67, 22.96, 35.41, 44.08, 43.5, 53.34, 47.57, 47.77, 43.96, 52.22, 46.67, 46.74, 46.46, 39.92] - -# ╔═╡ 68b6158a-a918-4809-bf81-b554bc70c6d0 -md""" -The measurement data for the 2nd experiment are: -""" - -# ╔═╡ 620729c8-62c2-4f0c-8684-713033a208bd -S1_meas2 = [137.96, 106.15, 90.15, 84.64, 76.15, 75.73, 73.32, 68.48, 70.06, 69.36, 70.91, 72.13, 76.25, 74.34, 74.93, 71.58] - -# ╔═╡ 5b83a98c-9c2b-459c-9d7f-7cc75d3bf70e -S2_meas2 = [124.08, 80.14, 60.15, 50.12, 49.66, 47.78, 46.56, 48.41, 42.7, 43.72, 49.03, 51.91, 48.24, 46.14, 51.22, 43.78] - -# ╔═╡ 9c568f28-8985-4a4a-a7aa-0010bbe37dc8 -md""" -For both experiments: -""" - -# ╔═╡ 0dc6fa2c-1eb2-4877-9155-dc7ea6cf6f18 -t_meas = 0:10:150 - -# ╔═╡ a4160054-3dfe-4595-81cb-94db4dd2fe20 -md""" -We can make a scatter plot of the measured data for both $S_1$ and $S_2$ for the 1st and 2nd experiments in the following way: -""" - -# ╔═╡ cef4b9a8-b5bf-4a2d-8a8f-5d8f85534859 -begin - scatter(t_meas, S1_meas1, label="S1 meas", color=:blue, title="Experiment 1") - scatter!(t_meas, S2_meas1, label="S2 meas", color=:red, ylims=(0, 150)) -end - -# ╔═╡ fc2cabd7-e778-4211-bf87-b5c11ca054c9 -begin - scatter(t_meas, S1_meas2, label="S1 meas", color=:blue, title="Experiment 2") - scatter!(t_meas, S2_meas2, label="S2 meas", color=:red, ylims=(0, 150)) -end - -# ╔═╡ c0b2db7b-0632-4008-9cff-d5fbf3e59807 -md""" -Calibrate the parameter values for $k$ and $S_{max}$ using the aforementioned measurement data for $S_1$ and $S_2$ in a timespan of $[0, 150]\,h$. Take the values from above as initial values for $k$ and $S_{max}$. -""" - -# ╔═╡ d35bbe54-5ebc-4ea5-a6c8-a6419476ec4c -md""" -Create an `ODEProblem`. Use the aforementioned values as initial values for the problem. -""" - -# ╔═╡ 10057510-e4b6-4a3e-9d3f-f05effc88a58 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ f4d49b1d-9105-4050-a9d4-196fa00a0591 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 777ce59f-c849-4a2e-a6dc-ae309d2a2e7c -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 43b83336-aea6-4914-bc26-b2e84994ce57 -# oprob = missing - -# ╔═╡ 923d04ce-b4d2-44b0-afff-7062c4628ad0 -md""" -Declare the Turing model. Make sure you take both experiments into account for optimizing $k$ and $S_{max}$. Use `InverseGamma` for the standard deviations of the measurements, `LogNormal` for $k$ and `Uniform` (between 100 and 200) for $Smax$. -""" - -# ╔═╡ 481eb8b9-5de2-4f68-b06a-ec18e054c9f5 -# Uncomment and complete the instruction -# @model function irrigation_fun(t_meas) -# σ_S1 ~ missing -# σ_S2 ~ missing -# k ~ missing -# Smax ~ missing -# params = missing -# u01 = missing -# oprob1 = missing -# osol1 = missing -# S1_s1 ~ missing -# S2_s1 ~ missing -# u02 = missing -# oprob2 = missing -# osol2 = missing -# S1_s2 ~ missing -# S2_s2 ~ missing -# end - -# ╔═╡ df933ae8-1f51-4467-93a7-33f153e5e4f8 -md""" -Provide the time measurements to the defined function and instantly condition the model with the measurements of $S_1$ and $S_2$ from both experiments: -""" - -# ╔═╡ 0e2aa675-9e09-4e06-b5f8-118707ee652a -# irrigation_cond_mod = missing # Uncomment and complete the instruction - -# ╔═╡ f7f47956-7c3b-44cc-bff7-fb7d32af874a -md""" -Optimize the priors ($\sigma_{S1}$, $\sigma_{S2}$, $k$ and $S_{max}$). Do this with `MLE` method and Nelder-Mead. Store the optimization results in `results_mle`. -""" - -# ╔═╡ 8c254d5a-225b-4772-9fdd-e9f700495fbd -# results_mle = missing # Uncomment and complete the instruction - -# ╔═╡ f15a1df5-047a-4f46-9419-8492ac1248e0 -md""" -Visualize a summary of the optimized parameters. -""" - -# ╔═╡ 00d944e4-2c88-4a5d-b809-69f435df4684 -# missing # Uncomment and complete the instruction - -# ╔═╡ 89eb31ef-b24f-44c8-bbe5-19101d859937 -md""" -Get the optimized values and assign them to `k_opt` and `Smax_opt`. -""" - -# ╔═╡ 92daa779-3373-40c0-b308-23e75e6674b6 -# k_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 35ab6ee5-fcd7-4dcc-9909-cc918fb1fe80 -# Smax_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 4026773f-ac5b-433e-bd9d-2122242861fd -md""" -Make plots of $S_1$ and $S_2$ for both experiments simulated with the optimized parameter values. -""" - -# ╔═╡ 8aa60652-eb9f-4dd3-ab06-0ce3dd261fe6 -md""" -Set up parameter values with optimized parameter values: -""" - -# ╔═╡ 97d53e48-590a-485b-bcf3-edc6a6124faf -# params_opt = missing # Uncomment and complete the instruction - -# ╔═╡ dfd2ac98-5cdc-4627-b6cf-71b33c0ff0d4 -md""" -Plot the simulation results $S_1$ and $S_2$ for the 1st experiment together with the corresponding measured data. Therefore initialize a vector `u01` with initial conditions for the 1st experiment. -""" - -# ╔═╡ 95ace332-52c0-46c3-ae28-d038320ed2c8 -# u01 = missing # Uncomment and complete the instruction - -# ╔═╡ 6ae63a13-d5ae-4dfb-b88d-be295b11a472 -# oprob1_opt = missing # Uncomment and complete the instruction - -# ╔═╡ bc6505ca-a61d-467f-afe6-47792a510ad5 -# osol1_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 67e423ea-e941-45bf-af4f-3fdecb648fbc -# Uncomment and complete the instruction -# begin -# missing -# missing -# missing -# end - -# ╔═╡ 8c7e0c75-01d2-4e21-a6cf-7ad70e0c6aae -md""" -Plot the simulation results $S_1$ and $S_2$ for the 1st experiment together with the corresponding measured data. Therefore initialize a vector `u02` with initial conditions for the 2nd experiment. -""" - -# ╔═╡ a7040b8e-c240-415b-8a9a-4a1a137398d4 -# u02 = missing # Uncomment and complete the instruction - -# ╔═╡ fe8f4961-68bd-42dc-a3f5-6692e918e241 -# oprob2_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 7f280230-7846-4529-a2ff-a81a2b9480bf -# osol2_opt = missing # Uncomment and complete the instruction - -# ╔═╡ ad9818a9-ccbe-4645-8b91-0c3fa773632a -# Uncomment and complete the instruction -# begin -# missing -# missing -# missing -# end - -# ╔═╡ 3c243670-2ba7-4396-8c6d-084726636741 -md""" -!!! question - Do your simulations fit well the measurements? -""" - -# ╔═╡ 1aeb0d86-b276-4bd6-9811-faf4a297ae6f -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═2b010e5c-1121-11ef-16fe-a5e3317122e4 -# ╠═6750d246-8e7a-4cfb-810e-d1100aa4fdef -# ╠═55675f3d-2fae-4a97-a0a0-ead29a6352e6 -# ╠═4947b0fd-13be-4f6a-b605-ed35b509d7ff -# ╠═61d14819-ba44-40fe-95a9-9d7b0bf3dc33 -# ╠═f6e77c8d-de11-4b9d-93c6-45bdcfbbbf9b -# ╠═9345dd8f-0a60-4aaf-a27f-ef8bf860f495 -# ╠═ea02aff2-7fb8-4b8f-8d0f-0bb3c6150708 -# ╟─55d5400d-1777-4918-a030-b94cb9a59f63 -# ╟─8f1afdec-b78d-4aba-a74f-cd3e4b35fab1 -# ╟─ad42d3a7-6d83-4362-aa3f-31628a1db9b2 -# ╠═dc26abff-f8ab-4881-9acf-7b325b386a16 -# ╟─e5d7520d-fd8c-48c0-bd36-826766212217 -# ╟─73c9b5fb-4f56-4bde-beb4-387651409c1b -# ╠═9f94c63e-628f-4ff3-ad29-0f90d32dfcb1 -# ╠═3d406f41-62fa-4a31-8e6b-a621a06a118c -# ╟─68b6158a-a918-4809-bf81-b554bc70c6d0 -# ╠═620729c8-62c2-4f0c-8684-713033a208bd -# ╠═5b83a98c-9c2b-459c-9d7f-7cc75d3bf70e -# ╟─9c568f28-8985-4a4a-a7aa-0010bbe37dc8 -# ╠═0dc6fa2c-1eb2-4877-9155-dc7ea6cf6f18 -# ╟─a4160054-3dfe-4595-81cb-94db4dd2fe20 -# ╠═cef4b9a8-b5bf-4a2d-8a8f-5d8f85534859 -# ╠═fc2cabd7-e778-4211-bf87-b5c11ca054c9 -# ╟─c0b2db7b-0632-4008-9cff-d5fbf3e59807 -# ╟─d35bbe54-5ebc-4ea5-a6c8-a6419476ec4c -# ╠═10057510-e4b6-4a3e-9d3f-f05effc88a58 -# ╠═f4d49b1d-9105-4050-a9d4-196fa00a0591 -# ╠═777ce59f-c849-4a2e-a6dc-ae309d2a2e7c -# ╠═43b83336-aea6-4914-bc26-b2e84994ce57 -# ╟─923d04ce-b4d2-44b0-afff-7062c4628ad0 -# ╠═481eb8b9-5de2-4f68-b06a-ec18e054c9f5 -# ╟─df933ae8-1f51-4467-93a7-33f153e5e4f8 -# ╠═0e2aa675-9e09-4e06-b5f8-118707ee652a -# ╟─f7f47956-7c3b-44cc-bff7-fb7d32af874a -# ╠═8c254d5a-225b-4772-9fdd-e9f700495fbd -# ╟─f15a1df5-047a-4f46-9419-8492ac1248e0 -# ╠═00d944e4-2c88-4a5d-b809-69f435df4684 -# ╟─89eb31ef-b24f-44c8-bbe5-19101d859937 -# ╠═92daa779-3373-40c0-b308-23e75e6674b6 -# ╠═35ab6ee5-fcd7-4dcc-9909-cc918fb1fe80 -# ╟─4026773f-ac5b-433e-bd9d-2122242861fd -# ╟─8aa60652-eb9f-4dd3-ab06-0ce3dd261fe6 -# ╠═97d53e48-590a-485b-bcf3-edc6a6124faf -# ╟─dfd2ac98-5cdc-4627-b6cf-71b33c0ff0d4 -# ╠═95ace332-52c0-46c3-ae28-d038320ed2c8 -# ╠═6ae63a13-d5ae-4dfb-b88d-be295b11a472 -# ╠═bc6505ca-a61d-467f-afe6-47792a510ad5 -# ╠═67e423ea-e941-45bf-af4f-3fdecb648fbc -# ╟─8c7e0c75-01d2-4e21-a6cf-7ad70e0c6aae -# ╠═a7040b8e-c240-415b-8a9a-4a1a137398d4 -# ╠═fe8f4961-68bd-42dc-a3f5-6692e918e241 -# ╠═7f280230-7846-4529-a2ff-a81a2b9480bf -# ╠═ad9818a9-ccbe-4645-8b91-0c3fa773632a -# ╟─3c243670-2ba7-4396-8c6d-084726636741 -# ╠═1aeb0d86-b276-4bd6-9811-faf4a297ae6f diff --git a/src/exercises/exercises.md b/src/exercises/exercises.md deleted file mode 100644 index 9b44cae0..00000000 --- a/src/exercises/exercises.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: "Introduction" -tags: ["exercises"] -order: 1 -layout: "md.jlmd" ---- - - - -# Exercises description - -Here are renders of the exercises, see all pages on the left. - -To download all exercises: see Ufora. - -We have annotated the exercises with either a number or an extra prefix. - -> 1. XYZ - -Will be covered in exercise lession 1. - -> EXTRA. XYZ - -Additional exercises that will not be covered in the guided exercises. - -# Notes on the dependencies - -If you insist on downloading the exercises from this website, note that because we are rendering the notebooks here, we make use of our a specific environment. You will need to update this on your system. Look out for the cell with: -```julia -using Pkg -Pkg.activate("../../pluto-deployment-environment") -``` -Change this to the your current folder so that the Project and Manifest files are generated there: -```julia -using Pkg -Pkg.activate(".") -``` diff --git a/src/exercises/model_selection_intro.jl b/src/exercises/model_selection_intro.jl deleted file mode 100644 index f6f8d857..00000000 --- a/src/exercises/model_selection_intro.jl +++ /dev/null @@ -1,1356 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "35" -#> title = "7. Model selection intro" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Model selection intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ e1e7bc8e-7264-4cbc-98d2-aa73679fa2df -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 5f4fea06-0632-11ef-102e-21f5606d2056 -using Markdown - -# ╔═╡ 489b5399-fe4c-481a-834f-0101bbe28cea -using InteractiveUtils - -# ╔═╡ ab4a1911-461e-4878-9258-931fc2f1ea06 -using PlutoUI; TableOfContents() - -# ╔═╡ cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 22489bd4-ab64-4bf9-ad03-5372ea273935 -using Catalyst - -# ╔═╡ 4ad72a6a-f541-4d0a-8392-56369e29ac96 -using Turing, StatsBase - -# ╔═╡ 70590d28-058a-44df-8cf6-092d8c87438c -using LinearAlgebra, Optim - -# ╔═╡ 427b509f-d08e-4d93-99ba-a79f9c244b28 -md""" -# Introduction to model selection -""" - -# ╔═╡ 49e7085c-3691-4182-9630-68dc9371ad18 -md""" -## Goal of this practicum -""" - -# ╔═╡ 84caa99f-32ab-433c-b620-f592d329d18d -md""" -In previous practicals, we have developed models to study phenomena and predict future behavior. We have also estimated the parameters associated with these models and we have also analyzed the sensitivity of the model predictions to changes in these parameters. We found that the mathematical structure of different models determines the sensitivity to errors in the parameters and errors in the model itself and we determined how these errors propagate through the model, allowing us to quantify the uncertainty in the predictions. -""" - -# ╔═╡ 7cc1d1ce-a34a-4432-953d-4eb2df92696f -md""" -In this practical, we investigate how to make an objective choice between different candidate models by weighing the complexity of the models against the fit to the experimental data and the quality of the prediction. We will use two information criteria often used in practice to balance model quality and complexity: the Akaike information criterion (AIC) and the Bayesian information criterion (BIC). -""" - -# ╔═╡ dcb51063-2b3f-4de9-90bc-0048e82cd4ab -md""" -In the Akaike information criterion, the fit or quality of the model (likelihood $L$) is compared against the number of model parameters ($k$), thus giving a measure of the balance between complexity and quality of the fit: - -$AIC = 2k - 2\,\log(L)$ -""" - -# ╔═╡ 6690a844-8fd5-4ea1-9e62-a834bd454efb -md""" -The Bayesian information criterion gives similar information, but penalizes complexity more heavily: - -$BIC = k\,\log(n) - 2\,\log(L)$ - -where $n$ is the number of data points considered. -""" - -# ╔═╡ 61e5bead-6fc3-4c07-b2b4-832ce7a69198 -md""" -In this notebook we will compare the different grass growth models and judge the quality of their fit to the calibration data set in order to select the simplest or least complex model that best represents the system. -""" - -# ╔═╡ 04e95855-7c4a-4d2c-b836-c5dde291adad -md""" -## Grass growth models -""" - -# ╔═╡ 4673bfdf-8f4a-42bd-a026-21a66d800f2b -md""" -In this notebook, three different models will be used, each modelling the yield of grass in a grassland: - -- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ -- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ -- Gompertz growth model: $\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$ - -with output $W$ the grass yield, and $W_f$, $\mu$ and $D$ parameters. The table below shows some typical values for the parameters: - -| | $\mu$ | $W_f$ | $D$ | -|:----------- |:----------:|:-----------:|:------------:| -| Logistic | 0.07 | 10.0 | | -| Exponential | 0.02 | 10.0 | | -| Gompertz | 0.09 | | 0.04 | - -We will use an initial condition of $W_0 = 2.0$ for each and a simulation time of $100$ days. -""" - -# ╔═╡ 6e5f494e-ecfb-467b-a0f1-c85427da8215 -md""" -In each of the three models we will use the following timespan: -""" - -# ╔═╡ 0b3d35bb-c5b0-44b7-94b3-06fa571d339e -tspan = (0.0, 100.0) # this will be the same for the three models - -# ╔═╡ d1c5a659-70c4-4768-9542-a2988ea39c43 -md""" -### The calibration data -""" - -# ╔═╡ 583ed493-27e7-4fa7-b519-75cc71468fc6 -md""" -Assume that the measured grass yields (of a certain plant type) over time are the following: -""" - -# ╔═╡ d4ca6986-df24-48dd-8637-0974e0b16f2c -W_meas = [1.87, 2.45, 3.72, 4.32, 5.28, 7.01, 6.83, 8.62, 9.45, 10.31, 10.56, 11.72, 11.05, 11.53, 11.39, 11.7, 11.15, 11.49, 12.04, 11.95, 11.68] - -# ╔═╡ 8215b6f0-017e-4a73-b4bf-aa61b8f3ca6f -md""" -They have been measured at the following corresponding time instances: -""" - -# ╔═╡ 2ff5f464-38d7-4a90-ad31-d19d412781fc -t_meas = 0:5:100 - -# ╔═╡ b1d02f3c-f528-4297-a0c1-993cfe0d7c17 -md""" -We can make a scatter plot of this data (including a title, a legend label, an X-axis label, X- and Y-axis limits) in the following way: -""" - -# ╔═╡ 1499c727-ff19-4ef0-b467-bc71c1802979 -scatter(t_meas, W_meas, title="Grass growth data", - label="Yield", - xlabel="t", - xlims=(0, 100), - ylims=(0, 14)) - -# ╔═╡ c692b627-8743-48f6-bb26-b7d9b3d3b9c8 -md""" -### Logistic growth -""" - -# ╔═╡ 676119ce-f4fe-41f9-8121-b2c21f0dd28c -md""" - -$$\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$$\ -$W_0$ = 2.0, $\mu$ = 0.07 and $W_f$ = 10.0\ -We will start by modelling our system and simulating using the aforementioned parameters values, initial condition and timespan in a way that we are familiar with. -""" - -# ╔═╡ 5492210b-6ee6-4baf-b37e-d21358cdeb60 -md""" -Implementation of the system: -""" - -# ╔═╡ 18eece66-46fa-4458-aaed-f4c8fa002c20 -growth_log = @reaction_network begin - @species W(t)=2.0 # default initial condition - @parameters μ=0.07 Wf=10.0 # default parameter values - μ*(1-W/Wf), W --> 2W -end - -# ╔═╡ 8bb0b24b-6cce-49cd-a625-5f375b92d9b7 -md""" -Convert the *reaction model* to check that we work with the correct differential equation: -""" - -# ╔═╡ ba181db8-d176-4b83-9168-d5939ffe9661 -osys_log = convert(ODESystem, growth_log) - -# ╔═╡ cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 -md""" -Setting initial conditions, timespan and parameter values: -""" - -# ╔═╡ cbb2ca49-b019-495b-9310-83fcc00cad26 -u0_log = [:W => 2.0] - -# ╔═╡ be565a3c-31b6-4df1-b73b-08f308a8c09b -md""" -For the sake of clarity, we will use the variables `μ_log` and `Wf_log` to store the parameter values. -""" - -# ╔═╡ f62806d1-77e1-470b-9711-33a924c788cc -μ_log = 0.07 - -# ╔═╡ 546ed163-26a7-4235-982f-7568ed609488 -Wf_log = 10.0 - -# ╔═╡ 0da53fa2-5a42-46e6-8bd8-45d6aa903d46 -params_log = [:μ => μ_log, :Wf => Wf_log] - -# ╔═╡ b1298f40-4696-49d0-ac94-896e0cdbc996 -md""" -Creating and solving the ODEProblem and plotting results: -""" - -# ╔═╡ 270647d2-1371-4272-8bc1-3a6ad77bc716 -oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log); -# Also possible here if initial conditions and parameter values are defined in the catalyst model: -# oprob_log = ODEProblem(growth_mod_log, [], tspan, []) - -# ╔═╡ ac235d86-1d93-4944-aa89-1b4fd38f0e6e -osol_log = solve(oprob_log, Tsit5(), saveat=0.5) - -# ╔═╡ 3e13efa1-9bc6-456f-8e62-ecd3165e2a65 -begin - plot(osol_log, label="model", lw=2, ylabel="W") - scatter!(t_meas, W_meas, title="Logistic growth model", label="data", xlabel="t", - xlims=(0, 100), ylims=(0, 14)) -end - -# ╔═╡ e2955af0-edf0-4702-9a8f-478141ffdc3b -md""" -We can see that the model does not predict well the data set for the considered parameter values. Thus we will use the data to both calibrate the model parameters and assess the quality of the fit. -""" - -# ╔═╡ c73669c4-d7af-4877-b32d-6499f339e27a -md""" -### Parameter estimation -""" - -# ╔═╡ 28e4a44f-e907-42ce-a748-6d21b60f0e33 -md""" -We declare our Turing model function: -""" - -# ╔═╡ 169a67ff-55fb-4d1d-b98b-126f4af47e77 -@model function growth_log_fun(t_meas) - σ_W ~ InverseGamma() - W0 ~ LogNormal() - μ ~ LogNormal() - Wf ~ LogNormal() - u0_log = [:W => W0] - params_log = [:μ => μ, :Wf => Wf] - oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log) - osol_log = solve(oprob_log, Tsit5(), saveat=t_meas) - W_s ~ MvNormal(osol_log[:W], σ_W^2 * I) - return osol_log # optionally, to be used with MCMC -end - -# ╔═╡ aa3e553d-2731-42d6-b0e6-1821e4d7f4d4 -md""" -We now provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: -""" - -# ╔═╡ 51426716-03b8-4d54-9064-3943df282fa4 -growth_log_cond_mod = growth_log_fun(t_meas) | (W_s = W_meas,) - -# ╔═╡ f40873af-3ef9-420b-b0a8-ed9f56b17047 -md""" -We are now ready to optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). This is done by calling the `optimize` function, providing the previously created object `growth_log_inf`, the method for estimating the parameters and (optionally) an algorithm (default: Nelder-Mead) to implement the method. -""" - -# ╔═╡ 4536c6b7-f3bc-42e8-b044-0633f00b56bb -md""" -We will use the MLE (Maximum Likelihood Estimation) method here and store the optimization results in `results_log_mle`. If you get an error the first time, try running the optimization again. -""" - -# ╔═╡ bc55fb07-1d84-4927-80d2-c1a012409400 -results_log_mle = optimize(growth_log_cond_mod, MLE(), NelderMead()) - -# ╔═╡ c224f1b8-e5a1-4379-ae50-981ae89f9eaa -md""" -You can visualize a summary of the optimized parameters by piping them to `coeftable`: -""" - -# ╔═╡ 4fe2b4cd-d0b0-489a-a867-14c58637327b -coeftable(results_log_mle) - -# ╔═╡ 6cf5e2f2-344c-4a7a-8901-5a0ee9cb8689 -md""" -You can obtain the actual optimized values using the function `coef` on the results object in conjunction by calling the parameters by name preceded by a colon. Here we assign the optimized parameter values to some suitable variable names: -""" - -# ╔═╡ 551859cb-9cd1-4977-a528-b96423e83504 -W0_opt_log = coef(results_log_mle)[:W0] - -# ╔═╡ 955524a6-1a53-4d2d-ad16-a5a3a2a39b80 -μ_opt_log = coef(results_log_mle)[:μ] - -# ╔═╡ 118a79cc-afaf-4a99-8486-8e0e01fda40a -Wf_opt_log = coef(results_log_mle)[:Wf] - -# ╔═╡ 8536cc9e-6cd4-4e38-9988-e45b06b75ea4 -md""" -Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ 25db8d7b-e467-4a2f-b3f3-0ad7df64988a -md""" -Setting up initial condition with optimized initial condition: -""" - -# ╔═╡ 01241971-cc9a-42d6-8e52-c840b91e6431 -u0_opt_log = [:W => W0_opt_log] - -# ╔═╡ e2db7492-4d68-4045-91b6-dca3aef8b514 -md""" -Setting up parameter values with optimized parameter values: -""" - -# ╔═╡ 7a890da6-7547-4854-b877-a5d935a9f2dd -params_opt_log = [:μ => μ_opt_log, :Wf => Wf_opt_log] - -# ╔═╡ c95bcde6-ffb9-4ce6-9dfb-38c79cc2e4b8 -md""" -Next, we create an ODEProblem and solve it: -""" - -# ╔═╡ bf90a66a-8505-4b33-b821-d708a3b7f8b0 -oprob_opt_log = ODEProblem(growth_log, u0_opt_log, tspan, params_opt_log) - -# ╔═╡ 7af67f91-58e2-429d-a88f-b27a0258a805 -osol_opt_log = solve(oprob_opt_log, Tsit5(), saveat=0.5); - -# ╔═╡ 3ee0d4de-fdf5-40f0-a849-63b0759eb44b -md""" -Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ 2c048fc0-5908-4f01-a9a5-480e12e098cc -begin - plot(osol_opt_log, label="model", xlabel="t", ylabel="W", - xlims=(0, 100), ylims=(0, 14), lw=2.0, title="Calibrated logistic growth model") - scatter!(t_meas, W_meas, label="data") -end - -# ╔═╡ 19ed4aa9-776e-4a80-b180-65d32f3f9f26 -md""" -We can extract from the calibration results the log-likelihood or quality of the fit: -""" - -# ╔═╡ 12a11f90-fa55-47f2-a75f-853cd54cbeab -L_log = results_log_mle.lp - -# ╔═╡ f6b44166-3ba9-4770-80db-303bd28112cf -md""" -## Model selection criteria -""" - -# ╔═╡ 02037c91-448e-4714-b932-86f4df631907 -md""" -### Akaike information criterion -""" - -# ╔═╡ cfea46a6-636c-4fcd-a04b-714e7a567cb3 -md""" -To calculate the AIC, we can implement a function that uses the information from the calibration: -""" - -# ╔═╡ cb9384a3-96b0-4694-a218-25ebf96187a9 -function AIC(results, measurements) - L = results.lp - k = length(results.values) - n = length(measurements) - return 2k - 2L # L = log-likelihood -end - -# ╔═╡ 73917400-43e0-4b9f-9448-9bbe02daebb9 -md""" -This function uses the results from the calibration, from where we can extract as well the number of calibrated parameters, which includes the estimated prediction error: -""" - -# ╔═╡ afba526d-0f71-4ba5-9a84-3c24804f959a -k_log = length(results_log_mle.values) # alternative: length(coef(results_log_mle)) - -# ╔═╡ 903380ce-1320-464e-a703-1222b4f381b1 -md""" -The AIC will use this to balance the complexity with the quality of the fit. For the logistic model: -""" - -# ╔═╡ 22bd4450-6c6d-4a4d-aa1d-a080d3b06eac -AIC_log = AIC(results_log_mle, W_meas) - -# ╔═╡ 34f8cb7d-7482-4883-87e0-5c68be7ec0e9 -md""" -### Bayesian information criterion -""" - -# ╔═╡ 4d976b86-74d6-4bb5-af17-8a6bd658abe2 -md""" -We can also calculate the BIC in a similar way to the AIC: -""" - -# ╔═╡ ba7974ec-3965-4c92-ab37-08cdf5ab8125 -function BIC(results, measurements) - L = results.lp - k = length(results.values) - n = length(measurements) - return k*log(n) - 2L # L = log-likelihood -end - -# ╔═╡ 5e6837d5-35e9-434c-9337-7c90daec9c33 -md""" -The BIC will additionally use the length of the data set for the complexity penalty term: -""" - -# ╔═╡ b97ca16c-e6c3-4840-a855-1d29140eff59 -n = length(W_meas) - -# ╔═╡ 1af53492-f363-4d39-bfaf-2f668a441725 -BIC_log = BIC(results_log_mle, W_meas) - -# ╔═╡ e176e6ce-33da-4f3b-860e-4db99a931079 -md""" -!!! question - What conclusions can we extract from a comparison of the AIC or BIC for different models? -""" - -# ╔═╡ 770e70d0-8a69-4585-a423-41335d453dcd -md""" -Conclusions: -- Lower values are better. For the same value of $L$, a simpler model would be preferred. -- BIC seems to penalize more complex models than AIC for the same values of $L$ and $k$. -""" - -# ╔═╡ f4f3b1fb-165d-4f77-88f1-8c1a79f3fb42 -md""" -### The posterior model probability -""" - -# ╔═╡ 4a86d852-634c-43f5-90e5-3e675aeb2d69 -md""" -We can use the AIC to compute the posterior probabilities of the different candidate models: - -$P(M_i|D) \propto \exp(-AIC(M_i)/2)$ -""" - -# ╔═╡ 8a43d4b8-1902-4e02-9cd7-c36131ec9528 -md""" -The following function will use the supplied AIC of several models to compute the normalized posterior probability that the model is the "true model", explaining the considered data set: -""" - -# ╔═╡ d149d829-7d3d-4f7a-bdad-b1debb2c5149 -function posterior(AICs) # AICs vector of AIC values - AICmin = minimum(AICs) - posterior = zeros(length(AICs)) - - for i in eachindex(1:length(AICs)) - posterior[i] = exp((AICmin-AICs[i])/2) - end - - return round.(posterior/sum(posterior); digits=3) # normalized sum -end - -# ╔═╡ e2bec1ab-f161-45b6-a67e-1f4f197ae685 -posterior([AIC_log]) - -# ╔═╡ 5db6b1a5-ed2e-4812-94db-37cfffb46227 -md""" -!!! note - This function will be used to compare the different candidate models (more than one). -""" - -# ╔═╡ bc8d315a-ddfb-4c82-84d0-90a4fb05256c -md""" -### Least squares model fitting -""" - -# ╔═╡ 51b348ef-0270-41d0-bbcb-a27eb10ff5d2 -md""" -The Akaike information criterion can be reformulated in terms of **least squares** if we assume that the model residuals are normally (and independently) distributed with zero mean, giving rise to: - -$AIC = 2k + n \log{\bigg(\frac{SSR}{n}}\bigg)$ - -where $SSR$ is the **squared sum of the model's residuals**. For small data sets, a correction is done: - -$AIC_c = 2k + n \log{\bigg(\frac{SSR}{n}}\bigg) + \frac{2k(k+1)}{n-k-1}$ - -When the number of observations is large enough, the corrected $AIC_c$ and $AIC$ are identical. - -The Bayesian information criterion can also be expressed in terms of the residuals: - -$BIC = k\log(n) + n \log{\bigg(\frac{SSR}{n}}\bigg)$ - -Both criteria are implemented below and can be used to compare the fitness of different models. -""" - -# ╔═╡ 7bf8fa3a-ce98-4f40-8d6c-4e84b79ae1c0 -function AIC_LS(SSR, n, k) - if n > 40 - return 2k + n*log(SSR/n) - else - return 2k + n*log(SSR/n) + 2k*(k+1)/(n-k-1) - end -end - -# ╔═╡ 498e0b9b-17a9-4059-9196-c6c4d86f926e -function BIC_LS(SSR, n, k) - return k*log(n) + n*log(SSR/n) -end - -# ╔═╡ b57131ad-e9d6-4318-bca7-c59c5d8da4c3 -md""" -We can thus obtain the squared sum of residuals from the calibrated model prediction and the data: -""" - -# ╔═╡ f2fe206b-2832-42f6-84bd-20c3838fcb0b -function SSR(y_pred, y_data) - return sum((y_pred - y_data).^2) # squared sum of residuals -end - -# ╔═╡ d51fff9b-17d3-4a0d-aa5e-01d58821a246 -md""" -We can now calculate the SSR and alternative AIC and BIC forms for the logistic model: -""" - -# ╔═╡ 6d95d3b1-d964-4b61-adb6-f44875e23ecb -begin - W_log = solve(oprob_opt_log, Tsit5(), saveat=t_meas)[:W] # model prediction - SSR_log = SSR(W_log, W_meas) - AIC_LS_log = AIC_LS(SSR_log, n, k_log) - BIC_LS_log = BIC_LS(SSR_log, n, k_log) -end; - -# ╔═╡ b1783355-0221-4f2d-a050-932b40920eab -AIC_log, BIC_log - -# ╔═╡ 3c69edb3-ba7d-4ee0-996a-40fd985e57d3 -AIC_LS_log, BIC_LS_log - -# ╔═╡ 982e1de1-0820-42fb-8abb-31f1c99def46 -md""" -!!! note - See the exercises below to apply the different criteria for model selection to the other models. -""" - -# ╔═╡ 79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 -md""" -## Exercises -""" - -# ╔═╡ fa2270d0-2548-409c-a23f-4369d8bce8ec -md""" -### Exercise 1 - Compare the logistic and exponential models -""" - -# ╔═╡ 545a317d-8bca-4b6c-9915-84b0b4ffbed5 -md""" -Calibrate the initial condition and both parameters of the exponential growth model. Use the values mentioned in the Table as initials values for the optimization of the parameters. Then compare the fit to that of the logistic model by plotting both predictions in the same figure. -""" - -# ╔═╡ 794b93c9-0a0a-4e77-b13c-0e2c06a9a0ec -md""" -$$\cfrac{dW}{dt} = \mu \left( W_f - W \right)$$\ -$W_0$ = 2.0, $\mu$ = 0.02 and $W_f$ = 10.0 -""" - -# ╔═╡ cfca0095-81e7-4c88-84bd-773eb5af7aa4 -growth_exp = @reaction_network begin - μ*Wf, 0 --> W - μ, W --> 0 -end - -# ╔═╡ ba9162dd-27f4-42b3-a6b5-ca9531614d5e -md""" -Use the same measurement data (`W_meas`, `t_meas`) as before. -""" - -# ╔═╡ b1f03df5-4353-45ed-9d91-9e2c9960f706 -md""" -Declare the Turing model function. -""" - -# ╔═╡ 89bf91c6-117c-4647-bfb9-6fc9b8dcfb5f -@model function growth_exp_fun(t_meas) - σ_W ~ InverseGamma() - W0 ~ LogNormal() - μ ~ LogNormal() - Wf ~ LogNormal() - u0_exp = [:W => W0] - params_exp = [:μ => μ, :Wf => Wf] - oprob_exp = ODEProblem(growth_exp, u0_exp, tspan, params_exp) - osol_exp = solve(oprob_exp, Tsit5(), saveat=t_meas) - W_s ~ MvNormal(osol_exp[:W], σ_W^2 * I) -end - -# ╔═╡ b9a8c0cb-fe38-448e-aa4c-e9422f24a4a4 -md""" -Provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: -""" - -# ╔═╡ 6b5fc852-1d40-45ce-a2e2-d22a6cc03156 -growth_exp_cond_mod = growth_exp_fun(t_meas) | (W_s = W_meas,) - -# ╔═╡ 29591722-bb44-47d0-a336-b2151502fd96 -md""" -Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). Do this with both the `MLE` and `MAP` methods and the Nelder-Mead algorithm. Store the optimization results in `results_exp_mle` and `results_exp_map`. -""" - -# ╔═╡ ec032fb5-76a7-4a0c-a8be-b0f6bd166580 -results_exp_mle = optimize(growth_exp_cond_mod, MLE(), NelderMead()) - -# ╔═╡ d44ed37a-d022-457c-befe-01bae5cd0ed9 -md""" -Visualize a summary of the optimized parameters. -""" - -# ╔═╡ 5d3f6390-1f71-4543-ae4e-d72f28b94f40 -coeftable(results_exp_mle) - -# ╔═╡ 9eb6e432-3f41-4b5f-9194-241e6ab3a039 -md""" -Get the optimized values and assign them to `W0_opt_exp`, `μ_opt_exp` and `Wf_opt_exp`. -""" - -# ╔═╡ cc0fc9ba-5564-4104-b0b3-a10ae39ef933 -W0_opt_exp = coef(results_exp_mle)[:W0] - -# ╔═╡ 6083305f-c838-4770-95c5-258fd7becbcb -μ_opt_exp = coef(results_exp_mle)[:μ] - -# ╔═╡ 9777f3e8-785d-4ff5-a6c6-80a68a9c53ba -Wf_opt_exp = coef(results_exp_mle)[:Wf] - -# ╔═╡ e80de22e-d5e6-4af7-af0e-feaf9ef660bf -md""" -Make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ 0020352d-52dc-4ca4-90dd-f53865306f9e -md" -Set up initial condition with optimized initial condition: -" - -# ╔═╡ e58c49dd-5365-4dcb-9baa-c7a6048eb0ed -u0_opt_exp = [:W => W0_opt_exp] - -# ╔═╡ 03a35cce-cacf-4b11-a0b8-3a26ad93c227 -md""" -Set up parameter values with optimized parameter values: -""" - -# ╔═╡ b3556b32-4665-439e-8751-a3ffa8ba1907 -params_opt_exp = [:μ => μ_opt_exp, :Wf => Wf_opt_exp] - -# ╔═╡ ae030bbd-6416-44b8-ba9e-3fe9be5c9c52 -md""" -Create an ODEProblem and solve it. Solve it using `Tsit5()` and `saveat=0.5`. -""" - -# ╔═╡ bfd9d570-7b12-4bbf-aaa9-449f2377a294 -oprob_opt_exp = ODEProblem(growth_exp, u0_opt_exp, tspan, params_opt_exp) - -# ╔═╡ f9e1ca7a-2a27-46aa-82a2-5e06882a9fad -osol_opt_exp = solve(oprob_opt_exp, Tsit5(), saveat=0.5); - -# ╔═╡ 0e62cf17-f885-4c38-8df2-39d1719bfa04 -md""" -Plot now $W$ simulated with the optimized initial value and parameter values of both logistic and exponential models together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ fe3c059f-cb4f-4ddf-ad54-c08aa70bc740 -# Uncomment and complete the instruction -# begin -# plot() -# missing -# missing -# missing -# title!("Comparison logistic vs. exponential growth") -# end - -# ╔═╡ fcfd18a2-154f-46fd-b4a5-01e406b500f1 -md""" -!!! question - By looking at the figure, how can you decide which candidate model is better? -""" - -# ╔═╡ 78d5ad0e-318f-4af1-883b-b887f8d72591 -md""" -- Answer: missing -""" - -# ╔═╡ 58006718-5e76-4c6b-a968-aa5372bb4a15 -md""" -Compare now the fit of both models by applying both the AIC and BIC criteria. -""" - -# ╔═╡ 7606cd16-7463-462e-83e6-5adaea4632a2 -md""" -Extract the log-probability and number of parameters from the calibration results of the exponential: -""" - -# ╔═╡ 2379169c-7f2c-415c-8955-e8b06fc9edf5 -L_exp = results_exp_mle.lp - -# ╔═╡ 577bb77e-d8c6-4121-84e6-1d3c000ce7b9 -k_exp = length(coef(results_exp_mle)) - -# ╔═╡ ac2d3b13-54d2-4217-8b18-7f9e276f1b6f -md""" -Calculate the AIC and BIC for the exponential model: -""" - -# ╔═╡ 2174d25b-b603-4d7b-b396-7ea00780143c -AIC_exp = AIC(results_exp_mle, W_meas) - -# ╔═╡ 0e8a6614-3164-4eab-b28b-fde3a328cbc3 -BIC_exp = BIC(results_exp_mle, W_meas) - -# ╔═╡ f2805dab-cac4-4a31-8418-6763c14d6415 -L_log, L_exp - -# ╔═╡ 41d8cfe2-a997-4731-971e-3e0c6166177e -AIC_log, AIC_exp - -# ╔═╡ 2b2110eb-b867-47a1-8238-3d43a64851bb -BIC_log, BIC_exp - -# ╔═╡ 4fb53b80-4ea1-47a0-871a-adde769f6d18 -md""" -!!! question - Draw your conclusions. -""" - -# ╔═╡ 22b58c97-1aa2-49c4-8a0a-488c63014a90 -md" -- missing -" - -# ╔═╡ 41abf8c4-e67e-4f66-a38a-7204a878d98d -md""" -### Exercise 2 - Comparison of the three models -""" - -# ╔═╡ b02787ec-40eb-4000-9684-e69cbd912c6c -md""" -Perform the calibration of the Gompertz model and compare its fitness to the other two candidates. -""" - -# ╔═╡ e34fed6f-6b58-4642-84eb-167467881bb2 -md""" -$$\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$$\ -$W_0$ = 2.0, $\mu$ = 0.09 and $D$ = 0.04. -""" - -# ╔═╡ 68895842-0a1b-4236-b159-19a2805ea44d -growth_gom = @reaction_network begin - μ-D*log(W), W --> 2W -end - -# ╔═╡ ec12d3c4-6b48-4f3a-b463-0fc67d3adff3 -md""" -Declare the Turing model. Take the same priors as before. -""" -# Take for $\sigma_W$ and $W_0$ the same priors (and distributions) as before, but take for $\mu$ a Uniform prior distribution in the range $[0, 2]$ and the same for $D$ but in the range $[0, 1]$. - -# ╔═╡ 1f8b10ba-f380-492e-800c-2673774cb25c -@model function growth_gom_fun(t_meas) - σ_W ~ InverseGamma() - W0 ~ LogNormal() - μ ~ LogNormal() - D ~ LogNormal() - u0_gom = [:W => W0] - params_gom = [:μ => μ, :D => D] - oprob_gom = ODEProblem(growth_gom, u0_gom, tspan, params_gom) - osol_gom = solve(oprob_gom, Tsit5(), saveat=t_meas) - W_s ~ MvNormal(osol_gom[:W], σ_W^2 * I) -end - -# ╔═╡ 82bee644-7b9a-42fa-a32f-76b3eb5038b5 -md""" -Provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: -""" - -# ╔═╡ e481b87c-c6f3-48c1-bfff-5d76de0dce36 -growth_gom_cond_mod = growth_gom_fun(t_meas) | (W_s = W_meas,) - -# ╔═╡ a438470b-e037-4b65-b6b0-fb9c202e74a4 -md""" -Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $D$). Do this now with `MAP` method and Nelder-Mead. Store the optimization results in `results_gom_map`. -""" - -# ╔═╡ f4c3dcd0-70ec-49ab-bbad-04b550da481c -results_gom_mle = optimize(growth_gom_cond_mod, MLE(), NelderMead()) - -# ╔═╡ df166b45-3c28-4bad-95b8-f43429a58fd0 -md""" -Visualize a summary of the optimized parameters. -""" - -# ╔═╡ a2738236-86a1-419a-9617-b1229f7c9240 -coeftable(results_gom_mle) - -# ╔═╡ 59f78a22-cac1-49a7-b3e6-9d74b694be64 -md""" -Get the optimized values and assign them to `W0_opt_gom`, `μ_opt_gom` and `D_opt_gom`. -""" - -# ╔═╡ 7c351d72-c5ce-4e5a-b2e5-87ff9dbc70e5 -W0_opt_gom = coef(results_gom_mle)[:W0] - -# ╔═╡ d7312308-7c95-4c14-9bd6-56a7ac99d49a -μ_opt_gom = coef(results_gom_mle)[:μ] - -# ╔═╡ 8f8b23b3-2e33-46e4-917a-294934fa090e -D_opt_gom = coef(results_gom_mle)[:D] - -# ╔═╡ 4ff1079f-8dfb-4069-9a98-0d263eba9920 -md""" -Make a plot of $W$ simulated with the optimized initial condition and parameter values. -""" - -# ╔═╡ 9edd77c3-eb1e-4be7-bb6c-83803e675102 -md""" -Set up initial condition with optimized initial condition: -""" - -# ╔═╡ c02a4f18-d51d-4954-b7a4-a4ae64b10fb7 -u0_opt_gom = [:W => W0_opt_gom] - -# ╔═╡ 060b5869-881e-4d57-8357-cff95912ba16 -md""" -Set up parameter values with optimized parameter values: -""" - -# ╔═╡ 0d225f18-bdf6-49d1-a827-2bc9551c158d -params_opt_gom = [:μ => μ_opt_gom, :D => D_opt_gom] - -# ╔═╡ 5d27e199-ad09-454a-8966-1531371ce67b -md""" -Create an ODEProblem and solve it. Use the solver `Tsit5()` and `saveat=0.5`. -""" - -# ╔═╡ 79c4439b-df8f-467a-b791-5581bd564996 -oprob_opt_gom = ODEProblem(growth_gom, u0_opt_gom, tspan, params_opt_gom) - -# ╔═╡ bcbe7409-e969-4c7a-9417-195d406617bf -osol_opt_gom = solve(oprob_opt_gom, Tsit5(), saveat=0.5); - -# ╔═╡ 6dbc81c8-4bc7-4499-a9d1-9719df368176 -md""" -Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. -""" - -# ╔═╡ ef5270de-da37-4a01-8734-dac1fd190a05 -# Uncomment and complete the instruction -# begin -# plot() -# missing -# missing -# missing -# title!("Comparison logistic vs. exponential growth") -# end - -# ╔═╡ b9030c3f-2cbb-44bf-90b7-23d116de4001 -# L_gom = missing - -# ╔═╡ 2e684fd0-8410-4037-a6a6-2c472d6592ab -# k_gom = missing - -# ╔═╡ 79797d6d-9ca8-4f4f-9694-8045a8cf8cbb -# AIC_gom = missing - -# ╔═╡ 2cd566bb-3088-43f8-8926-9952342b5e5c -# BIC_gom = missing - -# ╔═╡ 750edde4-16b5-4025-82e0-078c0eacaca9 -# AIC_log, AIC_exp, AIC_gom - -# ╔═╡ 1bbbd1ae-3540-44da-bc53-c8182a85b611 -# BIC_log, BIC_exp, BIC_gom - -# ╔═╡ 3afb2a69-32d6-42c2-8c1e-51e64b83a6b4 -md""" -!!! question - Draw your conclusions. -""" - -# ╔═╡ 01ddf3dd-5199-48cf-ab13-75686278f3dc -md""" -- Answer: missing -""" - -# ╔═╡ 36f71777-ad33-4b27-9a0e-ff03b22ad79e -md""" -You can use the following graph with all the information calculated so far for your conclusions. -""" - -# ╔═╡ d3b23128-2e7a-4246-a1f2-7325e0c2c432 -# plot( -# bar(1:3, [AIC_log, AIC_exp, AIC_gom], title="AIC", ylims=(0, 50)), -# bar(1:3, [BIC_log, BIC_exp, BIC_gom], title="BIC", ylims=(0, 50)), -# bar(1:3, [L_log, L_exp, L_gom], title="Log-probability", ylims=(-20, 0)), -# bar(1:3, [k_log, k_exp, k_gom], title="no. parameters", ylims=(0, 8)), -# xticks=(1:3, ["Logistic", "Exponential", "Gompertz"]), -# legend=:none -# ) - -# ╔═╡ a9e5b236-67d5-400b-ba9a-8b565f6feeb1 -md""" -### Exercise 3 - Calculation of the posterior probabilities -""" - -# ╔═╡ 3bf0c957-f08b-4a45-90e0-40d56330c8e0 -md""" -Use the above implemented function `posterior` to calculate the posterior model probabilities. -""" - -# ╔═╡ 947e26fe-2010-48ad-b927-d4e5186f21e0 -# posteriors = missing - -# ╔═╡ 3506765b-a6cf-41ca-9201-fcc650c6f56d -# posteriors - -# ╔═╡ 72dc27a0-0e61-4073-843a-67e1bfd1427a -# md""" -# We can summarize all calculated criteria so far in the following table: - -# | Model | k | Log(L) | AIC | BIC | $P(M_i\|D)$ | -# |:---|:---|:---|:---|:---|:---| -# | Logistic | $k_log | $(round(L_log;digits=3)) | $(round(AIC_log;digits=3)) | $(round(BIC_log;digits=3)) | $(posteriors[1]) | -# | Exponential | $k_exp | $(round(L_exp;digits=3)) | $(round(AIC_exp;digits=3)) | $(round(BIC_exp;digits=3)) | $(posteriors[2]) | -# | Gompertz | $k_gom | $(round(L_gom;digits=3)) | $(round(AIC_gom;digits=3)) | $(round(BIC_gom;digits=3)) | $(posteriors[3]) | - -# """ - -# ╔═╡ 6240553c-0c7c-45f4-b190-ebacf09ab632 -md""" -!!! question - Draw your conclusions. Does the posterior probability give the same raking as the other criteria? -""" - -# ╔═╡ 12f64333-36d0-4e2a-9061-e3dcc7a4ae96 -md" -- missing -" - -# ╔═╡ 95ba48fe-f430-4f47-ab04-f5896d75343b -md""" -### Exercise 4 - Comparison with least squares -""" - -# ╔═╡ ef293617-7a7a-4597-99d2-2c39c543c296 -md""" -Repeat below the comparison to least squares for the exponential and Gompertz models. -""" - -# ╔═╡ 8bbed35b-fec5-4682-8b45-99fd3bd55bf2 -begin - # Uncomment and complete the instruction - # W_exp = missing - # SSR_exp = missing - # AIC_LS_exp = missing - # BIC_LS_exp = missing -end; - -# ╔═╡ 414ebb03-4fb7-4591-90e9-9ce3cf11e93d -begin - # Uncomment and complete the instruction - # W_gom = missing - # SSR_gom = missing - # AIC_LS_gom = missing - # BIC_LS_gom = missing -end; - -# ╔═╡ 7a633bd6-10f1-4755-94ac-40d1dc9580a7 -# AIC_LS_log, AIC_LS_exp, AIC_LS_gom - -# ╔═╡ aef172f1-8563-4e51-8a88-ec6b764a735e -# BIC_LS_log, BIC_LS_exp, BIC_LS_gom - -# ╔═╡ 091e978a-fd89-455a-9b97-f3088d580683 -# posterior([AIC_LS_log, AIC_LS_exp, AIC_LS_gom]) - -# ╔═╡ d3d6e099-66b0-444e-8142-ccd1718ac9ab -# plot( -# bar(1:3, [AIC_LS_log, AIC_LS_exp, AIC_LS_gom], title="AIC", ylims=(-40, 0)), -# bar(1:3, [BIC_LS_log, BIC_LS_exp, BIC_LS_gom], title="BIC", ylims=(-40, 0)), -# bar(1:3, [SSR_log, SSR_exp, SSR_gom], title="SSR", ylims=(0, 8)), -# bar(1:3, [k_log, k_exp, k_gom], title="no. parameters", ylims=(0, 8)), -# xticks=(1:3, ["Logistic", "Exponential", "Gompertz"]), -# legend=:none, -# suptitle="(Least squares)" -# ) - -# ╔═╡ 2de0f619-63e0-444b-916b-316e4ebfa13e -md""" -!!! question - Draw your conclusions. Do the SSR and alternative AIC and BIC provide the same model ranking? -""" - -# ╔═╡ ef5329d2-ac63-4b49-a4cb-e8d6281a3184 -md""" -- Answer: missing -""" - -# ╔═╡ 0b8e6775-7562-4c93-b64d-cb694531f250 -md""" -## Additional exercises -""" - -# ╔═╡ c99ac3a9-1d5e-43a3-a4ef-a22147187bb3 -md""" -### 1. MAP estimation -""" - -# ╔═╡ b620006e-d6ac-4038-960f-8d0d93c2de00 -md""" -We can repeat the calibration and take into account the priors to obtain the MAP estimation. -""" - -# ╔═╡ 00dbb9d0-2c9d-4559-91d8-1a54171cc257 -results_log_map = optimize(growth_log_cond_mod, MAP(), NelderMead()) - -# ╔═╡ 3d5e9bb7-5e65-4de5-a521-6b1f1cb872b3 -coeftable(results_log_map) - -# ╔═╡ 3a3cd103-78b1-481a-8746-749d402346d1 -md""" -!!! question - How will this affect the different criteria for the model selection? How is log(L) compared to MLE? -""" - -# ╔═╡ b9986617-923e-4744-9d44-1935770edbd7 -md""" -### 2 - Watanabe-Akaike information criterion (WAIC) -""" - -# ╔═╡ 6e0c869d-6f90-4104-8589-41c2fa1b7342 -md""" -The AIC and BIC are easy to compute but do not take into account the uncertainty in the predictions for the assessment of the model. The more complex Widely Applicable Information Criterion (WAIC) or Watanabe-Akaike information criterion takes samples from the posterior distribution and provides a measure of uncertainty for each observation, which can be used for model assessment. -""" - -# ╔═╡ 6ca83977-cb8c-4465-ab2e-c91971f16157 -md""" -We can generate new samples from the posterior distribution with MCMC. -""" - -# ╔═╡ fb76009b-3aaa-4eb5-8d69-45372a23af00 -N = 200 - -# ╔═╡ b9c59973-512b-48eb-a2b1-831449e120a4 -results_log_nuts = sample(growth_log_cond_mod, NUTS(), N) - -# ╔═╡ cfeb28b8-2dd7-4c1d-8069-e8fe00d90567 -plot(results_log_nuts) - -# ╔═╡ 56784bdf-2a78-4986-9dd2-790a2a2ceea9 -md""" -The log-pointwise-predictive-density (lppd) is the sum of the log-likelihood of all observations: -""" - -# ╔═╡ bfb99943-1637-4dd6-adee-8a9e79274672 -lppd_log = sum(results_log_nuts.value[:, :lp]) - -# ╔═╡ a08fb39b-3911-450e-8377-cc94398825a9 -md""" -The second part of WAIC is the variance of the log-likelihood of each observation, also called the effective number of parameters, $p_{WAIC}$, considered here as a penalty term, similarly to AIC and BIC: -""" - -# ╔═╡ 2575199f-a3ec-470f-87ea-d62be28c42dc -pWAIC_log = sum(results_log_nuts.value[:, :lp].^2)/N - (lppd_log/N)^2 - -# ╔═╡ 6391d316-8826-4f19-9a2e-7065db787bee -sum((results_log_nuts.value[:, :lp] .- lppd_log/N).^2)/N - -# ╔═╡ 2879ff01-860f-4fb1-b52d-3a43e57ccb23 -md""" -Finally, WAIC is defined as: - -$WAIC = -2(\text{lppd} - p_{WAIC})$ -""" - -# ╔═╡ 3b78c1ff-a2f6-4c1c-8124-5fe58c5a312d -WAIC_log = -2*(lppd_log - pWAIC_log) - -# ╔═╡ 75e221b4-fdfe-4ef0-a8ea-1d75700fc1e7 -md""" -We repeat the calculation for the exponential and Gompertz models below. -""" - -# ╔═╡ 0f228304-de82-48f2-8e43-6ef81f7a9830 -results_exp_nuts = sample(growth_exp_cond_mod, NUTS(), N) - -# ╔═╡ 9843e282-44b3-40d0-b2b5-506144c22b27 -lppd_exp = sum(results_exp_nuts.value[:, :lp]) - -# ╔═╡ eb9fe668-0a0f-4a8f-a793-ba545cb02e85 -pWAIC_exp = (sum(results_exp_nuts.value[:, :lp].^2)/N - (lppd_exp/N)^2) - -# ╔═╡ ddf9ae6d-0abe-4c8c-9646-024dd9a41e02 -WAIC_exp = -2*(lppd_exp - pWAIC_exp) - -# ╔═╡ 8330fce5-13c9-447e-8368-01b7c7ebc68f -results_gom_nuts = sample(growth_gom_cond_mod, NUTS(), N) - -# ╔═╡ 0d2cab0c-04a2-4c8b-bb4f-2115ae0cf871 -lppd_gom = sum(results_gom_nuts.value[:, :lp]) - -# ╔═╡ 6900e703-de7d-4ea3-8ac5-0b38b21b083d -pWAIC_gom = sum(results_gom_nuts.value[:, :lp].^2)/N - (lppd_gom/N)^2 - -# ╔═╡ b353eab2-d78f-4c5e-8e38-bd57813b6816 -WAIC_gom = -2*(lppd_gom - pWAIC_gom) - -# ╔═╡ 48dd67e7-363a-4e7c-ad81-8f70fc6db847 -[WAIC_log, WAIC_exp, WAIC_gom] - -# ╔═╡ a7fa7870-f202-4694-a9c1-7021ed242966 -posterior([WAIC_log, WAIC_exp, WAIC_gom]) - -# ╔═╡ 1699d3ae-d77a-4272-8c4b-ddd4eadd503f -md""" -!!! question - Why is the WAIC criterion significantly better for model selection despite its complexity? -""" - -# ╔═╡ 72878862-dcc0-4c79-9c91-7c0bc44359d3 -md""" -#### References -1. [https://en.wikipedia.org/wiki/Watanabe%E2%80%93Akaike_information_criterion](https://en.wikipedia.org/wiki/Watanabe%E2%80%93Akaike_information_criterion) -2. [https://civil.colorado.edu/~balajir/CVEN6833/bayes-resources/RM-StatRethink-Bayes.pdf](https://civil.colorado.edu/~balajir/CVEN6833/bayes-resources/RM-StatRethink-Bayes.pdf) -""" - -# ╔═╡ Cell order: -# ╠═5f4fea06-0632-11ef-102e-21f5606d2056 -# ╠═489b5399-fe4c-481a-834f-0101bbe28cea -# ╠═e1e7bc8e-7264-4cbc-98d2-aa73679fa2df -# ╠═ab4a1911-461e-4878-9258-931fc2f1ea06 -# ╠═cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 -# ╠═22489bd4-ab64-4bf9-ad03-5372ea273935 -# ╠═4ad72a6a-f541-4d0a-8392-56369e29ac96 -# ╠═70590d28-058a-44df-8cf6-092d8c87438c -# ╟─427b509f-d08e-4d93-99ba-a79f9c244b28 -# ╟─49e7085c-3691-4182-9630-68dc9371ad18 -# ╟─84caa99f-32ab-433c-b620-f592d329d18d -# ╟─7cc1d1ce-a34a-4432-953d-4eb2df92696f -# ╟─dcb51063-2b3f-4de9-90bc-0048e82cd4ab -# ╟─6690a844-8fd5-4ea1-9e62-a834bd454efb -# ╟─61e5bead-6fc3-4c07-b2b4-832ce7a69198 -# ╟─04e95855-7c4a-4d2c-b836-c5dde291adad -# ╟─4673bfdf-8f4a-42bd-a026-21a66d800f2b -# ╟─6e5f494e-ecfb-467b-a0f1-c85427da8215 -# ╠═0b3d35bb-c5b0-44b7-94b3-06fa571d339e -# ╟─d1c5a659-70c4-4768-9542-a2988ea39c43 -# ╟─583ed493-27e7-4fa7-b519-75cc71468fc6 -# ╠═d4ca6986-df24-48dd-8637-0974e0b16f2c -# ╟─8215b6f0-017e-4a73-b4bf-aa61b8f3ca6f -# ╠═2ff5f464-38d7-4a90-ad31-d19d412781fc -# ╟─b1d02f3c-f528-4297-a0c1-993cfe0d7c17 -# ╠═1499c727-ff19-4ef0-b467-bc71c1802979 -# ╟─c692b627-8743-48f6-bb26-b7d9b3d3b9c8 -# ╟─676119ce-f4fe-41f9-8121-b2c21f0dd28c -# ╟─5492210b-6ee6-4baf-b37e-d21358cdeb60 -# ╠═18eece66-46fa-4458-aaed-f4c8fa002c20 -# ╟─8bb0b24b-6cce-49cd-a625-5f375b92d9b7 -# ╠═ba181db8-d176-4b83-9168-d5939ffe9661 -# ╟─cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 -# ╠═cbb2ca49-b019-495b-9310-83fcc00cad26 -# ╟─be565a3c-31b6-4df1-b73b-08f308a8c09b -# ╠═f62806d1-77e1-470b-9711-33a924c788cc -# ╠═546ed163-26a7-4235-982f-7568ed609488 -# ╠═0da53fa2-5a42-46e6-8bd8-45d6aa903d46 -# ╟─b1298f40-4696-49d0-ac94-896e0cdbc996 -# ╠═270647d2-1371-4272-8bc1-3a6ad77bc716 -# ╠═ac235d86-1d93-4944-aa89-1b4fd38f0e6e -# ╠═3e13efa1-9bc6-456f-8e62-ecd3165e2a65 -# ╟─e2955af0-edf0-4702-9a8f-478141ffdc3b -# ╟─c73669c4-d7af-4877-b32d-6499f339e27a -# ╟─28e4a44f-e907-42ce-a748-6d21b60f0e33 -# ╠═169a67ff-55fb-4d1d-b98b-126f4af47e77 -# ╟─aa3e553d-2731-42d6-b0e6-1821e4d7f4d4 -# ╠═51426716-03b8-4d54-9064-3943df282fa4 -# ╟─f40873af-3ef9-420b-b0a8-ed9f56b17047 -# ╟─4536c6b7-f3bc-42e8-b044-0633f00b56bb -# ╠═bc55fb07-1d84-4927-80d2-c1a012409400 -# ╟─c224f1b8-e5a1-4379-ae50-981ae89f9eaa -# ╠═4fe2b4cd-d0b0-489a-a867-14c58637327b -# ╟─6cf5e2f2-344c-4a7a-8901-5a0ee9cb8689 -# ╠═551859cb-9cd1-4977-a528-b96423e83504 -# ╠═955524a6-1a53-4d2d-ad16-a5a3a2a39b80 -# ╠═118a79cc-afaf-4a99-8486-8e0e01fda40a -# ╟─8536cc9e-6cd4-4e38-9988-e45b06b75ea4 -# ╟─25db8d7b-e467-4a2f-b3f3-0ad7df64988a -# ╠═01241971-cc9a-42d6-8e52-c840b91e6431 -# ╟─e2db7492-4d68-4045-91b6-dca3aef8b514 -# ╠═7a890da6-7547-4854-b877-a5d935a9f2dd -# ╟─c95bcde6-ffb9-4ce6-9dfb-38c79cc2e4b8 -# ╠═bf90a66a-8505-4b33-b821-d708a3b7f8b0 -# ╠═7af67f91-58e2-429d-a88f-b27a0258a805 -# ╟─3ee0d4de-fdf5-40f0-a849-63b0759eb44b -# ╠═2c048fc0-5908-4f01-a9a5-480e12e098cc -# ╟─19ed4aa9-776e-4a80-b180-65d32f3f9f26 -# ╠═12a11f90-fa55-47f2-a75f-853cd54cbeab -# ╟─f6b44166-3ba9-4770-80db-303bd28112cf -# ╟─02037c91-448e-4714-b932-86f4df631907 -# ╟─cfea46a6-636c-4fcd-a04b-714e7a567cb3 -# ╠═cb9384a3-96b0-4694-a218-25ebf96187a9 -# ╟─73917400-43e0-4b9f-9448-9bbe02daebb9 -# ╠═afba526d-0f71-4ba5-9a84-3c24804f959a -# ╟─903380ce-1320-464e-a703-1222b4f381b1 -# ╠═22bd4450-6c6d-4a4d-aa1d-a080d3b06eac -# ╟─34f8cb7d-7482-4883-87e0-5c68be7ec0e9 -# ╟─4d976b86-74d6-4bb5-af17-8a6bd658abe2 -# ╠═ba7974ec-3965-4c92-ab37-08cdf5ab8125 -# ╟─5e6837d5-35e9-434c-9337-7c90daec9c33 -# ╠═b97ca16c-e6c3-4840-a855-1d29140eff59 -# ╠═1af53492-f363-4d39-bfaf-2f668a441725 -# ╟─e176e6ce-33da-4f3b-860e-4db99a931079 -# ╟─770e70d0-8a69-4585-a423-41335d453dcd -# ╟─f4f3b1fb-165d-4f77-88f1-8c1a79f3fb42 -# ╟─4a86d852-634c-43f5-90e5-3e675aeb2d69 -# ╟─8a43d4b8-1902-4e02-9cd7-c36131ec9528 -# ╠═d149d829-7d3d-4f7a-bdad-b1debb2c5149 -# ╠═e2bec1ab-f161-45b6-a67e-1f4f197ae685 -# ╟─5db6b1a5-ed2e-4812-94db-37cfffb46227 -# ╟─bc8d315a-ddfb-4c82-84d0-90a4fb05256c -# ╟─51b348ef-0270-41d0-bbcb-a27eb10ff5d2 -# ╠═7bf8fa3a-ce98-4f40-8d6c-4e84b79ae1c0 -# ╠═498e0b9b-17a9-4059-9196-c6c4d86f926e -# ╟─b57131ad-e9d6-4318-bca7-c59c5d8da4c3 -# ╠═f2fe206b-2832-42f6-84bd-20c3838fcb0b -# ╟─d51fff9b-17d3-4a0d-aa5e-01d58821a246 -# ╠═6d95d3b1-d964-4b61-adb6-f44875e23ecb -# ╠═b1783355-0221-4f2d-a050-932b40920eab -# ╠═3c69edb3-ba7d-4ee0-996a-40fd985e57d3 -# ╟─982e1de1-0820-42fb-8abb-31f1c99def46 -# ╟─79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 -# ╟─fa2270d0-2548-409c-a23f-4369d8bce8ec -# ╟─545a317d-8bca-4b6c-9915-84b0b4ffbed5 -# ╟─794b93c9-0a0a-4e77-b13c-0e2c06a9a0ec -# ╠═cfca0095-81e7-4c88-84bd-773eb5af7aa4 -# ╟─ba9162dd-27f4-42b3-a6b5-ca9531614d5e -# ╟─b1f03df5-4353-45ed-9d91-9e2c9960f706 -# ╠═89bf91c6-117c-4647-bfb9-6fc9b8dcfb5f -# ╟─b9a8c0cb-fe38-448e-aa4c-e9422f24a4a4 -# ╠═6b5fc852-1d40-45ce-a2e2-d22a6cc03156 -# ╟─29591722-bb44-47d0-a336-b2151502fd96 -# ╠═ec032fb5-76a7-4a0c-a8be-b0f6bd166580 -# ╟─d44ed37a-d022-457c-befe-01bae5cd0ed9 -# ╠═5d3f6390-1f71-4543-ae4e-d72f28b94f40 -# ╟─9eb6e432-3f41-4b5f-9194-241e6ab3a039 -# ╠═cc0fc9ba-5564-4104-b0b3-a10ae39ef933 -# ╠═6083305f-c838-4770-95c5-258fd7becbcb -# ╠═9777f3e8-785d-4ff5-a6c6-80a68a9c53ba -# ╟─e80de22e-d5e6-4af7-af0e-feaf9ef660bf -# ╟─0020352d-52dc-4ca4-90dd-f53865306f9e -# ╠═e58c49dd-5365-4dcb-9baa-c7a6048eb0ed -# ╟─03a35cce-cacf-4b11-a0b8-3a26ad93c227 -# ╠═b3556b32-4665-439e-8751-a3ffa8ba1907 -# ╟─ae030bbd-6416-44b8-ba9e-3fe9be5c9c52 -# ╠═bfd9d570-7b12-4bbf-aaa9-449f2377a294 -# ╠═f9e1ca7a-2a27-46aa-82a2-5e06882a9fad -# ╟─0e62cf17-f885-4c38-8df2-39d1719bfa04 -# ╠═fe3c059f-cb4f-4ddf-ad54-c08aa70bc740 -# ╟─fcfd18a2-154f-46fd-b4a5-01e406b500f1 -# ╠═78d5ad0e-318f-4af1-883b-b887f8d72591 -# ╟─58006718-5e76-4c6b-a968-aa5372bb4a15 -# ╟─7606cd16-7463-462e-83e6-5adaea4632a2 -# ╠═2379169c-7f2c-415c-8955-e8b06fc9edf5 -# ╠═577bb77e-d8c6-4121-84e6-1d3c000ce7b9 -# ╟─ac2d3b13-54d2-4217-8b18-7f9e276f1b6f -# ╠═2174d25b-b603-4d7b-b396-7ea00780143c -# ╠═0e8a6614-3164-4eab-b28b-fde3a328cbc3 -# ╠═f2805dab-cac4-4a31-8418-6763c14d6415 -# ╠═41d8cfe2-a997-4731-971e-3e0c6166177e -# ╠═2b2110eb-b867-47a1-8238-3d43a64851bb -# ╟─4fb53b80-4ea1-47a0-871a-adde769f6d18 -# ╠═22b58c97-1aa2-49c4-8a0a-488c63014a90 -# ╟─41abf8c4-e67e-4f66-a38a-7204a878d98d -# ╟─b02787ec-40eb-4000-9684-e69cbd912c6c -# ╟─e34fed6f-6b58-4642-84eb-167467881bb2 -# ╠═68895842-0a1b-4236-b159-19a2805ea44d -# ╟─ec12d3c4-6b48-4f3a-b463-0fc67d3adff3 -# ╠═1f8b10ba-f380-492e-800c-2673774cb25c -# ╟─82bee644-7b9a-42fa-a32f-76b3eb5038b5 -# ╠═e481b87c-c6f3-48c1-bfff-5d76de0dce36 -# ╟─a438470b-e037-4b65-b6b0-fb9c202e74a4 -# ╠═f4c3dcd0-70ec-49ab-bbad-04b550da481c -# ╟─df166b45-3c28-4bad-95b8-f43429a58fd0 -# ╠═a2738236-86a1-419a-9617-b1229f7c9240 -# ╟─59f78a22-cac1-49a7-b3e6-9d74b694be64 -# ╠═7c351d72-c5ce-4e5a-b2e5-87ff9dbc70e5 -# ╠═d7312308-7c95-4c14-9bd6-56a7ac99d49a -# ╠═8f8b23b3-2e33-46e4-917a-294934fa090e -# ╟─4ff1079f-8dfb-4069-9a98-0d263eba9920 -# ╟─9edd77c3-eb1e-4be7-bb6c-83803e675102 -# ╠═c02a4f18-d51d-4954-b7a4-a4ae64b10fb7 -# ╟─060b5869-881e-4d57-8357-cff95912ba16 -# ╠═0d225f18-bdf6-49d1-a827-2bc9551c158d -# ╟─5d27e199-ad09-454a-8966-1531371ce67b -# ╠═79c4439b-df8f-467a-b791-5581bd564996 -# ╠═bcbe7409-e969-4c7a-9417-195d406617bf -# ╟─6dbc81c8-4bc7-4499-a9d1-9719df368176 -# ╠═ef5270de-da37-4a01-8734-dac1fd190a05 -# ╠═b9030c3f-2cbb-44bf-90b7-23d116de4001 -# ╠═2e684fd0-8410-4037-a6a6-2c472d6592ab -# ╠═79797d6d-9ca8-4f4f-9694-8045a8cf8cbb -# ╠═2cd566bb-3088-43f8-8926-9952342b5e5c -# ╠═750edde4-16b5-4025-82e0-078c0eacaca9 -# ╠═1bbbd1ae-3540-44da-bc53-c8182a85b611 -# ╟─3afb2a69-32d6-42c2-8c1e-51e64b83a6b4 -# ╠═01ddf3dd-5199-48cf-ab13-75686278f3dc -# ╟─36f71777-ad33-4b27-9a0e-ff03b22ad79e -# ╠═d3b23128-2e7a-4246-a1f2-7325e0c2c432 -# ╟─a9e5b236-67d5-400b-ba9a-8b565f6feeb1 -# ╟─3bf0c957-f08b-4a45-90e0-40d56330c8e0 -# ╠═947e26fe-2010-48ad-b927-d4e5186f21e0 -# ╠═3506765b-a6cf-41ca-9201-fcc650c6f56d -# ╠═72dc27a0-0e61-4073-843a-67e1bfd1427a -# ╟─6240553c-0c7c-45f4-b190-ebacf09ab632 -# ╠═12f64333-36d0-4e2a-9061-e3dcc7a4ae96 -# ╟─95ba48fe-f430-4f47-ab04-f5896d75343b -# ╟─ef293617-7a7a-4597-99d2-2c39c543c296 -# ╠═8bbed35b-fec5-4682-8b45-99fd3bd55bf2 -# ╠═414ebb03-4fb7-4591-90e9-9ce3cf11e93d -# ╠═7a633bd6-10f1-4755-94ac-40d1dc9580a7 -# ╠═aef172f1-8563-4e51-8a88-ec6b764a735e -# ╠═091e978a-fd89-455a-9b97-f3088d580683 -# ╠═d3d6e099-66b0-444e-8142-ccd1718ac9ab -# ╟─2de0f619-63e0-444b-916b-316e4ebfa13e -# ╠═ef5329d2-ac63-4b49-a4cb-e8d6281a3184 -# ╟─0b8e6775-7562-4c93-b64d-cb694531f250 -# ╟─c99ac3a9-1d5e-43a3-a4ef-a22147187bb3 -# ╟─b620006e-d6ac-4038-960f-8d0d93c2de00 -# ╠═00dbb9d0-2c9d-4559-91d8-1a54171cc257 -# ╠═3d5e9bb7-5e65-4de5-a521-6b1f1cb872b3 -# ╟─3a3cd103-78b1-481a-8746-749d402346d1 -# ╟─b9986617-923e-4744-9d44-1935770edbd7 -# ╟─6e0c869d-6f90-4104-8589-41c2fa1b7342 -# ╟─6ca83977-cb8c-4465-ab2e-c91971f16157 -# ╠═fb76009b-3aaa-4eb5-8d69-45372a23af00 -# ╠═cfeb28b8-2dd7-4c1d-8069-e8fe00d90567 -# ╠═b9c59973-512b-48eb-a2b1-831449e120a4 -# ╟─56784bdf-2a78-4986-9dd2-790a2a2ceea9 -# ╠═bfb99943-1637-4dd6-adee-8a9e79274672 -# ╟─a08fb39b-3911-450e-8377-cc94398825a9 -# ╠═2575199f-a3ec-470f-87ea-d62be28c42dc -# ╠═6391d316-8826-4f19-9a2e-7065db787bee -# ╟─2879ff01-860f-4fb1-b52d-3a43e57ccb23 -# ╠═3b78c1ff-a2f6-4c1c-8124-5fe58c5a312d -# ╟─75e221b4-fdfe-4ef0-a8ea-1d75700fc1e7 -# ╠═0f228304-de82-48f2-8e43-6ef81f7a9830 -# ╠═9843e282-44b3-40d0-b2b5-506144c22b27 -# ╠═eb9fe668-0a0f-4a8f-a793-ba545cb02e85 -# ╠═ddf9ae6d-0abe-4c8c-9646-024dd9a41e02 -# ╠═8330fce5-13c9-447e-8368-01b7c7ebc68f -# ╠═0d2cab0c-04a2-4c8b-bb4f-2115ae0cf871 -# ╠═6900e703-de7d-4ea3-8ac5-0b38b21b083d -# ╠═b353eab2-d78f-4c5e-8e38-bd57813b6816 -# ╠═48dd67e7-363a-4e7c-ad81-8f70fc6db847 -# ╠═a7fa7870-f202-4694-a9c1-7021ed242966 -# ╟─1699d3ae-d77a-4272-8c4b-ddd4eadd503f -# ╟─72878862-dcc0-4c79-9c91-7c0bc44359d3 diff --git a/src/exercises/ode_model_XTRA_tank_T_h_mtk.jl b/src/exercises/ode_model_XTRA_tank_T_h_mtk.jl new file mode 100644 index 00000000..13faf513 --- /dev/null +++ b/src/exercises/ode_model_XTRA_tank_T_h_mtk.jl @@ -0,0 +1,342 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "6" +#> title = "1. ODE_model_Xtra_tank" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "extra exercises on modeling water height in a tank" + +using Markdown +using InteractiveUtils + +# ╔═╡ a6fbee1a-9863-11f0-1dfe-cf500206e163 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 21c2d9ad-9411-4e05-be3e-7f37deebc70d +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ eaa957df-d305-48fb-b2c2-4b4eb29a08be +using OrdinaryDiffEq, ModelingToolkit + +# ╔═╡ 5f3f3184-7bf8-4f40-80ca-bb48ad85e2f9 +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 0d12503e-5480-4ed8-9b4a-569b7801f855 +md""" +# Exercise: Cylindrical tank with cooling jacket +""" + +# ╔═╡ fdf5f147-5e6d-4730-8f16-ddc62ff9fcfe +solution(text) = Markdown.MD(Markdown.Admonition("hint", "Solution", [text])); + +# ╔═╡ a57a3436-1938-4ae7-bca3-a13534a28d44 +md""" +![](https://users.ugent.be/~gvhaelew/fig/tank_temp.png) +""" + +# ╔═╡ 82207f70-ca9a-409b-952d-520f3b8babc3 +md""" +## Deriving the model equations +""" + +# ╔═╡ defe58ce-d407-4ab4-a4e4-e37fc061149c +md""" +A cylindrical tank, surrounded by a cooling jacket, contains an initial amount of water (initial temperature $T_c$ and initial height $h_0$). The cooling jacket remains at a fixed temperature $T_c$. At a certain moment ($t=0$), a stream of warmer water with flow rate $Q_{in}$ and temperature $T_{in}$ enters the tank, and at the same moment a valve at the bottom of the tank is opened. The water in the tank is well mixed at all times. We are interested in the evolution of the temperature $T$ of the water in the tank and its height $h$. + +We denote $\rho$ as the mass density of the water, $c_p$ as the specific heat capacity of the water, $U_c$ the heat transfer coefficient, $V (= h \cdot A)$ the volume of the water in the tank and $Q_{out} (= \sqrt{h}/R)$ the outgoing flow at the bottom of the tank. The total amount of heat-energy of the water in the tank is $H= c_p \rho V T$ [in $J$]. + +The following quantities are important for setting-up the model equations: +- $c_p \rho Q_{in} T_{in}$ : rate of heat flow due to the incoming water [$Js^{-1}$] +- $c_p \rho Q_{out} T$ : rate of heat flow due to the outgoing water [$Js^{-1}$] +- $U_c (A + \pi d h) (T - T_c)$ : rate of heat transfer due to the cooling jacket [$Js^{-1}$] + +For the last quantity, we have assumed that the heat transfer only occurs at the side-walls and bottom where the water is in direct contact with the tank. Hence, no heat transfer at the water surface inside the tank. +""" + +# ╔═╡ 1992f00e-c2a6-479b-a81b-d272eeb2dbed +md""" +!!! task + Derive the model equations for the total heat-energy $H$ and the volume $V$ of the water inside the tank. +""" + +# ╔═╡ 10c49e36-f058-4803-8dd5-a3ea2c22602d +md""" +!!! hint + - Use the 'important' quantities mentioned above. + - Set up: + ``` math + \begin{align} + H &= c_p \rho V T \\ + V &= h A \\ + \frac{d H}{dt} &= \dots \\ + \frac{d V}{dt} &= \dots + \end{align} + ``` + - Think well before putting a $+$ or $-$ sign before the terms while setting-up the differential equations! +""" + +# ╔═╡ 27539311-aaf7-4b94-87cf-dfc4f4d2bcc6 +solution(md""" +``` math +\begin{align} + H &= c_p \rho V T \\ + V &= h A \\ + \frac{d H}{dt} &= c_p \rho Q_{in} T_{in} - c_p \rho \cfrac{\sqrt{h}}{R} T - U_c (A + \pi d h) (T - T_c)\\ + \cfrac{dV}{dt} &= Q_{in} - \cfrac{\sqrt{h}}{R} +\end{align} +``` +""") + +# ╔═╡ 50bbe739-85fb-4403-b49a-2c210ab95701 +md""" +## Setting up the equations +""" + +# ╔═╡ de867d6d-b327-4e6f-a03d-9fb8f45dab7e +md""" +Define variables for the temperature $T$, the height $h$, the total heat-energy $H$ and the volume $V$ of the water in the tank. Use the following variable names: `T`, `h`, `H` and `V`. Mention the dependency on the time $t$. +""" + +# ╔═╡ 3efd0cbd-293e-421d-b469-802486709698 +# @variables missing + +# ╔═╡ bf1b6e89-5c90-4327-9a26-066feb2a51e4 +md""" +Define the parameters for this model and assign their corresponding values. + +| Parameter | Value | Unit | Meaning | +|:---------- |:---------- |:------------|:------------| +| $A$ | 0.283 | $m^2$ | cross sectional area | +| $d$ | 0.6 | $m$ | diameter of the tank | +| $\rho$ | 1000.0 | $kg/m^3$ | water density | +| $Q_{in}$ | 0.01 | $m^3/s$ | inlet flow | +| $T_{in}$ | 30.0 | $^{\circ}C$ | temperature of the incoming water stream | +| $U_c$ | 4000 | $J/(s m^2\,^{\circ}C)$ | heat transfer coefficient | +| $T_c$ | 15.0 | $^{\circ}C$ | temperature of the cooling water | +| $c_p$ | 4200 | $J/(kg ^{\circ}C)$ | heat capacity | +| $R$ | 200 | $s/m^{5/2}$ | res. coeff. orifice | +""" + +# ╔═╡ cb5b651d-4e26-4136-a8e2-cadbd75234db +# @parameters missing + +# ╔═╡ 3f94b45e-284f-4d09-9b63-8bd11215f148 +md""" +Set up the equation for total heat-energy, the volume, the rate of change in the total heat energy and the rate of change in volume. +""" + +# ╔═╡ f8cdf897-6fca-41e6-92c9-ae2a7cc36eb8 +eq_heat = missing + +# ╔═╡ 2fae0b7b-1440-4adb-9531-1652b89d6f64 +eq_volume = missing + +# ╔═╡ 1c5707fa-cb02-49d4-bc8a-d17bb7055ac7 +change_heat = missing + +# ╔═╡ 70e23f70-aac4-46e6-b20c-18947e0153d7 +change_volume = missing + +# ╔═╡ 2d03afaf-55b9-4eab-8a41-5581468d97e1 +md""" +Bundle the equations. +""" + +# ╔═╡ 6052c938-48d5-4bf4-82ac-43eb42aca0ff +eqns_tank = missing + +# ╔═╡ bc764684-0ef8-4925-b874-8479affd6d12 +md""" +## Part 1: simple simulation +""" + +# ╔═╡ 370763e7-ff6c-4713-ac52-c67df0607da0 +md""" +### Building the ODE system +""" + +# ╔═╡ 016fcba0-d53a-4dd0-abfe-22460a70b8f9 +md""" +Build the model. Name it `sys1_tank`. +""" + +# ╔═╡ 38cd75e9-4820-4e5e-a692-5d684f883c24 +# @mtkbuild missing + +# ╔═╡ 6909277e-7c4e-4d79-aaa0-3eeff6841998 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ c1d1f8a4-8934-43ff-9b35-8c291dfe016d +md""" +Create the ODE problem. Take as initial temperature of the water `15.0` $^{\circ}C$ and initial height of the water `0.8` $m$. Simulate for `2400.0`$s$. +""" + +# ╔═╡ 3ab78b19-7d18-42d7-9962-11da4d46f5c7 +oprob1_tank = missing + +# ╔═╡ fcae1777-2c2f-4ad0-ae9c-a60ff6504de7 +md""" +Solve the ODE problem. Use `saveat=1` and `reltol=1e-9`. +""" + +# ╔═╡ bcb70e32-49ac-4d04-91cf-92f8f5d6548e +sol1_tank = missing + +# ╔═╡ 9b0988e7-4546-4a98-a0fe-325b1e874f93 +md""" +### Plotting results +""" + +# ╔═╡ 9117e5c7-0617-4cec-8c29-5d362fa2eb16 +md""" +Plot the temperature (with the option `idxs=[T]`) and the height (with the option `idxs=[h]`). Use `twinx()` as first argument in the second plot instruction to plot the height on the right y-axis. +""" + +# ╔═╡ ff0cd780-ea3c-407f-a18a-9404ba208b68 +# begin +# plot(missing) +# plot!(missing) +# end + +# ╔═╡ 3cee6515-f9b8-4435-aa1a-bf93f976a618 +md""" +!!! question + Why do you think the temperature first rises, goes to a maximum and then decreases to go to an equilibrium value? +""" + +# ╔═╡ b27bbc52-9131-467e-85c5-03f9b0ee2e78 +md""" +Answers: missing +""" + +# ╔═╡ 75d483ad-d983-487e-901c-40d3e530780d +md""" +## Part 2: effect of the resistance coefficient of the orifice +""" + +# ╔═╡ afb92363-2ae7-4106-a829-d20bbf8c49da +md""" +In this part the resistance coefficient of the orifice will be increased to `250` at the time instant of `1200.0` $s$. In order to achieve this a discrete event will be used. +""" + +# ╔═╡ 55cd8777-593d-4733-b740-ab4e0ab19724 +md""" +### Building the ODE system +""" + +# ╔═╡ b968af64-6cbb-4e01-b290-1eb68b3564f9 +md""" +Build the new model that includes the discrete event. Name it `sys2_tank`. +""" + +# ╔═╡ 09cab085-e5b4-4030-a027-d1ffe9cdbdc0 +# @mtkbuild missing + +# ╔═╡ f1965bf2-6985-453e-bdba-617245c6d705 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ f3bc477c-3044-413b-b1d5-e84f485106a3 +md""" +Create the new ODE problem. Use the same initial values and simulation time as in the previous part. +""" + +# ╔═╡ 291c2204-5026-425c-a431-2b27946cf72a +oprob2_tank = missing + +# ╔═╡ d762e7f2-c030-49a4-865a-139d648e42b1 +md""" +Solve the ODE problem. Use `saveat=1` and `reltol=1e-9`. Don't forget to take a `deepcopy` of the ODE problem. +""" + +# ╔═╡ 50719433-3e82-4614-ad19-b1610fe23916 +sol2_tank = missing + +# ╔═╡ 671696da-42ed-4d08-8234-aeea74e29a11 +md""" +### Plotting results +""" + +# ╔═╡ e5b36ae7-0a44-4c90-bc67-8f35e51d6ea7 +md""" +Plot the temperature and the height. Use `twinx()` as first argument in the second plot instruction to plot the height on the right y-axis. +""" + +# ╔═╡ 41a61694-551d-4034-879e-67066385158c +# begin +# plot() +# plot!() +# end + +# ╔═╡ 7f9b877b-eab3-4df5-a5a6-55fee459f272 +md""" +!!! question + Is the evolution of $T$ and $h$ after the increase of $R$ according to your exceptations? Explain it. +""" + +# ╔═╡ cf9f3ddf-f1ce-4bf8-b42b-18c53ccdc6d6 +md""" +Answer: missing +""" + +# ╔═╡ Cell order: +# ╟─0d12503e-5480-4ed8-9b4a-569b7801f855 +# ╠═a6fbee1a-9863-11f0-1dfe-cf500206e163 +# ╠═21c2d9ad-9411-4e05-be3e-7f37deebc70d +# ╠═eaa957df-d305-48fb-b2c2-4b4eb29a08be +# ╠═5f3f3184-7bf8-4f40-80ca-bb48ad85e2f9 +# ╟─fdf5f147-5e6d-4730-8f16-ddc62ff9fcfe +# ╟─a57a3436-1938-4ae7-bca3-a13534a28d44 +# ╟─82207f70-ca9a-409b-952d-520f3b8babc3 +# ╟─defe58ce-d407-4ab4-a4e4-e37fc061149c +# ╟─1992f00e-c2a6-479b-a81b-d272eeb2dbed +# ╟─10c49e36-f058-4803-8dd5-a3ea2c22602d +# ╟─27539311-aaf7-4b94-87cf-dfc4f4d2bcc6 +# ╟─50bbe739-85fb-4403-b49a-2c210ab95701 +# ╟─de867d6d-b327-4e6f-a03d-9fb8f45dab7e +# ╠═3efd0cbd-293e-421d-b469-802486709698 +# ╟─bf1b6e89-5c90-4327-9a26-066feb2a51e4 +# ╠═cb5b651d-4e26-4136-a8e2-cadbd75234db +# ╟─3f94b45e-284f-4d09-9b63-8bd11215f148 +# ╠═f8cdf897-6fca-41e6-92c9-ae2a7cc36eb8 +# ╠═2fae0b7b-1440-4adb-9531-1652b89d6f64 +# ╠═1c5707fa-cb02-49d4-bc8a-d17bb7055ac7 +# ╠═70e23f70-aac4-46e6-b20c-18947e0153d7 +# ╟─2d03afaf-55b9-4eab-8a41-5581468d97e1 +# ╠═6052c938-48d5-4bf4-82ac-43eb42aca0ff +# ╟─bc764684-0ef8-4925-b874-8479affd6d12 +# ╟─370763e7-ff6c-4713-ac52-c67df0607da0 +# ╟─016fcba0-d53a-4dd0-abfe-22460a70b8f9 +# ╠═38cd75e9-4820-4e5e-a692-5d684f883c24 +# ╟─6909277e-7c4e-4d79-aaa0-3eeff6841998 +# ╟─c1d1f8a4-8934-43ff-9b35-8c291dfe016d +# ╠═3ab78b19-7d18-42d7-9962-11da4d46f5c7 +# ╟─fcae1777-2c2f-4ad0-ae9c-a60ff6504de7 +# ╠═bcb70e32-49ac-4d04-91cf-92f8f5d6548e +# ╟─9b0988e7-4546-4a98-a0fe-325b1e874f93 +# ╟─9117e5c7-0617-4cec-8c29-5d362fa2eb16 +# ╠═ff0cd780-ea3c-407f-a18a-9404ba208b68 +# ╟─3cee6515-f9b8-4435-aa1a-bf93f976a618 +# ╠═b27bbc52-9131-467e-85c5-03f9b0ee2e78 +# ╟─75d483ad-d983-487e-901c-40d3e530780d +# ╟─afb92363-2ae7-4106-a829-d20bbf8c49da +# ╟─55cd8777-593d-4733-b740-ab4e0ab19724 +# ╟─b968af64-6cbb-4e01-b290-1eb68b3564f9 +# ╠═09cab085-e5b4-4030-a027-d1ffe9cdbdc0 +# ╟─f1965bf2-6985-453e-bdba-617245c6d705 +# ╟─f3bc477c-3044-413b-b1d5-e84f485106a3 +# ╠═291c2204-5026-425c-a431-2b27946cf72a +# ╟─d762e7f2-c030-49a4-865a-139d648e42b1 +# ╠═50719433-3e82-4614-ad19-b1610fe23916 +# ╟─671696da-42ed-4d08-8234-aeea74e29a11 +# ╟─e5b36ae7-0a44-4c90-bc67-8f35e51d6ea7 +# ╠═41a61694-551d-4034-879e-67066385158c +# ╟─7f9b877b-eab3-4df5-a5a6-55fee459f272 +# ╠═cf9f3ddf-f1ce-4bf8-b42b-18c53ccdc6d6 diff --git a/src/exercises/ode_model_XTRA_temp_reactors_mtk.jl b/src/exercises/ode_model_XTRA_temp_reactors_mtk.jl new file mode 100644 index 00000000..64d8d4c8 --- /dev/null +++ b/src/exercises/ode_model_XTRA_temp_reactors_mtk.jl @@ -0,0 +1,413 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "7" +#> title = "1. ODE_model_temp_reactor" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "modeling temperature in a CSTR" + +using Markdown +using InteractiveUtils + +# ╔═╡ 7d61614c-9e15-11f0-1ce8-af091a3beb9a +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 2e418e46-84fd-4922-9985-a92a412c1fb1 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ d0a12f36-2592-4c3f-9581-6e41fbe5117f +using OrdinaryDiffEq, ModelingToolkit + +# ╔═╡ 225d31d2-a974-4494-8813-c93484ce721e +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 4bf49e49-7675-46eb-acfd-603dd68ac027 +md""" +# Exercise: Temperature evolution in a set of reactors +""" + +# ╔═╡ 0e4065c2-281c-439a-a69b-e4eca3b5ae41 +solution(text) = Markdown.MD(Markdown.Admonition("hint", "Solution", [text])); + +# ╔═╡ 7e2a3f8c-5f1c-4357-ae92-f4c1c677fde3 +md""" +## Deriving the model equations +""" + +# ╔═╡ 9e8c3a6b-6d69-4f9b-a8ea-de732de886ca +md""" +Consider two reactors in series with constant volumes of liquid $V_{1}$ and $V_{2}$ in a room at a constant ambient temperature $T_a$. A fluid with temperature $T_{in}$, adjustable by the control engineer, flows through both reactors at a constant flow rate $Q$. The temperature of the fluid in the first and second reactors are $T_1$ and $T_2$ respectively. The heat loss through the walls of the reactors is proportional to the wall areas $A_1$ (first reactor) and $A_2$ (second reactor). The heat transfer coefficient of both reactor walls is $\lambda$. In the second reactor a heater with an adjustable power $P_h$ delivers heat to the fluid. The density of the fluid is $\rho$ and its thermal heat capacity is $c_p$. +""" + +# ╔═╡ 6a5cc6f2-b6b8-40fe-9b52-1a3e2d5d4fb7 +# PlutoUI.LocalResource("fig/temperature_reactors.png") +md""" +![Temperature reators](https://users.ugent.be/~gvhaelew/fig/temperature_reactors.png) +""" + +# ╔═╡ d1480cba-5e0f-4767-8164-b8ed5291e888 +md""" +The following quantities are important for setting-up the model equations: +- $c_p \rho V_1 T_1$: the amount of heat-energy in reactor 1 +- $c_p \rho V_2 T_2$: the amount of heat-energy in reactor 2 +- $c_p \rho Q \left( T_{in} - T_1 \right)$: rate of heat flow entering reactor 1 +- $c_p \rho Q \left( T_{1} - T_2 \right)$: rate of heat flow entering reactor 2 +- $\lambda A_1 \left(T_1 - T_a\right)$: rate of heat flow dissipating from reactor 1 +- $\lambda A_2 \left(T_2 - T_a\right)$: rate of heat flow dissipating from reactor 2 +- $P_h$: rate of heat supplied to reactor 2 +""" + +# ╔═╡ 826b1cda-3fcc-46e6-b186-d031929a569b +md""" +!!! task + Derive the model equations for the fluid temperatures $T_1$ and $T_2$. +""" + +# ╔═╡ 409e4f75-0cc7-4f2b-96c6-ca94a10ee22f +md""" +!!! hint + - Use the 'important' quantities mentioned above. + - Set up: + ``` math + \begin{align} + c_p\,\rho\,V_1\,\cfrac{dT_1}{dt} &= \dots \\ + c_p\,\rho\,V_2\,\cfrac{dT_2}{dt} &= \dots + \end{align} + ``` + - Think well before putting a $+$ or $-$ sign before the terms while setting-up the differential equations! +""" + +# ╔═╡ 41ed50f3-9270-4563-b31f-30351f07468b +solution(md""" +``` math +\left\{ +\begin{array}{l} +c_p\,\rho\,V_1\,\cfrac{dT_1}{dt} &= c_p\,\rho\,Q \left( T_{in} - T_1 \right) - \lambda\,A_1 \left(T_1 - T_a\right) \\ +c_p\,\rho\,V_2\,\cfrac{dT_2}{dt} &= c_p\,\rho\,Q \left( T_1 - T_2 \right) - \lambda\,A_2 \left(T_2 - T_a\right) + P_h +\end{array} +\right. +``` +""") + +# ╔═╡ bdabfb0c-66ff-4cb7-a61f-f31fb49eeb34 +md""" +Temperature sensors measures the fluid temperatures $T_1$ and $T_2$ in both reactors. We are interested in the evolution of $T_1$ and $T_2$ as a function of time. +""" + +# ╔═╡ f4431fbe-2823-4ce5-ac35-16f51879d393 +md""" +## Setting up the equations +""" + +# ╔═╡ ceb468ea-c05d-4938-bc95-31256af53a24 +md""" +The values of the parameters are listed here + +| Parameter | Value | Unit | Meaning | +|:---------- |:---------- |:------------|:------------| +| `Q` | 0.05e-3 | $m^3/s$ | flow rate | +| `V₁` | 0.004 | $m^3$ | volume of liquid in reactor 1 | +| `V₂` | 0.006 | $m^3$ | volume of liquid in reactor 2 | +| `Tin` | 40.0 | $^{\circ}C$ | temperature of incoming liquid | +| `λ` | 240 | $W/(m^2\,^{\circ}C)$ | heat transfer coefficient | +| `A₁` | 0.1 | $m^2$ | wall area of reactor 1 | +| `A₂` | 0.2 | $m^2$ | wall area of reactor 2 | +| `cₚ` | 4200 | $J/(kg\,^{\circ}C$ | thermal heat capacity of fluid | +| `ρ` | 1000 | $kg/m^3$ | density of fluid | +| `Tₐ` | 20.0 | $^{\circ}C$ | ambient temperature | +| `Pₕ` | 5000 | $W$ | power of heater (in reactor 2) | + +The parameters $T_{in}$ and $P_{h}$ will be called *manipulable parameters* since they can be (suddenly) changed by a control engineer. + +""" + +# ╔═╡ c76afb2e-a12a-4540-a622-c00304cce2e4 +md""" +Define the variables for the temperature in both reactors. Use `T₁` and `T₂`. +""" + +# ╔═╡ b60730ff-a3de-4bd0-b074-6f4ee0b35957 +# @variables + +# ╔═╡ 6b189311-3fa1-4a81-b65a-74e2d972b047 +md""" +Define the parameters for this model and assign their corresponding values. +""" + +# ╔═╡ 07e57de7-6ac1-4351-b2c2-fd60212c15b0 +# @parameters + +# ╔═╡ 01a57415-a5f1-47be-b164-6b951d7ae6e7 +md""" +Set up the equation that describes the change in $T_1$. +""" + +# ╔═╡ ef9b10d8-c1df-41aa-a7c6-1b1fbc0b727c +eq_T1 = missing + +# ╔═╡ bb57ddca-de84-46cf-9751-88b9501a30d5 +md""" +Set up the equation that describes the change in $T_2$. +""" + +# ╔═╡ 420b93da-59bc-4aa0-af4c-3e9df7d00107 +eq_T2 = missing + +# ╔═╡ 12f793e3-6c33-4394-8b24-2aa046320943 +md""" +Bundle both equations. +""" + +# ╔═╡ d2160d11-f0d3-40cb-a921-7668af9d5b31 +eqns_reactors = missing + +# ╔═╡ 18b41ac9-0795-46e5-a9bb-9abb7695c601 +md""" +## Part 1: simple simulation +""" + +# ╔═╡ 30bd37c4-16b1-48c8-ad51-f3fdcc34bda3 +md""" +In this part we will assume that the manipulable parameters remain constant with values as listed before. +""" + +# ╔═╡ 4995f295-ca60-4035-8cb3-f3607d4ad3c4 +md""" +### Building the ODE system +""" + +# ╔═╡ 8748f2eb-2a02-426e-b71b-166298a2667f +md""" +Build the model. Name it `sys1_reactors`. +""" + +# ╔═╡ de8c6064-8809-4aee-8809-b7cc0b07122f +# @mtkbuild missing + +# ╔═╡ 9bfc0267-074b-43cf-b199-52cc0e4a0f46 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ 8f9d348e-d1ea-484d-a5e8-6174a7ce858b +md""" +Create the ODE problem. Assume an initial temperature of 15.0 $^{\circ}C$ for $T_1$ and $T_2$ and simulate during 600.0 seconds. Use `[]` for the parameters argument since their values have been set before. +""" + +# ╔═╡ 85c9b5e6-3ca5-4e93-bdce-4cdf690d44b8 +oprob1_reactors = missing + +# ╔═╡ b10799d3-3da8-4f8c-af02-9e57448cbcf4 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=1`. +""" + +# ╔═╡ a5c54103-1fc9-4358-9a58-eb6bd88e7d18 +sol1_reactors = missing + +# ╔═╡ 09b21e52-1648-42e8-90a9-a0719a6440df +md""" +### Plotting results +""" + +# ╔═╡ 01c1fced-1852-422b-ab14-32751f903495 +md""" +Make a plot of $T_1$ and $T_2$ over time. +""" + +# ╔═╡ 14701ae1-99c6-47f9-ab59-e880ae3ea0d4 +missing + +# ╔═╡ 9d1b4080-300f-496d-aa40-af8bc6c080b1 +md""" +Retrieve the final values of $T_1$ and $T_2$ in the solution (i.e., at $t=600\;s$). +""" + +# ╔═╡ b1077d9d-8523-4ad4-83bf-bcee0dc11b26 +missing + +# ╔═╡ 6d05657b-24f2-49a3-b568-bd25e52394e6 +missing + +# ╔═╡ 36d951d4-d354-450d-8dbe-1b3a088fcef0 +md""" +### Calculating steady state values +""" + +# ╔═╡ f0e79793-5862-4806-9865-f18323d4577a +md""" +Calculate the steady state values using `SteadyStateProblem` and `solve`. Use the above retrieved final values as a first guess. +""" + +# ╔═╡ a2d2ae7a-9c55-4a3a-9428-8376fdf5ed8b +equil_T_vals = missing + +# ╔═╡ 9c7a9403-c0f9-4500-b84c-1478d72269eb +md""" +Display the steady state values. +""" + +# ╔═╡ 25876e54-adfe-4f2b-b844-7e19b3820868 +missing + +# ╔═╡ 572aa97b-ea95-453b-8c27-d7f579ee8823 +missing + +# ╔═╡ a2725289-11ff-408e-8375-17f2b54f3a4f +md""" +!!! questions + 1. Why is the steady state value of $T_1$ not equal to $T_{in}$? + 2. Why is the steady state value of $T_2$ much larger than $T_1$? +""" + +# ╔═╡ 5af588fa-f2e5-4b25-b431-cb93e0683ba7 +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ a76bb35a-f405-4fc8-b8fb-58d513f86b2e +md""" +## Part 2: sudden change in $T_{in}$ and $P_h$ +""" + +# ╔═╡ e71032db-1e3e-48a5-b660-2b15dcbf8480 +md""" +In this part we will assume that both manipulable parameters $T_{in}$ and $P_h$ suddenly change at some point in time: +- `Tin` changes from 40.0 $^{\circ}C$ to 45.0 $^{\circ}C$, and +- `Pₕ` changes from 5000 $W$ to 2250 $W$, +at $t =$ 600 seconds. +""" + +# ╔═╡ 15a37c5f-921c-4543-b605-cf89fdb898a2 +md""" +### Building the ODE system +""" + +# ╔═╡ 2dc76e23-723e-4c1d-a703-0cb7fd0edfa0 +md""" +Build a new model where you include both discrete events. Name it `sys2_reactors`. +""" + +# ╔═╡ cb30b1f0-6fbb-4c11-9cfa-ee8ed3ebc74a +# @mtkbuild missing + +# ╔═╡ 0b13a875-6c68-4ca9-812e-fc84989d70d4 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ b9aa284a-8058-4b76-ac40-7aefbb43f377 +md""" +Create the ODE problem. Take the same initial values for the temperatures as before but now simulate for 1200 seconds. +""" + +# ╔═╡ 0ea3b443-96d3-4291-9cac-266735b23454 +oprob2_reactors = missing + +# ╔═╡ 5c59de0c-31d2-41b1-b89a-c77cdca2c322 +md""" +Solve the ODE problem. Make a deepcopy of the ODE problem, use `Tsit5()` and `saveat=1`. +""" + +# ╔═╡ 1eaa2ae5-4d1f-4c24-9358-3cd150ab7ccd +sol2_reactors = missing + +# ╔═╡ 6820dc35-d1e4-45e2-b5b0-664a62e172b8 +md""" +### Plotting results +""" + +# ╔═╡ 19acc5b0-484a-4a36-8a3b-a59421848848 +md""" +Make a plot of $T_1$ and $T_2$ over time. +""" + +# ╔═╡ b9774204-0cb5-47c0-9a5c-f3508ff86435 +missing + +# ╔═╡ 21b22b69-1aeb-49b7-ab7f-075f978e1acb +md""" +!!! question + 1. Is the variable $T_1$ affected by (A) only $T_{in}$, (B) only $P_h$, (C) both $T_{in}$ and $P_h$, or (D) none of them? Try to reason using the model equation for the rate of change of $T_1$. + 2. Is the variable $T_2$ affected by (A) only $T_{in}$, (B) only $P_h$, (C) both $T_{in}$ and $P_h$, or (D) none of them? Try to reason using the model equation for the rate of change of $T_2$. +""" + +# ╔═╡ 18f1bdd7-da96-49d7-96d7-fe456818cd4b +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ Cell order: +# ╟─4bf49e49-7675-46eb-acfd-603dd68ac027 +# ╠═7d61614c-9e15-11f0-1ce8-af091a3beb9a +# ╠═2e418e46-84fd-4922-9985-a92a412c1fb1 +# ╠═d0a12f36-2592-4c3f-9581-6e41fbe5117f +# ╠═225d31d2-a974-4494-8813-c93484ce721e +# ╟─0e4065c2-281c-439a-a69b-e4eca3b5ae41 +# ╟─7e2a3f8c-5f1c-4357-ae92-f4c1c677fde3 +# ╟─9e8c3a6b-6d69-4f9b-a8ea-de732de886ca +# ╟─6a5cc6f2-b6b8-40fe-9b52-1a3e2d5d4fb7 +# ╟─d1480cba-5e0f-4767-8164-b8ed5291e888 +# ╟─826b1cda-3fcc-46e6-b186-d031929a569b +# ╟─409e4f75-0cc7-4f2b-96c6-ca94a10ee22f +# ╟─41ed50f3-9270-4563-b31f-30351f07468b +# ╟─bdabfb0c-66ff-4cb7-a61f-f31fb49eeb34 +# ╟─f4431fbe-2823-4ce5-ac35-16f51879d393 +# ╟─ceb468ea-c05d-4938-bc95-31256af53a24 +# ╟─c76afb2e-a12a-4540-a622-c00304cce2e4 +# ╠═b60730ff-a3de-4bd0-b074-6f4ee0b35957 +# ╟─6b189311-3fa1-4a81-b65a-74e2d972b047 +# ╠═07e57de7-6ac1-4351-b2c2-fd60212c15b0 +# ╟─01a57415-a5f1-47be-b164-6b951d7ae6e7 +# ╠═ef9b10d8-c1df-41aa-a7c6-1b1fbc0b727c +# ╟─bb57ddca-de84-46cf-9751-88b9501a30d5 +# ╠═420b93da-59bc-4aa0-af4c-3e9df7d00107 +# ╟─12f793e3-6c33-4394-8b24-2aa046320943 +# ╠═d2160d11-f0d3-40cb-a921-7668af9d5b31 +# ╟─18b41ac9-0795-46e5-a9bb-9abb7695c601 +# ╟─30bd37c4-16b1-48c8-ad51-f3fdcc34bda3 +# ╟─4995f295-ca60-4035-8cb3-f3607d4ad3c4 +# ╟─8748f2eb-2a02-426e-b71b-166298a2667f +# ╠═de8c6064-8809-4aee-8809-b7cc0b07122f +# ╟─9bfc0267-074b-43cf-b199-52cc0e4a0f46 +# ╟─8f9d348e-d1ea-484d-a5e8-6174a7ce858b +# ╠═85c9b5e6-3ca5-4e93-bdce-4cdf690d44b8 +# ╟─b10799d3-3da8-4f8c-af02-9e57448cbcf4 +# ╠═a5c54103-1fc9-4358-9a58-eb6bd88e7d18 +# ╟─09b21e52-1648-42e8-90a9-a0719a6440df +# ╟─01c1fced-1852-422b-ab14-32751f903495 +# ╠═14701ae1-99c6-47f9-ab59-e880ae3ea0d4 +# ╟─9d1b4080-300f-496d-aa40-af8bc6c080b1 +# ╠═b1077d9d-8523-4ad4-83bf-bcee0dc11b26 +# ╠═6d05657b-24f2-49a3-b568-bd25e52394e6 +# ╟─36d951d4-d354-450d-8dbe-1b3a088fcef0 +# ╟─f0e79793-5862-4806-9865-f18323d4577a +# ╠═a2d2ae7a-9c55-4a3a-9428-8376fdf5ed8b +# ╟─9c7a9403-c0f9-4500-b84c-1478d72269eb +# ╠═25876e54-adfe-4f2b-b844-7e19b3820868 +# ╠═572aa97b-ea95-453b-8c27-d7f579ee8823 +# ╟─a2725289-11ff-408e-8375-17f2b54f3a4f +# ╠═5af588fa-f2e5-4b25-b431-cb93e0683ba7 +# ╟─a76bb35a-f405-4fc8-b8fb-58d513f86b2e +# ╟─e71032db-1e3e-48a5-b660-2b15dcbf8480 +# ╟─15a37c5f-921c-4543-b605-cf89fdb898a2 +# ╟─2dc76e23-723e-4c1d-a703-0cb7fd0edfa0 +# ╠═cb30b1f0-6fbb-4c11-9cfa-ee8ed3ebc74a +# ╟─0b13a875-6c68-4ca9-812e-fc84989d70d4 +# ╟─b9aa284a-8058-4b76-ac40-7aefbb43f377 +# ╠═0ea3b443-96d3-4291-9cac-266735b23454 +# ╟─5c59de0c-31d2-41b1-b89a-c77cdca2c322 +# ╠═1eaa2ae5-4d1f-4c24-9358-3cd150ab7ccd +# ╟─6820dc35-d1e4-45e2-b5b0-664a62e172b8 +# ╟─19acc5b0-484a-4a36-8a3b-a59421848848 +# ╠═b9774204-0cb5-47c0-9a5c-f3508ff86435 +# ╟─21b22b69-1aeb-49b7-ab7f-075f978e1acb +# ╠═18f1bdd7-da96-49d7-96d7-fe456818cd4b diff --git a/src/exercises/ode_model_XTRA_water_evap_infil_mtk.jl b/src/exercises/ode_model_XTRA_water_evap_infil_mtk.jl new file mode 100644 index 00000000..a7af0b30 --- /dev/null +++ b/src/exercises/ode_model_XTRA_water_evap_infil_mtk.jl @@ -0,0 +1,331 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "8" +#> title = "1. ODE_model_Xtra_evaporation" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "modeling evaporation and infiltration in ground" + +using Markdown +using InteractiveUtils + +# ╔═╡ e079823b-8b40-42a2-a63f-1645a97b33f0 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 62dc7706-f58a-11ee-2d3d-f78f7ceca914 +using Markdown; InteractiveUtils; + +# ╔═╡ 65571bb8-e260-4a82-b0d2-198e47c56271 +using StatsPlots, PlutoUI + +# ╔═╡ 5e79d359-393b-405a-847e-dbdec15a1848 +using OrdinaryDiffEq, ModelingToolkit + +# ╔═╡ cd21ad3c-94b8-4031-8fc0-17a5a39043f4 +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 935787f8-2aa3-477b-8c91-c9c3b0f08d3d +md""" +# Exercise: Water evaporation and infiltration +""" + +# ╔═╡ 66e8a12c-74b6-4077-b90e-3d85e5a61d6e +solution(text) = Markdown.MD(Markdown.Admonition("hint", "Solution", [text])); + +# ╔═╡ 7a83f3cd-da5b-46d4-a1c7-9fe14c2122d0 +md""" +![](https://users.ugent.be/~gvhaelew/fig/water_evap_infil_model.png) +""" + +# ╔═╡ f55e0ca3-07ff-43a1-907f-9ad640227822 +md""" +Consider a water reservoir, such as a lake, where the water in the reservoir is in contact with the air as well as with the groundwater. We will denote the water level in the reservoir as $W$ and the groundwater level as $G$. + +The water in the reservoir evaporates at a rate $k_1$ (i.e. the *evaporation coefficient*) and there can be infiltration into or from the groundwater at a rate $k_2$ (i.e., *infiltration coefficient*) depending on the difference in the water level in the reservoir and groundwater (cf. $(W-G)$) + +There is a natural constant inflow of water into the reservoir at a rate $I$. At time $t=0\;s$ a pumping device is switched on such that the reservoir is rapidly being emptied at an outflow rate $O$ until the level of the water reservoir drops to zero. From then on, the pump is switched off. +""" + +# ╔═╡ a551f3c5-2fc3-4236-ab4a-3d9c14afc62b +md""" +!!! task + Set-up a system of differential equations modelling the above problem. +""" + +# ╔═╡ 4eb95688-ba9d-4524-a522-3b8343f4e2be +solution( +md""" +The system of differential equations that models the water level in a reservoir ($W$) and the groundwater level ($G$) considering evaporation, infiltration, inlet flow and outlet flow can be written down as: + +$$\begin{align} +\frac{dW}{dt} & = I - O - k_1 \cdot W - k_2 \cdot (W - G) \\ +\frac{dG}{dt} & = k_2 \cdot (W - G) +\end{align}$$ +""" +) + +# ╔═╡ f7373868-80fc-4838-bbc2-bc1593a86d6e +md""" +Model the aforementioned system of differential equations using ModelingToolkit. +""" + +# ╔═╡ 37faba08-5b07-4471-bd4c-296a9c371100 +md""" +Define the variables with `@variables`. +""" + +# ╔═╡ f86d8696-aa00-463d-a421-ea6a738e5bf8 +missing + +# ╔═╡ c08c6d18-400d-46db-bb5e-23b4cfe6f2db +md""" +Define the parameters with `@parameters`. +""" + +# ╔═╡ db7a6a7f-3e46-433a-9eaf-b9c1061958e0 +missing + +# ╔═╡ e0145073-e173-4d7a-be0b-aa270c9b57dc +md""" +Set up the equations. Name the set of equations `eqs_wei`. +""" + +# ╔═╡ 6900fb0a-430a-411e-a9b4-bf5ceaff36c6 +eqs_wei = missing + +# ╔═╡ 1e3c0058-a5da-47b8-b37c-f2820d20fe41 +md""" +## Part 1 +""" + +# ╔═╡ 0c107a12-0ec3-4225-95ba-9afecdcf5b61 +md""" +There is a natural constant inflow of water into the reservoir at a rate $I$. At time $t=0\;s$ a pumping device is switched on such that the reservoir is rapidly being emptied at an outflow rate $O$ until the level of the water reservoir drops to zero. From then on, the pump is switched off. + +The latter can be handled using the continuous event. Use the following option when creating an `ODESystem`: `continuous_events = [[W ~ 0]=>[O ~ 0]]`. This will make sure that when $W$ hits zero, then $O$ will be put to zero. +""" + +# ╔═╡ b49dc322-2171-4890-b070-ee83593b9336 +md""" +Build a system of equations with `@mtkbuild`. Name it `sys1_wei`. +""" + +# ╔═╡ b7e1dc94-c1b5-4dab-ba9b-f8b9a8fe1898 +missing + +# ╔═╡ acab2bf0-b792-4ccc-bee0-7611bedab23c +md""" +Both water levels are initially $6.75\;m$. The inflow rate is constant and is $2.7\; m/min$. The evaporation and infiltration coefficient are $0.4\;min^{-1}$ and $1.0\;min^{-1}$ respectively. The outflow rate due to the pump is $20\;m/min$ and the pump stops working when $W$ equals zero. We wish to simulate the evolution of $W$ and $G$ during $20\;min$. +""" + +# ╔═╡ 44cf2ac6-75e2-4440-94f1-8fe6887ee1e0 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ 733bdb56-fb4f-4bc6-b50c-e3245fd59730 +u0 = missing + +# ╔═╡ 521fcca5-3c88-463b-9395-e5871b9fc5a3 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ f2267cc9-4bfd-44af-984c-cf54aa855f91 +tspan = missing + +# ╔═╡ e12840ed-d448-4f7c-893d-cfe974f62f9a +md""" +Initialize a vector `parms` with the parameter values: +""" + +# ╔═╡ 5a3d520d-fd4f-485c-a8b0-63858eca4bfc +parms = missing + +# ╔═╡ aafb49a4-b468-49b2-838d-5ddfcc852d48 +md""" +Create the ODE problem and store it in `oprob1_wei`: +""" + +# ╔═╡ a25d3652-13f5-47ef-9f16-c6698547a734 +oprob1_wei = missing + +# ╔═╡ 451a3c66-5bcc-4161-af81-f89af33b5862 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()`, `saveat=0.1` and `reltol=1e-9`. Store the solution in `osol1_wei`: +""" + +# ╔═╡ b9d2c6cb-88f0-4a88-9e61-eecc905ff3e6 +osol1_wei = missing + +# ╔═╡ ab77c284-d379-47d1-bd86-88fa77749165 +md""" +Plot the results: +""" + +# ╔═╡ e29a3294-245b-445d-bb1e-12cafb2ec175 +missing + +# ╔═╡ 66588291-d399-4169-9383-ac6c05cdf906 +md""" +Interpret the results. Ask yourself the following questions: + +1. Can you clearly see the drop in $W$? To what value does $W$ drops? +""" + +# ╔═╡ 89ece6c0-3690-4265-8b1d-c3a3cc8b095f +md"- Answer: missing" + +# ╔═╡ 92fa2a5a-123b-434c-a97f-39d9727a5ab6 +md""" +2. Why does $G$ also drop when $W$ drops? Explain. +""" + +# ╔═╡ 71dde4bd-711c-40c3-8ee2-9f7a79d72aa2 +md"- Answer: missing" + +# ╔═╡ d04d906d-1432-4753-81b9-b03a06df9816 +md""" +3. To what values are $W$ and $G$ tending to go? Was the system with the initial values for $W$ and $G$ and no outflow in equilibrium? Explain. +""" + +# ╔═╡ 4ce3328e-3436-45ad-b899-9b901b53a8ea +md"- Answer: missing" + +# ╔═╡ 97fc5a24-f5bf-4f62-b3c6-d3b3be1f7b71 +md""" +## Part 2 +""" + +# ╔═╡ 21560a84-db0e-495c-bb87-ae8a81a8755b +md""" +Copy the above system of equations that you have built with `@mtkbuild`. Name it `sys2_wei`. Extend this system with two discrete events so that the pump is switched on ($20\;L/min$) at time 10 and time 15. + +Use the following additional option when creating an `ODESystem`:\ +`discrete_events = [[10]=>[O ~ 20], [15]=>[O ~ 20]]`. +""" + +# ╔═╡ ef66afba-119f-4b07-ae18-875188d5caab +missing + +# ╔═╡ 071425d1-a012-4bbc-99ce-e2a4137988e2 +md""" +We will use the same values for the initial conditions, time span and parameter, so we don't need to redefine them. +""" + +# ╔═╡ d08aea59-c2f3-4fcd-b201-bff8999cb7ef +md""" +Create the ODE problem and store it in `oprob2_wei`: +""" + +# ╔═╡ 8e855e0d-58e7-4413-8124-e8ea1e092dd6 +oprob2_wei = missing + +# ╔═╡ 07f5a55b-f79b-41f9-8568-23bcfa325090 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()`, `saveat=0.1` and `reltol=1e-9`. Store the solution in `osol2_wei`: +""" + +# ╔═╡ 37cf1c26-41d6-4f89-922b-4d5125fd1001 +osol2_wei = missing + +# ╔═╡ 15482456-5b18-4453-87ab-57bf6a75d1de +md""" +Plot the results: +""" + +# ╔═╡ fe8cc5dc-4e47-46af-b18c-a56812338988 +missing + +# ╔═╡ 47822a88-4a89-4466-870f-207f825fb82f +md""" +Interpret the results. +""" + +# ╔═╡ e1355c7b-9d8a-4908-abc5-f0a5d0e8c1f4 +md""" +missing +""" + +# ╔═╡ 35bed614-5c58-4690-9341-276228561436 +md""" +## Part 3 +""" + +# ╔═╡ a55e40f6-dda7-4705-a37b-191ab7b1f16c +md""" +Solve for the equilibrium values using a `SteadyStateProblem`. You can using either `sys1_wei` or `sys2_wei` to do that. Provide a vector with an initial guess for the equibibrium values and vector for the parameter values where $O$ is zero (cf. pump is switched off)! +""" + +# ╔═╡ ad03bfce-ca51-4727-934b-dbfdc42e9443 +eq_val = missing + +# ╔═╡ a86346a2-0d31-4c25-88fb-b2ed3c3bd425 +Weq = missing + +# ╔═╡ de29ebf9-01fb-451e-8100-cc171c7b4843 +Geq = missing + +# ╔═╡ Cell order: +# ╟─935787f8-2aa3-477b-8c91-c9c3b0f08d3d +# ╠═62dc7706-f58a-11ee-2d3d-f78f7ceca914 +# ╠═e079823b-8b40-42a2-a63f-1645a97b33f0 +# ╠═65571bb8-e260-4a82-b0d2-198e47c56271 +# ╠═5e79d359-393b-405a-847e-dbdec15a1848 +# ╠═cd21ad3c-94b8-4031-8fc0-17a5a39043f4 +# ╟─66e8a12c-74b6-4077-b90e-3d85e5a61d6e +# ╟─7a83f3cd-da5b-46d4-a1c7-9fe14c2122d0 +# ╟─f55e0ca3-07ff-43a1-907f-9ad640227822 +# ╟─a551f3c5-2fc3-4236-ab4a-3d9c14afc62b +# ╟─4eb95688-ba9d-4524-a522-3b8343f4e2be +# ╟─f7373868-80fc-4838-bbc2-bc1593a86d6e +# ╟─37faba08-5b07-4471-bd4c-296a9c371100 +# ╠═f86d8696-aa00-463d-a421-ea6a738e5bf8 +# ╟─c08c6d18-400d-46db-bb5e-23b4cfe6f2db +# ╠═db7a6a7f-3e46-433a-9eaf-b9c1061958e0 +# ╟─e0145073-e173-4d7a-be0b-aa270c9b57dc +# ╠═6900fb0a-430a-411e-a9b4-bf5ceaff36c6 +# ╟─1e3c0058-a5da-47b8-b37c-f2820d20fe41 +# ╟─0c107a12-0ec3-4225-95ba-9afecdcf5b61 +# ╟─b49dc322-2171-4890-b070-ee83593b9336 +# ╠═b7e1dc94-c1b5-4dab-ba9b-f8b9a8fe1898 +# ╟─acab2bf0-b792-4ccc-bee0-7611bedab23c +# ╟─44cf2ac6-75e2-4440-94f1-8fe6887ee1e0 +# ╠═733bdb56-fb4f-4bc6-b50c-e3245fd59730 +# ╟─521fcca5-3c88-463b-9395-e5871b9fc5a3 +# ╠═f2267cc9-4bfd-44af-984c-cf54aa855f91 +# ╟─e12840ed-d448-4f7c-893d-cfe974f62f9a +# ╠═5a3d520d-fd4f-485c-a8b0-63858eca4bfc +# ╟─aafb49a4-b468-49b2-838d-5ddfcc852d48 +# ╠═a25d3652-13f5-47ef-9f16-c6698547a734 +# ╟─451a3c66-5bcc-4161-af81-f89af33b5862 +# ╠═b9d2c6cb-88f0-4a88-9e61-eecc905ff3e6 +# ╟─ab77c284-d379-47d1-bd86-88fa77749165 +# ╠═e29a3294-245b-445d-bb1e-12cafb2ec175 +# ╟─66588291-d399-4169-9383-ac6c05cdf906 +# ╠═89ece6c0-3690-4265-8b1d-c3a3cc8b095f +# ╟─92fa2a5a-123b-434c-a97f-39d9727a5ab6 +# ╠═71dde4bd-711c-40c3-8ee2-9f7a79d72aa2 +# ╟─d04d906d-1432-4753-81b9-b03a06df9816 +# ╠═4ce3328e-3436-45ad-b899-9b901b53a8ea +# ╟─97fc5a24-f5bf-4f62-b3c6-d3b3be1f7b71 +# ╟─21560a84-db0e-495c-bb87-ae8a81a8755b +# ╠═ef66afba-119f-4b07-ae18-875188d5caab +# ╟─071425d1-a012-4bbc-99ce-e2a4137988e2 +# ╟─d08aea59-c2f3-4fcd-b201-bff8999cb7ef +# ╠═8e855e0d-58e7-4413-8124-e8ea1e092dd6 +# ╟─07f5a55b-f79b-41f9-8568-23bcfa325090 +# ╠═37cf1c26-41d6-4f89-922b-4d5125fd1001 +# ╟─15482456-5b18-4453-87ab-57bf6a75d1de +# ╠═fe8cc5dc-4e47-46af-b18c-a56812338988 +# ╟─47822a88-4a89-4466-870f-207f825fb82f +# ╠═e1355c7b-9d8a-4908-abc5-f0a5d0e8c1f4 +# ╟─35bed614-5c58-4690-9341-276228561436 +# ╟─a55e40f6-dda7-4705-a37b-191ab7b1f16c +# ╠═ad03bfce-ca51-4727-934b-dbfdc42e9443 +# ╠═a86346a2-0d31-4c25-88fb-b2ed3c3bd425 +# ╠═de29ebf9-01fb-451e-8100-cc171c7b4843 diff --git a/src/exercises/ode_model_anaerobic_fermentation.jl b/src/exercises/ode_model_anaerobic_fermentation.jl deleted file mode 100644 index 8471bb8e..00000000 --- a/src/exercises/ode_model_anaerobic_fermentation.jl +++ /dev/null @@ -1,467 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "10" -#> title = "EXTRA. ODE anaerobic fermentation" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of anaerobic fermentation" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 2552c020-1e29-451e-9f59-c4bde047faad -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 84e21b44-a1b0-11ef-014d-c58a169e3de3 -using Markdown - -# ╔═╡ 3cd7556f-73b8-4176-a779-ad38909f464d -using InteractiveUtils - -# ╔═╡ 896b4151-e26b-40ee-bfb9-c56dfc4e7048 -using Catalyst - -# ╔═╡ aaf6da21-60cc-478c-b447-3f33aa375240 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ e119ed70-fd81-4978-92d9-696cad71125f -using PlutoUI; TableOfContents() - -# ╔═╡ bb10266a-1f6c-4fda-b276-6a9cf3a86e90 -md""" -# Exercise - Anaerobic fermentation -""" - -# ╔═╡ c3f85244-e0a8-4808-aa2d-15ab6bbb1b26 -md""" -## Part 1 -""" - -# ╔═╡ 2e7c2eab-a3bc-4574-a0fd-0a45b59b803b -md""" -An operator at a beverage factory would like to model the anaerobic fermentation that occurs in one of his reactors. After a literature review, he finds that sucrose ($S$) is converted to ethanol ($E$) via glucose ($G$) under the action of an enzyme called invertase ($I$) from yeast using the following reaction stoichiometry (all components have the unit $mol\;L^{-1}$): - -$$C_{12}H_{22}O_{11} + I \xrightarrow{r_1} 2C_{6}H_{12}O_{6} + I$$ - -$$C_{6}H_{12}O_{6} \xrightarrow{r_2} 2C_{2}H_{5}OH + 2CO_2$$ - -The operator knows that both reactions are carried out in isothermal conditions in a reactor with volume $V$ $[L]$. - -The operator knows from literature that the reaction rate $r_1$ is first-order in both sucrose and invertase and with specific reaction rate $k_1$. The reaction rate $r_2$ is second-order with respect to glucose with specific reaction rate $k_2$. Additionally, the reaction is inhibited by ethanol itself according to $\cfrac{K}{E+K}$ where $K$ represents the ethanol concentration at which $r_2$ achieves half of its maximum reaction rate. - -The initial concentrations and the parameter values are summarised in the following tables: - -| $S_0$ | $I_0$ | $G_0$ | $E_0$ | $CO_{2,0}$ | -|:---------:|:---------:|:---------:|:---------:|:----------:| -| $0.04$ | $0.02$ | $0.00$ | $0.01$ | $0.00$ | - -| $k_1$ | $k_2$ | $K$ | -|:---------:|:---------:|:---------:| -| $0.40$ | $0.65$ | $0.50$ | - -""" - -# ╔═╡ 35f2015d-9c28-4bc8-83cd-c185757db0dc -md" -#### Implementation of the system -" - -# ╔═╡ bfd7fc6f-57f6-40d8-8618-d514d1e5d9ad -md""" -Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of $S$, $I$, $G$, $E$ and $CO_2$ during $1440\;min$ ($=24\;h$). Name it `anaerobic_fermentation1`. - -Tips: -- For the reaction with reaction rate $r_2$, in order to have a second-order reaction with respect to glucose, you need to double the stoichiometric coefficients, i.e., you reaction should be $2G \rightarrow 4E + 4CO_2$. -- For the inhibition factor $\cfrac{K}{E+K}$ you can use the function `mmr(..., ..., ...)`! -""" - -# ╔═╡ 53c32175-4298-4450-ae85-132ea0cd6a9b -# Uncomment and complete the instruction -# anaerobic_fermentation1 = @reaction_network begin -# @species missing -# missing -# missing -# end - -# ╔═╡ 485c2b25-f281-4ee9-bcb8-7ca010205930 -md""" -Check out the species. -""" - -# ╔═╡ 86200a5e-55ce-4d58-87ba-fe30f65c9120 -# missing # Uncomment and complete the instruction - -# ╔═╡ 579318a9-bad9-4392-9420-3f5474ccfff8 -md""" -Convert the system to a symbolic differential equation model and inspect your differential equations. -""" - -# ╔═╡ bc56d0fb-db19-4f63-9f34-617308da6c8a -# osys1 = missing # Uncomment and complete the instruction - -# ╔═╡ 5983b9d3-5119-47ac-9a0c-8a2755ad23e6 -md""" -Initialize a vector `u01` with the initial conditions: -""" - -# ╔═╡ c822fcbc-e8d3-4952-93bc-38a41f1786a7 -# u01 = missing # Uncomment and complete the instruction - -# ╔═╡ eb7fda8e-d1db-4bcc-91d6-964a6dcbbe90 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 124571f0-fe33-44a2-9ab4-f6a418ac4f51 -# tspan1 = missing # Uncomment and complete the instruction - -# ╔═╡ 703d2eb0-8504-496f-9abe-8c6911a0fdbc -md""" -Initialize a vector `params1` with the parameter values: -""" - -# ╔═╡ 9dd45f30-9076-45d5-827b-02ae2d73ef97 -# params1 = missing # Uncomment and complete the instruction - -# ╔═╡ 64f85604-9562-4a97-bedd-ea5d28ada096 -md""" -Create the ODE problem and store it in `oprob1`: -""" - -# ╔═╡ 04816732-3419-40e3-a927-d6e102949553 -# oprob1 = missing # Uncomment and complete the instruction - -# ╔═╡ 9a517512-a7c0-4a92-a104-b19a8bf786c7 -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol1`: -""" - -# ╔═╡ 69f9ce3a-0422-499a-8492-ea70de43d587 -# osol1 = missing # Uncomment and complete the instruction - -# ╔═╡ f1c134c9-9127-4ed4-84a9-e1d218a82295 -md""" -Plot the results. Use a line width of 2 (`linewidth=...`). -""" - -# ╔═╡ e8eeefd3-f83a-4154-945d-568ac629cd94 -# missing # Uncomment and complete the instruction - -# ╔═╡ 622f65d3-0592-419f-8101-bc6f8b8ea5ed -md""" -Interprete the results. Try to come up with an answer to the following questions: - -1. Why is the concentration of invertase ($I$) constant zero, and the concentration of sucrose ($S$) becoming zero? -""" - -# ╔═╡ 39e96a71-7483-4c85-b44d-76cafc3b22dc -md"- Answer: missing" - -# ╔═╡ 192ec8c6-ff60-4584-91f9-d257399bac76 -md""" -2. Try to explain the peak in the glucose ($G$) concentration. -""" - -# ╔═╡ e2681cdb-581d-41d7-93c6-b137ef54c083 -md"- Answer: missing" - -# ╔═╡ 9f5bf914-7ec6-48ba-8fed-2087f42b37d8 -md""" -3. Why is the difference in ethanol ($E$) and $CO_2$ concentration constant? -""" - -# ╔═╡ e50013b7-be37-4fa9-bdd6-2a902443f222 -md"- Answer: missing" - -# ╔═╡ 939716cd-08c9-44a0-9217-146f9e312d35 -md""" -## Part 2 -""" - -# ╔═╡ 194a596b-22de-412e-8ead-94a8c02c98c6 -md""" -Additionally, sucrose and glucose are added at a flow rate $Q_{in},[L\; min^{-1}]$ and respective concentrations $S_{in}$ and $G_{in}$. The same flow rate is removed from the reactor but the invertase $I$ stays in the reactor. Now the volume $V$ of the reactor will matter. Furthermore, the invertase enzyme degrades at a rate $d=0.003\;min^{-1}$. - -The additional parameter values are summarised in the following table: - -| $Q_{in}$ | $V$ | $S_{in}$ | $G_{in}$ | $d$ | -|:---------:|:---------:|:---------:|:---------:|:--------:| -| $1.00$ | $100$ | $0.12$ | $0.05$ | $0.003$ | -""" - -# ╔═╡ 76b1d8ba-cc55-48f7-bee4-09f899582e62 -md""" -Make a copy of the content of the previous *reaction network object* and complement it with the new information. Name it `anaerobic_fermentation2`. -""" - -# ╔═╡ 4165677e-a483-404c-a8c5-6cd85e5a8090 -# Uncomment and complete the instruction -# anaerobic_fermentation2 = @reaction_network begin -# @species missing -# @parameters missing -# missing -# ... -# missing -# end - -# ╔═╡ 3da7ee15-6d30-42bb-be03-6f9129febc60 -md""" -Convert the system to a symbolic differential equation model and inspect your differential equations. -""" - -# ╔═╡ 58b8545f-1699-45b4-91c0-86722668a428 -# osys2 = missing # Uncomment and complete the instruction - -# ╔═╡ f6d58bac-7256-4b57-836d-6d243bf292c0 -md""" -Make an exact copy of `u01` and rename it to `u02` with the initial conditions: -""" - -# ╔═╡ 75eb088a-33fd-47d7-892a-da934ec9896a -# u02 = missing # Uncomment and complete the instruction - -# ╔═╡ b4c4fc78-b6e4-40db-9948-6be83ee57d87 -md""" -Make an exact copy of `tspan1` and rename it to `tspan2`: -""" - -# ╔═╡ 4a0e9abf-99e7-4fe6-82ae-aa958ab51dd0 -# tspan2 = missing # Uncomment and complete the instruction - -# ╔═╡ 0ec39752-6654-452b-98db-eadd8c02b27f -md""" -Make a copy of `params1`, rename it to `params2` and supplement it with the new parameter values: -""" - -# ╔═╡ 3aea0dbe-1cb3-4b19-bb38-53e58db153dd -# param2 = missing # Uncomment and complete the instruction - -# ╔═╡ 49984e8e-a194-464a-994f-501342800026 -md""" -Create the ODE problem and store it in `oprob2`: -""" - -# ╔═╡ 18a3513f-8ecc-4c2f-bb39-5d0cb39a3d92 -# oprob2 = missing # Uncomment and complete the instruction - -# ╔═╡ 5318708e-3410-4675-8162-827c0c6e039c -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol2`: -""" - -# ╔═╡ e5476343-8376-4a9a-8286-f69e0d023939 -# osol2 = missing # Uncomment and complete the instruction - -# ╔═╡ 29a3cd1b-fd8f-4e5c-9ec7-a69fe328d824 -md""" -Plot the results. Use a line width of 2 (`linewidth=...`). If you only want to see the curves for, e.g., $E$, $S$ and $G$, you can use the option `idxs=[:E, :S, :G]` in the `plot` command. -""" - -# ╔═╡ 0c179e6b-bcaf-4e89-8d04-59f24b456127 -# missing # Uncomment and complete the instruction - -# ╔═╡ e596f49e-1b3a-422e-be9a-f72133f042d5 -md""" -Interprete the results. -""" - -# ╔═╡ 3505c10d-270c-49fc-a83a-3753974d4d5a -md"- Answer: missing" - -# ╔═╡ 5f865fec-09d5-4358-be57-0d568740968d -md""" -Check out the last concentrations (at the end time) for each of the species. - -Tips: -- You can see the last values of all species with `osol2.u[end]` -- If later you need all last values separately, you can access the last value of $S$ with `osol2[:S][end]` and then you can put everything on one line separating the values with comma's. -""" - -# ╔═╡ acc8acb3-f014-4108-80d7-bff893ce07d1 -# missing # Uncomment and complete the instruction - -# ╔═╡ 053cca09-0e31-44c0-89db-26a581816744 -# osol2[:S][end], ..., ..., ..., ...) # Uncomment and complete the instruction - -# ╔═╡ 83cfd592-896d-42f5-ae2a-b1d0a9f14aac -md""" -Create a vector named `u_guess` in the same way as `u02`, but now with the end values of the species. -""" - -# ╔═╡ f5a8f97b-7cbb-4eb4-b5ff-82c184b7de85 -# u_guess2 = missing # Uncomment and complete the instruction - -# ╔═╡ ce67b1f7-a82d-4fa3-a22b-f8ee3d2f4af0 -md""" -Calculate the steady-state values of the species: -""" - -# ╔═╡ 3a014879-883b-4cab-a7ac-0c3a708f5da6 -# Sw2, Iw2, Gw2, Ew2, CO2w2 = missing # Uncomment and complete the instruction - -# ╔═╡ d7bde85c-05f1-4794-9e91-c4e5e120f876 -md""" -Check ou the steady states: -""" - -# ╔═╡ 876683d7-b3cd-4a06-94b2-d38a1d4a34b6 -# missing - -# ╔═╡ 55d303ec-29cb-4e8e-ab52-808d881ae0f2 -md""" -## Part 3 -""" - -# ╔═╡ e0ef008c-9276-41e5-a85a-649a276c4666 -md""" -We now want to keep a relatively high production of ethanol. Therefore, if the invertase decreases to $0.008$, then the invertase is instantaneously renewed to the initial concentration of $0.02$. Apply the change in the invertase concentration using a continuous event. -""" - -# ╔═╡ 81db6771-ee45-42c8-ab23-fe1960370b86 -md""" -Create the correct condition. -""" - -# ╔═╡ 166a21f0-46a5-4d7a-9f54-788d7a51a487 -# condition3 = missing # Uncomment and complete the instruction - -# ╔═╡ 5e6c5431-6fad-4272-a539-5e3af83fa226 -md""" -Include the condition into the *reaction network model*. -""" - -# ╔═╡ 98be4b39-4097-4cfa-886e-171558da0df6 -# Uncomment and complete the instruction -# @named anaerobic_fermentation3_c = missing - -# ╔═╡ 66568f6a-b874-42cb-834d-ab9d4df815d1 -md""" -Complete the *reaction network model*. -""" - -# ╔═╡ 66441227-c202-48da-a6ed-58e25ffde3ce -# Uncomment and complete the instruction -# anaerobic_fermentation3_c_com = missing - -# ╔═╡ 71b4bd6e-b906-4d88-a9a3-18700528ea12 -md""" -Create a new ODE problem. -""" - -# ╔═╡ 16d0d671-464c-4de3-bd73-593e1a73168b -# oprob3 = missing # Uncomment and complete the instruction - -# ╔═╡ 66015536-5f23-45b5-8d57-f1d27d604f8c -md""" -Solve the new ODE problem. Make a `deepcopy`, use `Tsit5()` and `saveat=0.5`. -""" - -# ╔═╡ 7f0777a2-6eb9-4c6d-9fb7-25a1f32f291d -# osol3 = missing - -# ╔═╡ 124d578f-42ea-488e-af65-312793f104ca -md""" -Plot the results. -""" - -# ╔═╡ 9ad3aff4-76ea-452f-bd7b-fc379725df70 -# missing - -# ╔═╡ e07e1667-2f2c-4086-9818-90dff54296de -md""" -Interprete the results. -""" - -# ╔═╡ 91af34c0-9b38-4a62-8387-5e3c60b4b145 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═84e21b44-a1b0-11ef-014d-c58a169e3de3 -# ╠═3cd7556f-73b8-4176-a779-ad38909f464d -# ╠═2552c020-1e29-451e-9f59-c4bde047faad -# ╠═896b4151-e26b-40ee-bfb9-c56dfc4e7048 -# ╠═aaf6da21-60cc-478c-b447-3f33aa375240 -# ╠═e119ed70-fd81-4978-92d9-696cad71125f -# ╟─bb10266a-1f6c-4fda-b276-6a9cf3a86e90 -# ╟─c3f85244-e0a8-4808-aa2d-15ab6bbb1b26 -# ╟─2e7c2eab-a3bc-4574-a0fd-0a45b59b803b -# ╟─35f2015d-9c28-4bc8-83cd-c185757db0dc -# ╟─bfd7fc6f-57f6-40d8-8618-d514d1e5d9ad -# ╠═53c32175-4298-4450-ae85-132ea0cd6a9b -# ╟─485c2b25-f281-4ee9-bcb8-7ca010205930 -# ╠═86200a5e-55ce-4d58-87ba-fe30f65c9120 -# ╟─579318a9-bad9-4392-9420-3f5474ccfff8 -# ╠═bc56d0fb-db19-4f63-9f34-617308da6c8a -# ╟─5983b9d3-5119-47ac-9a0c-8a2755ad23e6 -# ╠═c822fcbc-e8d3-4952-93bc-38a41f1786a7 -# ╟─eb7fda8e-d1db-4bcc-91d6-964a6dcbbe90 -# ╠═124571f0-fe33-44a2-9ab4-f6a418ac4f51 -# ╟─703d2eb0-8504-496f-9abe-8c6911a0fdbc -# ╠═9dd45f30-9076-45d5-827b-02ae2d73ef97 -# ╟─64f85604-9562-4a97-bedd-ea5d28ada096 -# ╠═04816732-3419-40e3-a927-d6e102949553 -# ╟─9a517512-a7c0-4a92-a104-b19a8bf786c7 -# ╠═69f9ce3a-0422-499a-8492-ea70de43d587 -# ╟─f1c134c9-9127-4ed4-84a9-e1d218a82295 -# ╠═e8eeefd3-f83a-4154-945d-568ac629cd94 -# ╟─622f65d3-0592-419f-8101-bc6f8b8ea5ed -# ╟─39e96a71-7483-4c85-b44d-76cafc3b22dc -# ╟─192ec8c6-ff60-4584-91f9-d257399bac76 -# ╟─e2681cdb-581d-41d7-93c6-b137ef54c083 -# ╟─9f5bf914-7ec6-48ba-8fed-2087f42b37d8 -# ╟─e50013b7-be37-4fa9-bdd6-2a902443f222 -# ╟─939716cd-08c9-44a0-9217-146f9e312d35 -# ╟─194a596b-22de-412e-8ead-94a8c02c98c6 -# ╟─76b1d8ba-cc55-48f7-bee4-09f899582e62 -# ╠═4165677e-a483-404c-a8c5-6cd85e5a8090 -# ╟─3da7ee15-6d30-42bb-be03-6f9129febc60 -# ╠═58b8545f-1699-45b4-91c0-86722668a428 -# ╟─f6d58bac-7256-4b57-836d-6d243bf292c0 -# ╠═75eb088a-33fd-47d7-892a-da934ec9896a -# ╟─b4c4fc78-b6e4-40db-9948-6be83ee57d87 -# ╠═4a0e9abf-99e7-4fe6-82ae-aa958ab51dd0 -# ╟─0ec39752-6654-452b-98db-eadd8c02b27f -# ╠═3aea0dbe-1cb3-4b19-bb38-53e58db153dd -# ╟─49984e8e-a194-464a-994f-501342800026 -# ╠═18a3513f-8ecc-4c2f-bb39-5d0cb39a3d92 -# ╟─5318708e-3410-4675-8162-827c0c6e039c -# ╠═e5476343-8376-4a9a-8286-f69e0d023939 -# ╟─29a3cd1b-fd8f-4e5c-9ec7-a69fe328d824 -# ╠═0c179e6b-bcaf-4e89-8d04-59f24b456127 -# ╟─e596f49e-1b3a-422e-be9a-f72133f042d5 -# ╟─3505c10d-270c-49fc-a83a-3753974d4d5a -# ╟─5f865fec-09d5-4358-be57-0d568740968d -# ╠═acc8acb3-f014-4108-80d7-bff893ce07d1 -# ╠═053cca09-0e31-44c0-89db-26a581816744 -# ╟─83cfd592-896d-42f5-ae2a-b1d0a9f14aac -# ╠═f5a8f97b-7cbb-4eb4-b5ff-82c184b7de85 -# ╟─ce67b1f7-a82d-4fa3-a22b-f8ee3d2f4af0 -# ╠═3a014879-883b-4cab-a7ac-0c3a708f5da6 -# ╟─d7bde85c-05f1-4794-9e91-c4e5e120f876 -# ╠═876683d7-b3cd-4a06-94b2-d38a1d4a34b6 -# ╟─55d303ec-29cb-4e8e-ab52-808d881ae0f2 -# ╟─e0ef008c-9276-41e5-a85a-649a276c4666 -# ╟─81db6771-ee45-42c8-ab23-fe1960370b86 -# ╠═166a21f0-46a5-4d7a-9f54-788d7a51a487 -# ╟─5e6c5431-6fad-4272-a539-5e3af83fa226 -# ╠═98be4b39-4097-4cfa-886e-171558da0df6 -# ╟─66568f6a-b874-42cb-834d-ab9d4df815d1 -# ╠═66441227-c202-48da-a6ed-58e25ffde3ce -# ╟─71b4bd6e-b906-4d88-a9a3-18700528ea12 -# ╠═16d0d671-464c-4de3-bd73-593e1a73168b -# ╟─66015536-5f23-45b5-8d57-f1d27d604f8c -# ╠═7f0777a2-6eb9-4c6d-9fb7-25a1f32f291d -# ╟─124d578f-42ea-488e-af65-312793f104ca -# ╠═9ad3aff4-76ea-452f-bd7b-fc379725df70 -# ╟─e07e1667-2f2c-4086-9818-90dff54296de -# ╟─91af34c0-9b38-4a62-8387-5e3c60b4b145 diff --git a/src/exercises/ode_model_birth_death.jl b/src/exercises/ode_model_birth_death.jl deleted file mode 100644 index 8706568f..00000000 --- a/src/exercises/ode_model_birth_death.jl +++ /dev/null @@ -1,265 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "4" -#> title = "1. ODE birth rate" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of birth rate" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 12fee37d-eef9-4e90-b0af-d892469fad08 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 4a3d066c-f5b1-11ee-0145-2da7c11147a5 -using Markdown - -# ╔═╡ 0ab4d97a-0c47-4f6e-a76b-9cec00ad410a -using InteractiveUtils - -# ╔═╡ 34a2f237-848c-4d07-9cea-fa5505a9e215 -using PlutoUI; TableOfContents() - -# ╔═╡ b721cc4d-43ba-4221-bbd1-15293aaf54b5 -using Catalyst - -# ╔═╡ 47e9c791-99cc-4a78-94b0-e0f5d4e0ecc5 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ c701d64e-640c-473f-b0fa-688024962f28 -md""" -# Exercise: Simple birth-death model for mice - -In a simple birth-death model for mice, the birth rate of mice represents the -rate at which new individuals are added to the population through reproduction. -This rate is influenced by factors such as the number of reproductive females, -their fertility, and the frequency of reproduction cycles. Conversely, -the death rate reflects the rate at which individuals are removed from -the population due to mortality factors such as predation, disease, -and environmental stressors. Together, these rates interact dynamically -to shape the population dynamics of mice in their natural habitat. -Denote the number of mice by $X$, the average birth rate by $b$ ($mice / day$), and the average death rate by $d$ ($day^{-1}$). Hence, assume for this overly simplified model, that the birth of mice is a zeroth-order process and that the death of mice is a first-order process. -""" - -# ╔═╡ b583efef-dc7d-4447-bf27-e6373f5872c4 -md""" -Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of $X$ with time. Name it `birth_death`. -""" - -# ╔═╡ 74c3f7ed-d705-4cb9-b52c-e06c9df5ca13 -# Uncomment and complete the instruction -# birth_death = @reaction_network begin -# missing -# end - -# ╔═╡ c84f2a32-e66b-4a36-a257-7031ce799225 -md""" -Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. -""" - -# ╔═╡ 124eb20e-6945-4eec-a05a-6b835efbdd2e -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 04c645ad-873d-44c1-9bc0-940a412630b9 -md""" -## Part 1 - -Simulate the evolution of the number of mice **per day** during $10$ years starting off with $2$ mice. **Assume that per year 25 pups are born.** Suppose the death rate to be $0.0015\;day^{-1}$. -""" - -# ╔═╡ 1cd5136a-f472-4329-980a-b8382d1c04ea -md""" -First, calculate the birth rate in $mice/day$. -""" - -# ╔═╡ 6b64ec8b-d85e-4a4f-aa3f-f177f5e34180 -# missing # Uncomment and complete the instruction - -# ╔═╡ 52ee74d2-4dca-47ef-8b97-f826ef31ddf9 -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ 23f282d8-2eeb-43dd-8ba9-84684daca7a4 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 30fa6fc6-54e0-4233-9334-b3d15f630489 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ b461a225-bae6-45b8-bb42-54742a72b98f -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ c753d65f-9ed3-4e60-8e36-6e4bca77c19b -md""" -Initialize a vector `param` with the parameter values: -""" - -# ╔═╡ e7942ba9-5434-4b22-b1d4-f18f6227320d -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 2af7f6b9-9d95-4bf6-af67-449803440639 -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ 9432f90b-7141-4518-b6fd-d55a1389e14a -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ ab197ac7-413b-4305-8352-2723bf9f2aff -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=1.0`. Store the solution in `osol`: -""" - -# ╔═╡ 8e517114-7c74-4d7c-954c-2787d975a1df -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ 9eaf1ca5-3d65-4263-a307-991bc8bf62d2 -md""" -Plot the results: -""" - -# ╔═╡ 83e36e63-7e6b-4c17-8f7d-ed0e07f36fc5 -# missing # Uncomment and complete the instruction - -# ╔═╡ 5fc64787-795c-4e17-b751-cd3683f8016b -md""" -Interpret the results. Ask yourself the following questions: - -1. What is the (approximate) steady state value for $X$? -""" - -# ╔═╡ f5718c08-c8aa-4d74-b70f-8c08c6decab2 -md"- Answer: missing" - -# ╔═╡ fe4526c8-4504-4d91-841a-a83aeef55fc7 -md""" -## Part 2 - -Suppose that at $t = 3\;years$ the death rate of the mice population increases by $50\,\%$ due to a new predator species in the area. Use the same initial condition, timespan and parameter values. Simulate the evolution of the number of mice. -""" - -# ╔═╡ 1e869025-c537-4bf1-9c38-93e24b598156 -md""" -Create the *condition*. Store it in `condition2`: -""" - -# ╔═╡ 1c5d8daa-5c7e-49ef-a6da-6459a1131320 -# condition2 = missing # Uncomment and complete the instruction - -# ╔═╡ 4d9ba697-5db3-4879-be88-278765e766f1 -md""" -Make a new *reaction system* where the discrete event is included. Name it `birth_death2`. -""" - -# ╔═╡ d40c2598-5783-49c5-916d-296e946de3f6 -# @named birth_death2 = missing # Uncomment and complete the instruction - -# ╔═╡ afbc4f00-0b4a-427a-9630-3a279ccbc330 -md""" -Complete the new *reaction system*. Name it `birth_death2_com`. -""" - -# ╔═╡ 3dd3febc-fc3f-477f-a5be-335ae90223e3 -# birth_death2_com = missing # Uncomment and complete the instruction - -# ╔═╡ cf05864f-37f0-4fbb-93a5-ef406a2ae014 -md""" -Create the ODE problem and store it in `oprob2`: -""" - -# ╔═╡ 65e24cee-06c3-4cb7-8f8c-61e02b7dcd29 -# oprob2 = missing # Uncomment and complete the instruction - -# ╔═╡ 9cab94b5-6d2d-4b5a-96f3-3b8eee1c61a1 -md""" -Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=1.0`. Store the solution in `osol2`: -""" - -# ╔═╡ 00750aa4-6a00-4d33-a0ab-2d7abe952078 -# osol2 = missing # Uncomment and complete the instruction - -# ╔═╡ 694e5b50-f4c9-4ecc-8860-1b217696e1ea -md""" -Plot the results: -""" - -# ╔═╡ c69d0e86-0d15-4915-a890-8abd378da59a -# missing # Uncomment and complete the instruction - -# ╔═╡ 1f869579-7019-4120-8d14-b38f48d05bdd -md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of the increase in the death rate? -""" - -# ╔═╡ 775f2cf3-ffbb-491a-9002-3a39b9c70f3c -md"- Answer: missing" - -# ╔═╡ a02b5eac-eacb-422c-be5e-ba6337ed33f8 -md""" -2. If the death rate increases at a different timepoint, would you reach the same steady state value for $X$? Explain. -""" - -# ╔═╡ ce733400-9bf4-42b3-8f77-351ef9b1cd92 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═4a3d066c-f5b1-11ee-0145-2da7c11147a5 -# ╠═0ab4d97a-0c47-4f6e-a76b-9cec00ad410a -# ╠═12fee37d-eef9-4e90-b0af-d892469fad08 -# ╠═34a2f237-848c-4d07-9cea-fa5505a9e215 -# ╟─c701d64e-640c-473f-b0fa-688024962f28 -# ╠═b721cc4d-43ba-4221-bbd1-15293aaf54b5 -# ╟─b583efef-dc7d-4447-bf27-e6373f5872c4 -# ╠═74c3f7ed-d705-4cb9-b52c-e06c9df5ca13 -# ╟─c84f2a32-e66b-4a36-a257-7031ce799225 -# ╠═124eb20e-6945-4eec-a05a-6b835efbdd2e -# ╠═47e9c791-99cc-4a78-94b0-e0f5d4e0ecc5 -# ╟─04c645ad-873d-44c1-9bc0-940a412630b9 -# ╟─1cd5136a-f472-4329-980a-b8382d1c04ea -# ╠═6b64ec8b-d85e-4a4f-aa3f-f177f5e34180 -# ╟─52ee74d2-4dca-47ef-8b97-f826ef31ddf9 -# ╠═23f282d8-2eeb-43dd-8ba9-84684daca7a4 -# ╟─30fa6fc6-54e0-4233-9334-b3d15f630489 -# ╠═b461a225-bae6-45b8-bb42-54742a72b98f -# ╟─c753d65f-9ed3-4e60-8e36-6e4bca77c19b -# ╠═e7942ba9-5434-4b22-b1d4-f18f6227320d -# ╟─2af7f6b9-9d95-4bf6-af67-449803440639 -# ╠═9432f90b-7141-4518-b6fd-d55a1389e14a -# ╟─ab197ac7-413b-4305-8352-2723bf9f2aff -# ╠═8e517114-7c74-4d7c-954c-2787d975a1df -# ╟─9eaf1ca5-3d65-4263-a307-991bc8bf62d2 -# ╠═83e36e63-7e6b-4c17-8f7d-ed0e07f36fc5 -# ╟─5fc64787-795c-4e17-b751-cd3683f8016b -# ╟─f5718c08-c8aa-4d74-b70f-8c08c6decab2 -# ╟─fe4526c8-4504-4d91-841a-a83aeef55fc7 -# ╟─1e869025-c537-4bf1-9c38-93e24b598156 -# ╠═1c5d8daa-5c7e-49ef-a6da-6459a1131320 -# ╟─4d9ba697-5db3-4879-be88-278765e766f1 -# ╠═d40c2598-5783-49c5-916d-296e946de3f6 -# ╟─afbc4f00-0b4a-427a-9630-3a279ccbc330 -# ╠═3dd3febc-fc3f-477f-a5be-335ae90223e3 -# ╟─cf05864f-37f0-4fbb-93a5-ef406a2ae014 -# ╠═65e24cee-06c3-4cb7-8f8c-61e02b7dcd29 -# ╟─9cab94b5-6d2d-4b5a-96f3-3b8eee1c61a1 -# ╠═00750aa4-6a00-4d33-a0ab-2d7abe952078 -# ╟─694e5b50-f4c9-4ecc-8860-1b217696e1ea -# ╠═c69d0e86-0d15-4915-a890-8abd378da59a -# ╟─1f869579-7019-4120-8d14-b38f48d05bdd -# ╟─775f2cf3-ffbb-491a-9002-3a39b9c70f3c -# ╟─a02b5eac-eacb-422c-be5e-ba6337ed33f8 -# ╟─ce733400-9bf4-42b3-8f77-351ef9b1cd92 diff --git a/src/exercises/ode_model_catalyst_intro.jl b/src/exercises/ode_model_catalyst_intro.jl deleted file mode 100644 index a64a1c4f..00000000 --- a/src/exercises/ode_model_catalyst_intro.jl +++ /dev/null @@ -1,812 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "2" -#> title = "1. ODE catalyst intro" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE catalyst intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). -macro bind(def, element) - #! format: off - return quote - local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end - local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) - el - end - #! format: on -end - -# ╔═╡ e6e748a5-b692-4c7c-b39e-ef0fe9e46c35 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ be326550-25ea-4c5f-ac4a-dd74d12bc89a -using Markdown - -# ╔═╡ cd166045-7788-4f5c-97d9-d1bedeceedae -using InteractiveUtils - -# ╔═╡ 3cfe381f-5871-4447-9149-fc023c787b42 -using PlutoUI; TableOfContents() - -# ╔═╡ b1bfe3b7-782c-46c9-86db-829942b6a9d0 -using Catalyst - -# ╔═╡ 19398992-dca7-441a-a5fd-7f9d0ad30be5 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 9ea49d7a-e524-11ee-1b68-9d1d71aaba24 -md""" -# Introduction to Catalyst (ODE) -""" - -# ╔═╡ ba4dfc95-b74f-4d36-b34b-5eb2836a5cd6 -md""" -Catalyst.jl is a symbolic modelling package for construction, analysis and high performance simulation of chemical reaction networks. Catalyst defines symbolic ReactionSystems, which can be created programmatically or easily specified using Catalyst's **D**omain **S**pecific **L**anguage (DSL). -""" - -# ╔═╡ 94440e99-7c0b-4d87-8038-141a2bc5fcb8 -md""" -This notebook describes the syntax for building chemical reaction network models using Catalyst's DSL. We will illustrate this by implementing and solving an infection model by means of ODE (**O**rdinary **D**ifferential **E**quations). -""" - -# ╔═╡ 83fa478c-ac54-482b-92de-8eb6a01ddfaf -md""" -## The infection model -""" - -# ╔═╡ d257102b-8480-4ce3-bdba-50995ccbdc26 -md""" -It is important to model the outbreak of infectious diseases in order to devise appropriate measures to avoid global epidemics. In this exercise, we consider an isolated group of people in which a viral disease is spreading. We use an infection model (similar to the SIR-model but slightly extended) for this purpose. We are interested in the evolution of the number of susceptible ($S$), infected ($I$), deceased ($D$) and resistant ($R$) persons.\ -We make the following assumptions: -1. Transmission of the disease from an infected person to a susceptible person takes place through direct contact. The chance of any two inhabitants of the group coming into contact with each other is $\beta$, and the probability of infection after contact between an infected and a susceptible person is $\alpha$. -2. Note that the above assumption implicitly states that the probability of two neighbours coming into contact with each other is as high as the probability of two people living at two extremes of the territory coming into contact with each other. -3. A person leaves the infection period at a rate $r$ (hence, a person is contagious for an average of $1/r$ days. Without appropriate medication, a fraction $m$ of infected people die and a fraction $(1-m)$ of infected people acquire immunity after healing. -4. We assume that there is no migration in or out of the population. -""" - -# ╔═╡ df9a46ab-56eb-4d3d-a3a8-e1f2da59b32e -md" -Below we summarize the **relevant variables** (**species**): -" - -# ╔═╡ 7dd37224-071b-4ec7-88d5-39fd4482e615 -md""" -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``S`` | *persons* | number of susceptible persons | -| ``I`` | *persons* | number of infected persons | -| ``D`` | *persons* | number of deceased persons | -| ``R`` | *persons* | number of resistant persons | -""" - -# ╔═╡ 97d34ab6-a1c6-404d-af1f-7da85287757f -md" -Below we summarize the **parameters**: -" - -# ╔═╡ b5e6cb25-da6f-475a-bc73-041c4c9256d5 -md""" -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``\alpha`` | ``\frac{persons}{contact}`` | chances of getting infected after contact | -| ``\beta`` | ``\frac{contact}{persons^2\,day}`` | contact rate | -| ``r`` | ``\frac{1}{day}`` | rate of leaving infection period | -| ``m`` | ``\frac{person}{person}`` | fraction of persons deceasing | -| ``1-m`` | ``\frac{person}{person}`` | fraction of persons becoming resistant | -""" - -# ╔═╡ 529ddac3-bb7a-4cc3-9bbd-ca31f5796e27 -md""" -Hence, the infection rate is ``\alpha \beta``. This means that a susceptible person meets an infected person: ``S+I``, this will result in ``2I`` at a rate ``\alpha \beta``. Futhermore, an infected person ``I`` will either become a deceased person ``D`` at a rate ``m r`` or become a resistant person ``R`` at rate ``(1-m) r`` -""" - -# ╔═╡ f9ca0b86-1447-48c1-88e1-876180d7628a -md""" -Our infection model has three reaction events: - -1. Infection, where a susceptible persons meets an infected persons and also becomes infected. -2. Deceasing, where an infected person die. -3. Recovery, where an infected person recovers and becomes resitant. -""" - -# ╔═╡ bf2b06d8-884b-4a35-b308-83a2f3d6696f -md""" -Each reaction is also associated with a specific rate: - -1. ``\alpha \beta``, the infection rate. -2. ``m r``, the death rate. -3. ``(1-m) r``, the recovery rate. -""" - -# ╔═╡ 1f405b09-53cd-43e0-b498-70eafef92acb -md""" -Hence, the following *infection reactions* are: - -$$S + I \xrightarrow[]{\alpha \beta} 2I$$ -$$I \xrightarrow[]{mr} D$$ -$$I \xrightarrow[]{(1-m)r} R$$ -""" - -# ╔═╡ 39d80a3f-14d2-49d6-905b-171adf5930a0 -md""" -We are going to implement this system of *reactions* using Catalyst. -""" - -# ╔═╡ 44c6cdc0-8610-43bd-b3b3-a7441ee62615 -md""" -We first load the Catalyst package, which is required for the code in this introduction to run: -""" - -# ╔═╡ cd3da548-b24f-426f-8b46-2db70e0a979c -md""" -### Implementation of the system - -The following code creates a so called *reaction network object*, that we have named `infection_model`, that implements the aforementioned *reactions*. -""" - -# ╔═╡ 7d2f8bf9-3a00-4631-84ff-1a36b6d7e19b -infection_model = @reaction_network begin - α * β, S + I --> 2I - r * m, I --> D - r * (1 - m), I --> R -end - -# ╔═╡ 4a504e36-8269-4822-8919-75b7f6dcaf6d -md" -Each line (between `begin` and `end`) corresponds to a *reaction*. Each *reaction* consists of: - -- a **reaction rate** (the expression on the left-hand side of `,`), -- a set of **substrates** (the expression in-between `,` and `-->`), -- a set of **products** (the expression on the right-hand side of `-->`). - -The substrates and the products may contain one or more reactants, separated by `+`. -" - -# ╔═╡ bb5578b8-40d4-48c9-be87-cfaeadb8c5f9 -md""" -!!! hint "Hint" -The Greek letters can be visualized by typing a **backslash** followed by the **name of the Greek letter** and then the **TAB** key. For example `\alpha` followed by the TAB key results in in a list where you can choose `α`. -""" - -# ╔═╡ af6b169d-55c3-4384-9f57-ad629e02bad6 -md" -The *reaction model* is stored in the variable `infection_model` (the variable name can be chosen freely). It is a symbolic representation of the (chemical) network. -" - -# ╔═╡ 52b2cc8f-1eac-47c1-8d42-09c1b91d1350 -md""" -You can get a list of the different *reaction* **species** with the command `species` -""" - -# ╔═╡ 5f1d6198-ccdc-4a63-bf98-2032a6d683ad -species(infection_model) - -# ╔═╡ 59686253-3e0e-454e-8bc6-dde189ee2197 -md""" -To get a list of the *reaction* **parameters**, you can use the command `parameters`: -""" - -# ╔═╡ fdb79a26-fbc5-482a-8cdb-760d18f57a77 -parameters(infection_model) - -# ╔═╡ 2bd02577-7321-46b5-9f42-1484ed86d1d3 -md""" -!!! important "Important" -You can also get the different species and parameters using `@unpack` followed by comma separated species and/or parameter names followed by the equal sign and the name of the reaction network model. For example: -""" - -# ╔═╡ 64455b26-4374-435f-b71b-8b9600cab263 -@unpack S, I, D, R = infection_model; - -# ╔═╡ 64a48327-03ec-46a0-9a76-79c10acb6e20 -md""" -The *reaction model* can be converted to a symbolic differential equation model via -""" - -# ╔═╡ 78a22ba2-1e60-40b3-989c-922dcf9ca054 -osys = convert(ODESystem, infection_model) - -# ╔═╡ eeb25e50-165b-4e93-8397-5b09fe8e7242 -md""" -Note that the model equations are essencially: - -$$\cfrac{dS(t)}{dt} = -\alpha \beta S(t) I(t)$$ -$$\cfrac{dI(t)}{dt} = \alpha \beta S(t) I(t) - r I(t)$$ -$$\cfrac{dD(t)}{dt} = m r I(t)$$ -$$\cfrac{dR(t)}{dt} = (1-m) r I(t)$$ -""" - -# ╔═╡ 0d39fbbc-4fcb-45eb-b24b-14f2e093c98c -md""" -You can get a list of the differential equations with the command `equations`: -""" - -# ╔═╡ ca18795a-d270-493f-807f-a3b8c78aa6d7 -equations(osys) - -# ╔═╡ 3f6f38a3-12ec-4b5b-8ebb-9e48190d3f7e -md""" -To get a list of the state variables, you can use the command `unknowns`: -""" - -# ╔═╡ 72f7f870-b2fc-4df1-9325-3c587be7e014 -unknowns(osys) - -# ╔═╡ da5cfd6e-b57a-4235-b79d-a1c2f148328b -md""" -To get a list of the parameters, you can use the command `parameters`: -""" - -# ╔═╡ a09b3a39-bedc-4180-ae5b-8cec708a64cb -parameters(osys) - -# ╔═╡ 7fc8c671-b75c-4487-a013-779ee2422c8b -md""" -### Simulating the system as an ODE-problem - -We first need to load the Differential and Plot package, which is required for simulating the system and plotting the results. -""" - -# ╔═╡ 3197244f-655b-4dca-80f3-794b30722551 -md""" -Now we wish to simulate our model. To do this, we need to provide some the following information: - -- Initial conditions for the state variables $S$, $I$, $D$ and $R$. -- The parameter values for $\alpha$, $\beta$, $r$ and $m$. -- The timespan, which is the timeframe over which we wish to run the simulation. - -Assume in this example that there are $10\,000\,000$ people in the country, and that initially $1\,000$ person are infected. Hence, $I_0 = 1\,000$, $S_0 = 10\,000\,000-I_0 = 9\,999\,000$, $D_0 = 0$ and $R_0 = 0$.\ -Furthermore, we take the following values for the parameters: $\alpha = 0.08\;person/contact$, $\beta = 10^{-6}\;contact/(person^2\,day)$, $r = 0.2\;day^{-1}$ (i.e. a person is contagious for an average of $5\;days$) and $m=0.4$. The following table summarizes the above values: - -|Initial conditions |Parameters | -|:------------------------------------|:-------------------| -|$S_0 = 9\,999\,000$ |$\alpha = 0.08$ | -|$I_0 = 1\,000$ |$\beta = 10^{-6}$ | -|$D_0 = 0$ |$r = 0.2$ | -|$R_0 = 0$ |$m=0.4$ | - -Finally, we want to run our simulation from day $0$ till day $90$. -""" - -# ╔═╡ 979afe85-b910-44a0-8ac0-6e719cb9157e -md""" -### Setting initial conditions -""" - -# ╔═╡ 1ba859fa-46a5-434f-a99c-e710ba85caf8 -md""" -The initial conditions are given as a *Vector*. This is a type which collects several different values. To declare a vector, the values are specific within brackets, `[]`, and separated by `,`. Since we have four species, the vector holds four elements. E.g., we set the value of $I$ using the `:I => 1` syntax. Here, we first denote the name of the species (with a colon `:` pre-appended), next follows a `=>` and then the value of `I`.\ -The vector holding the initial conditions for $S$, $I$, $D$ and $R$ can be created in the following way: -""" - -# ╔═╡ 35bd9a1a-bb4a-4285-98f0-853b03c95cb7 -u0 = [:S => 9_999_000.0, :I => 1_000.0, :D => 0.0, :R => 0.0] - -# ╔═╡ 95c1c0ea-51d0-47ee-8948-a5b87c42a70d -md" -Note that the order of the vector elements doesn't matter, because the initial values of each of the species is indicated using its variable name. -" - -# ╔═╡ 57036f49-2f1f-4327-89ed-2c96098a1c22 -md""" -### Setting the timespan -""" - -# ╔═╡ 56ebb72d-2351-4e67-b268-1f48bbb77cb3 -md""" -The timespan sets the time point at which we start the simulation (typically `0.0` is used) and the final time point of the simulation. These are combined into a two-valued Tuple. Tuples are similar to vectors, but are enclosed by `()` and not `[]`. Again, we will let both time points be decimal valued. -""" - -# ╔═╡ a25d5925-a254-488b-b782-d29cff4470a2 -tspan = (0.0, 90.0) - -# ╔═╡ 0952b1d1-24b4-4540-91cd-94f7a4dcbd57 -md""" -### Setting parameter values -""" - -# ╔═╡ a235c7ce-f14a-4c7c-86a1-08aa5f2d9c85 -md""" -Similarly, the parameters values are also given as a vector. We have four parameters, hence, the parameter vector will also contain four elements. We use a similar notation for setting the parameter values as the initial condition (first the colon, then the parameter name, then an arrow, then the value). -""" - -# ╔═╡ 9a9440fa-d8a3-44bc-8037-4bf1f8af40b0 -params = [:α => 0.08, :β => 1.0e-6, :r => 0.2, :m => 0.4] - -# ╔═╡ c4e83ef8-9490-4361-a2a9-5abc45e242be -md""" -### Creating an ODEProblem -""" - -# ╔═╡ aa1b904a-c8a9-41a4-9297-8d7c821d4b77 -md" -Next, before we can simulate our model, we bundle all the required information together in a so-called **ODEProblem**. *Note that the order in which the input (the model name, the initial condition, the timespan, and the parameter values) is provided to the ODEProblem matters!* Here, we save our ODEProblem in the `oprob` variable. -" - -# ╔═╡ c6d2dd69-8c61-4a40-894f-664b2d2d14be -oprob = ODEProblem(infection_model, u0, tspan, params) - -# ╔═╡ 3c253bf3-886d-4e86-82ac-7751d23f342f -md""" -### Solving the ODEProblem -""" - -# ╔═╡ 14756171-9e8e-4cb0-b7af-74c2d649fe9f -md""" -We can now simulate our model. We do this by providing the ODEProblem to the `solve` function. There are some [examples](https://docs.sciml.ai/DiffEqDocs/stable/getting_started/) online on how to solve ODE problems with the *DifferentialEquations.jl* package. We save the output to the `sol` variable. Optionally, one can provide a [solver method](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Full-List-of-Methods) (e.g., `Tsit5`), and the time stepsize (with `saveat`). -""" -# https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Full-List-of-Methods - -# ╔═╡ 142e3e48-bf75-4498-ad0e-9f47cb921045 -# osol = solve(oprob) -osol = solve(oprob, Tsit5(), saveat=0.5) - -# ╔═╡ a3599781-a690-4fa3-b483-cd47727935cb -md""" -Note that at the different time points the variables values in the solution are decimal numbers (and not integer numbers), despite the fact that we are applying the model to individuals. This is inherent to using an ODE approach. Later on, we will see how we can discretise the problem, and hence, work on the level of individual infections (reactions).\ -Futhermore, note that executing the `solve` command at different occasions with an ODE problem will never modify the solution because ODE problems are **deterministic**. This will become different when simulating the individual infection (reaction) events by means of a stochastic (random) algorithm. -""" - -# ╔═╡ 8ac90e5c-ce85-4113-bc1b-9dc17cf7e928 -md""" -### Plotting the results -""" - -# ╔═╡ 0af3c166-46b6-455d-af6b-a72c4d2a5ce4 -md""" -Finally, we can plot the solution through the plot function. -""" - -# ╔═╡ 513c037b-c54c-47fa-b97a-06f69a983386 -plot(osol) - -# ╔═╡ 5672749b-283a-4e8e-8f90-76b5312b30ac -md""" -If you want to plot less species, like for example just $S$ and $I$, you can specify this with the option `idxs=[:S, :I]` (*notice the brackets*) in the plot function. -""" - -# ╔═╡ 01188934-ce0f-4d65-b880-caac4837d796 -plot(osol, idxs=[:S, :I]) # brackets [ ] - -# ╔═╡ f3cf01a1-3c65-4a37-bf9a-cd2233cb470b -md""" -If you want a fase plot of for example just $I$ versus $S$, you can specify this with the option `idxs=(:S, :I)` (*notice the parentheses*) in the plot function. You can indicate the $S$ and $I$ axes with the additional options `xlab="S"` and `ylab="I"`. -""" - -# ╔═╡ ff8f4c23-5695-48aa-9965-02677103f2c9 -plot(osol, idxs=(:S, :I), xlab="S", ylab="I") # parentheses ( ) - -# ╔═╡ b07f09c6-0515-4d27-9ca1-c45427a5988c -md""" -If you want to see the final values of $S$, $I$, $D$ and $R$, type: -""" - -# ╔═╡ ab4940b8-b8ec-4835-8d6e-4e57a5e2e464 -osol.u[end] - -# ╔═╡ a8b68546-9401-4815-9f33-cc22b7a0579d -md""" -If you want the vector of, e.g., $S$ values separately, type: -""" - -# ╔═╡ 89cc8970-693d-4885-b0b5-46110db4ca32 -osol[:S] - -# ╔═╡ dcdd693d-f2f3-406c-8647-31cdedcb823b -md""" -If you want the last value in the S vector, type: -""" - -# ╔═╡ 0a2ed6a6-1535-4721-a6da-008f62b16942 -osol[:S][end] - -# ╔═╡ 757d9f1e-5cba-4ae8-9172-e0c2fceb8458 -md""" -If you want the time vector separately, type: -""" - -# ╔═╡ 54bedb4d-51c5-4f58-bc4b-50cc469b7b04 -osol.t - -# ╔═╡ 764e2f1a-f974-4916-8573-cacba897cf07 -md" -## More advanced examples -" - -# ╔═╡ 2ae76ddb-71f5-49d7-a250-429d6c0138f6 -md""" -In Example 1 we will show you one way of how you could analyze the simulation results for a limited range of parameter values. - -In Examples 2 and 3 we will apply some new concepts, namely **discrete** and **continuous** events. The latter will basically affect, e.g., one or more parameter values or state variables during the solving process based on one or more *conditions* (also called *events*). These conditions can be either *time* or *state variable* related: - -- A time related condition is a vector of one (or more) timepoint(s) for which the value of one (or more) parameter(s) or state variable(s) need to be altered. We refer to them as **discrete events**. -- A state variable related condition is usually a condition for a certain value of a state variable. We will refer to them as **continuous events**. -""" - -# ╔═╡ 590f1b49-7442-4a71-af8c-8acdea071448 -md""" -**Important remark:**\ -You may have noticed that while using the Pluto notebooks, when you change the value of some variable (e.g., a parameter or an initial condition) that your results/plots will subsequently and automatically be altered based on the currect variable values in memory. In some cases this can be advantageous, in others not. For the latter reason, in this notebook, we will use slightly different variable names for some variables in order not to alter other results. -""" - -# ╔═╡ 45c1c238-a9f7-4f7b-a0ce-07b5bb4768d4 -md""" -### Example 1 - Influence of $r$ - -Influence of the duration of infection $1/r$ for average infection periods of between $10$, days and $1$ day contagious ($r$ between $0.1$ and $1.0$, step $0.1$, default value $0.1$). -""" - -# ╔═╡ b6baafc2-6d5e-43c3-8ef9-845961cdd20b -md""" -We will create a slider for the $r$-values between $0.1$ and $1.0$, stepsize $0.1$, default value $0.1$. -""" - -# ╔═╡ cd32beba-67cf-4b12-a77b-99f96263f0a4 -@bind r Slider(0.1:0.1:1, default=0.1, show_value=true) - -# ╔═╡ ae38c663-0ee4-409e-bfca-5f13ed88b67d -md""" -We will create een new parameter value vector, ODE problem and solution object by putting `1` at the end of the corresponding variable names. In that way, the previous simulation results will be unaffected! The model, the initial conditions and the timespan are identical as before. In there we also use the variable `r` coupled to the slider. -""" - -# ╔═╡ d44da6c6-c93d-4c61-8125-9eee464c897e -params1 = [:α => 0.08, :β => 1.0e-6, :r => r, :m => 0.4] - -# ╔═╡ 00a72697-d36a-41cc-9eec-8e821829ce0e -# put semi-colon at end of instruction to avoid seeing its output. -oprob1 = ODEProblem(infection_model, u0, tspan, params1); - -# ╔═╡ cf39b4cf-9cd0-4755-80db-ca4aea7c1084 -osol1 = solve(oprob1, Tsit5(), saveat=0.5); - -# ╔═╡ 52901bbf-e47b-4da1-95c4-f0869812398c -plot(osol1, ylim=(0, 1e7)) - -# ╔═╡ 102b4fbc-23b1-46ed-bb72-124eb88517ce -md""" -Now, change the value of $r$ in the `param1` vector and analyze the effect in the plot. -""" - -# ╔═╡ f6cbafbf-98b4-4286-86d2-fe95821a5ff4 -md""" -Try to interpret the results yourself. -Ask yourself the following questions: - -1. What are the trends in the results obtained? -""" - -# ╔═╡ ff9ce0c5-931f-44f4-aa0d-4aead0284160 -md"- Answer: missing" - -# ╔═╡ f5f98168-119c-4b49-a8e3-a3cc775faeb0 -md"""2. How can this be explained from the model structure?""" - -# ╔═╡ a308de53-80a2-4b37-a752-cb2c383cf7a4 -md"- Answer: missing" - -# ╔═╡ ade413c2-d7d9-4250-8490-75534900a389 -md""" -### Example 2 - Discrete Event - -Suppose that regulations are such that on day 14, people need to reduce their contacts by 50%. Hence, this means that the parameter value $\beta$ needs to be divided by a factor of 2 at timepoint 14. In order to realize that we need to now the order of the parameters in the model because we will need to address the value of $\beta$ by means of an index. -""" - -# ╔═╡ 58730ac6-d83b-420d-a000-2f50545f0d39 -md""" -We need to state that the parameter $\beta$ needs to be reduce by $50\%$ at time $t=14\,days$. We put this in a condition named `condition2`. -""" - -# ╔═╡ 7411474c-fac7-4b7c-8ded-4c2df5956fb0 -condition2 = [14.0] => [infection_model.β ~ infection_model.β/2] - -# ╔═╡ e6949052-6d12-4414-ac67-cb9435b46290 -md""" -The discrete time event needs to be included in our model. -""" - -# ╔═╡ 8752373f-a602-437b-9b3a-2602c8babf87 -@named infection_model2 = ReactionSystem(equations(infection_model), discrete_events=condition2) - -# ╔═╡ ee596a6c-29bf-4d18-b687-be943c128aa7 -md""" -After that, we need to *complete* our *reaction network model*. -""" - -# ╔═╡ 40f849cb-20f9-4bbe-806e-512abd6f3210 -infection_model2_com = complete(infection_model2) - -# ╔═╡ d4e537da-490b-4781-9a76-c89d762849ec -md""" -Then we need to create a new ODE problem. -""" - -# ╔═╡ 4a6f357c-afd7-4b0d-b1d2-15f5c0e4298b -oprob2 = ODEProblem(infection_model2_com, u0, tspan, params) - -# ╔═╡ e76e77be-9f15-4aa5-8506-cdb32a6ec9b1 -md""" -Finally, the ODE problem can be solved. Notice that you need to make a deepcopy of the ODE problem, because otherwise changes to the parameter $\beta$ will remain after the first call to `solve`. -""" - -# ╔═╡ 37b11fdb-8c16-4ec4-a7ad-e2fdbaf4ea5d -osol2 = solve(deepcopy(oprob2), Tsit5(), saveat=0.5) - -# ╔═╡ 59f4f4dd-f0b5-4354-b53c-4554c878d5a8 -md""" -Now we can plot the results. -""" - -# ╔═╡ 0af84963-ca64-4958-a544-42d445da5a7c -plot(osol2) - -# ╔═╡ 18151ab1-1e4e-48d8-be70-4fa4c8a43af1 -md""" -If you want to see the final values of $S$, $I$, $D$ and $R$, type: -""" - -# ╔═╡ 7d7ed974-7c9c-4c30-ab2a-e3028ce702dd -osol2.u[end] - -# ╔═╡ 7375edbc-24a4-4300-bdef-2686cb377cfc -md""" -Try to interpret the results yourself. Ask yourself the following questions: - -1. What are the trends in the results obtained? -""" - -# ╔═╡ a736a17b-ee8e-491b-840a-6319619a8dab -md"- Answer: missing" - -# ╔═╡ 2ba54805-e901-4281-9b07-cc7137b8c809 -md""" -2. How much less casualties are there compared to not altering the contact rate? -""" - -# ╔═╡ 20d56d2f-2532-4286-88cc-b42ccb22d246 -md"- Answer: missing" - -# ╔═╡ cb8a6f77-f08c-4fc8-9445-bd1c17521fcc -md""" -### Example 3 - Continuous Event - -Suppose that when the number of infected individuals reaches $1\,000\,000$, then $999\,000$ of them are promptly put into isolation (or removed from the population). Hence, a $1000$ individuals remain infected at some point. -""" - -# ╔═╡ 5005a09d-a844-4f9d-a058-0d93583d5bab -md""" -Normally in a continuous event the value of one or more species can be changed when a certain condition is met. In our specific case we want the change in the species happening only once! So, if you want that the continuous event: ''when $I$ reaches $10^6$ then $999000$ is subtrated from $I$'' happens only once, then we need to include a ficticious new *species* in our *reaction network model*. We will call this ficticious *species* `pwc` (a short for _**p**roceed **w**ith **c**ondition_) and we set it default to `true`. -""" - -# ╔═╡ f77d2d75-468f-4746-b81e-0bbbf33fc8d7 -infection_model3 = @reaction_network begin - @species pwc(t)=true - α * β, S + I --> 2I - r * m, I --> D - r * (1 - m), I --> R -end - -# ╔═╡ 51f0f4b0-0332-416f-8ade-582fa4ba0257 -species(infection_model3) - -# ╔═╡ 30bcea84-f7ca-4907-9a0f-54934e1731d0 -md""" -We create the condition in the following way. When `pwc` is true then $I$ will be changed and also `pwc` will become `false`, so that the condition happens only once. We assume hereby that $I$ will never reach $0$! -""" - -# ╔═╡ 87d9be87-3bc7-4442-a396-fb501355fe8c -condition3 = [infection_model3.I ~ 1e6*infection_model3.pwc] => [infection_model3.I ~ infection_model3.I - 0.999e6, infection_model3.pwc ~ false] - -# ╔═╡ 467834b5-0063-4e91-b446-2828a1d44d78 -md""" -The continuous event needs to be included in our model. -""" - -# ╔═╡ 09ec4a21-c8fd-42fe-ae0c-f3a548c7032c -@named infection_model3_c = ReactionSystem(equations(infection_model3), continuous_events=condition3) - -# ╔═╡ 7b93b776-caff-4608-9ec3-8519b9b95e51 -md""" -After that, we need to *complete* our *reaction network model*. -""" - -# ╔═╡ 96787705-f294-4f3c-9acf-8af7f6e0d579 -infection_model3_c_com = complete(infection_model3_c) - -# ╔═╡ 54d5f92c-de07-4c06-8bc5-e11e2b2c5764 -md""" -Then we need to create a new ODE problem. -""" - -# ╔═╡ e0562699-d065-4ae1-9c39-f9e0bf561fa3 -oprob3 = ODEProblem(infection_model3_c_com, u0, tspan, params) - -# ╔═╡ 6b3ea888-1238-46ae-9eaf-52e86c76bc1c -md""" -Finally, the ODE problem can be solved. Notice that you need to make a deepcopy of the ODE problem again. -""" - -# ╔═╡ abe11178-7177-418c-98b4-da1afc56842e -osol3 = solve(deepcopy(oprob3), Tsit5(), saveat=0.1) - -# ╔═╡ 23804c2a-d031-4025-92d3-5cdb74f87353 -md""" -Now we can plot the results. -""" - -# ╔═╡ 02173eaa-1178-4383-b57a-03e53ce38af8 -plot(osol3) - -# ╔═╡ 6d1aa79b-8614-4a77-a327-f7e6962d1944 -md""" -If you want to see the final values of $S$, $I$, $D$, $R$ and `pwc`, type: -""" - -# ╔═╡ d04b8d97-e9d3-4428-a695-bfde4b44a291 -osol3.u[end] - -# ╔═╡ 70fd136e-5ded-4c30-818e-a5de61fbbf86 -md""" -Try to interpret the results yourself. Ask yourself the following questions: - -1. What are the trends in the results obtained? -""" - -# ╔═╡ 7872701b-8506-4303-b615-32d3c1afa50d -md"- Answer: missing" - -# ╔═╡ 0d6c2233-35c5-4257-9dfd-398704080ba3 -md""" -2. How much less casualties are there compared to not putting $999\,000$ individuals into isolation? (Hint: you also need to take into account the casualties in the $999\,000$ individuals that had been put into isolation.) -""" - -# ╔═╡ 029a2024-1ec7-4af9-9e73-717f13483468 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═be326550-25ea-4c5f-ac4a-dd74d12bc89a -# ╠═cd166045-7788-4f5c-97d9-d1bedeceedae -# ╠═e6e748a5-b692-4c7c-b39e-ef0fe9e46c35 -# ╠═3cfe381f-5871-4447-9149-fc023c787b42 -# ╟─9ea49d7a-e524-11ee-1b68-9d1d71aaba24 -# ╟─ba4dfc95-b74f-4d36-b34b-5eb2836a5cd6 -# ╟─94440e99-7c0b-4d87-8038-141a2bc5fcb8 -# ╟─83fa478c-ac54-482b-92de-8eb6a01ddfaf -# ╟─d257102b-8480-4ce3-bdba-50995ccbdc26 -# ╟─df9a46ab-56eb-4d3d-a3a8-e1f2da59b32e -# ╟─7dd37224-071b-4ec7-88d5-39fd4482e615 -# ╟─97d34ab6-a1c6-404d-af1f-7da85287757f -# ╟─b5e6cb25-da6f-475a-bc73-041c4c9256d5 -# ╟─529ddac3-bb7a-4cc3-9bbd-ca31f5796e27 -# ╟─f9ca0b86-1447-48c1-88e1-876180d7628a -# ╟─bf2b06d8-884b-4a35-b308-83a2f3d6696f -# ╟─1f405b09-53cd-43e0-b498-70eafef92acb -# ╟─39d80a3f-14d2-49d6-905b-171adf5930a0 -# ╟─44c6cdc0-8610-43bd-b3b3-a7441ee62615 -# ╠═b1bfe3b7-782c-46c9-86db-829942b6a9d0 -# ╟─cd3da548-b24f-426f-8b46-2db70e0a979c -# ╠═7d2f8bf9-3a00-4631-84ff-1a36b6d7e19b -# ╟─4a504e36-8269-4822-8919-75b7f6dcaf6d -# ╟─bb5578b8-40d4-48c9-be87-cfaeadb8c5f9 -# ╟─af6b169d-55c3-4384-9f57-ad629e02bad6 -# ╟─52b2cc8f-1eac-47c1-8d42-09c1b91d1350 -# ╠═5f1d6198-ccdc-4a63-bf98-2032a6d683ad -# ╟─59686253-3e0e-454e-8bc6-dde189ee2197 -# ╠═fdb79a26-fbc5-482a-8cdb-760d18f57a77 -# ╟─2bd02577-7321-46b5-9f42-1484ed86d1d3 -# ╠═64455b26-4374-435f-b71b-8b9600cab263 -# ╟─64a48327-03ec-46a0-9a76-79c10acb6e20 -# ╠═78a22ba2-1e60-40b3-989c-922dcf9ca054 -# ╟─eeb25e50-165b-4e93-8397-5b09fe8e7242 -# ╟─0d39fbbc-4fcb-45eb-b24b-14f2e093c98c -# ╠═ca18795a-d270-493f-807f-a3b8c78aa6d7 -# ╟─3f6f38a3-12ec-4b5b-8ebb-9e48190d3f7e -# ╠═72f7f870-b2fc-4df1-9325-3c587be7e014 -# ╟─da5cfd6e-b57a-4235-b79d-a1c2f148328b -# ╠═a09b3a39-bedc-4180-ae5b-8cec708a64cb -# ╟─7fc8c671-b75c-4487-a013-779ee2422c8b -# ╠═19398992-dca7-441a-a5fd-7f9d0ad30be5 -# ╟─3197244f-655b-4dca-80f3-794b30722551 -# ╟─979afe85-b910-44a0-8ac0-6e719cb9157e -# ╟─1ba859fa-46a5-434f-a99c-e710ba85caf8 -# ╠═35bd9a1a-bb4a-4285-98f0-853b03c95cb7 -# ╟─95c1c0ea-51d0-47ee-8948-a5b87c42a70d -# ╟─57036f49-2f1f-4327-89ed-2c96098a1c22 -# ╟─56ebb72d-2351-4e67-b268-1f48bbb77cb3 -# ╠═a25d5925-a254-488b-b782-d29cff4470a2 -# ╟─0952b1d1-24b4-4540-91cd-94f7a4dcbd57 -# ╟─a235c7ce-f14a-4c7c-86a1-08aa5f2d9c85 -# ╠═9a9440fa-d8a3-44bc-8037-4bf1f8af40b0 -# ╟─c4e83ef8-9490-4361-a2a9-5abc45e242be -# ╟─aa1b904a-c8a9-41a4-9297-8d7c821d4b77 -# ╠═c6d2dd69-8c61-4a40-894f-664b2d2d14be -# ╟─3c253bf3-886d-4e86-82ac-7751d23f342f -# ╟─14756171-9e8e-4cb0-b7af-74c2d649fe9f -# ╠═142e3e48-bf75-4498-ad0e-9f47cb921045 -# ╟─a3599781-a690-4fa3-b483-cd47727935cb -# ╟─8ac90e5c-ce85-4113-bc1b-9dc17cf7e928 -# ╟─0af3c166-46b6-455d-af6b-a72c4d2a5ce4 -# ╠═513c037b-c54c-47fa-b97a-06f69a983386 -# ╟─5672749b-283a-4e8e-8f90-76b5312b30ac -# ╠═01188934-ce0f-4d65-b880-caac4837d796 -# ╟─f3cf01a1-3c65-4a37-bf9a-cd2233cb470b -# ╠═ff8f4c23-5695-48aa-9965-02677103f2c9 -# ╟─b07f09c6-0515-4d27-9ca1-c45427a5988c -# ╠═ab4940b8-b8ec-4835-8d6e-4e57a5e2e464 -# ╟─a8b68546-9401-4815-9f33-cc22b7a0579d -# ╠═89cc8970-693d-4885-b0b5-46110db4ca32 -# ╟─dcdd693d-f2f3-406c-8647-31cdedcb823b -# ╠═0a2ed6a6-1535-4721-a6da-008f62b16942 -# ╟─757d9f1e-5cba-4ae8-9172-e0c2fceb8458 -# ╠═54bedb4d-51c5-4f58-bc4b-50cc469b7b04 -# ╟─764e2f1a-f974-4916-8573-cacba897cf07 -# ╟─2ae76ddb-71f5-49d7-a250-429d6c0138f6 -# ╟─590f1b49-7442-4a71-af8c-8acdea071448 -# ╟─45c1c238-a9f7-4f7b-a0ce-07b5bb4768d4 -# ╟─b6baafc2-6d5e-43c3-8ef9-845961cdd20b -# ╠═cd32beba-67cf-4b12-a77b-99f96263f0a4 -# ╟─ae38c663-0ee4-409e-bfca-5f13ed88b67d -# ╠═d44da6c6-c93d-4c61-8125-9eee464c897e -# ╠═00a72697-d36a-41cc-9eec-8e821829ce0e -# ╠═cf39b4cf-9cd0-4755-80db-ca4aea7c1084 -# ╠═52901bbf-e47b-4da1-95c4-f0869812398c -# ╟─102b4fbc-23b1-46ed-bb72-124eb88517ce -# ╟─f6cbafbf-98b4-4286-86d2-fe95821a5ff4 -# ╟─ff9ce0c5-931f-44f4-aa0d-4aead0284160 -# ╟─f5f98168-119c-4b49-a8e3-a3cc775faeb0 -# ╟─a308de53-80a2-4b37-a752-cb2c383cf7a4 -# ╟─ade413c2-d7d9-4250-8490-75534900a389 -# ╟─58730ac6-d83b-420d-a000-2f50545f0d39 -# ╠═7411474c-fac7-4b7c-8ded-4c2df5956fb0 -# ╟─e6949052-6d12-4414-ac67-cb9435b46290 -# ╠═8752373f-a602-437b-9b3a-2602c8babf87 -# ╟─ee596a6c-29bf-4d18-b687-be943c128aa7 -# ╠═40f849cb-20f9-4bbe-806e-512abd6f3210 -# ╟─d4e537da-490b-4781-9a76-c89d762849ec -# ╠═4a6f357c-afd7-4b0d-b1d2-15f5c0e4298b -# ╟─e76e77be-9f15-4aa5-8506-cdb32a6ec9b1 -# ╠═37b11fdb-8c16-4ec4-a7ad-e2fdbaf4ea5d -# ╟─59f4f4dd-f0b5-4354-b53c-4554c878d5a8 -# ╠═0af84963-ca64-4958-a544-42d445da5a7c -# ╟─18151ab1-1e4e-48d8-be70-4fa4c8a43af1 -# ╠═7d7ed974-7c9c-4c30-ab2a-e3028ce702dd -# ╟─7375edbc-24a4-4300-bdef-2686cb377cfc -# ╟─a736a17b-ee8e-491b-840a-6319619a8dab -# ╟─2ba54805-e901-4281-9b07-cc7137b8c809 -# ╟─20d56d2f-2532-4286-88cc-b42ccb22d246 -# ╟─cb8a6f77-f08c-4fc8-9445-bd1c17521fcc -# ╟─5005a09d-a844-4f9d-a058-0d93583d5bab -# ╠═f77d2d75-468f-4746-b81e-0bbbf33fc8d7 -# ╠═51f0f4b0-0332-416f-8ade-582fa4ba0257 -# ╟─30bcea84-f7ca-4907-9a0f-54934e1731d0 -# ╠═87d9be87-3bc7-4442-a396-fb501355fe8c -# ╟─467834b5-0063-4e91-b446-2828a1d44d78 -# ╠═09ec4a21-c8fd-42fe-ae0c-f3a548c7032c -# ╟─7b93b776-caff-4608-9ec3-8519b9b95e51 -# ╠═96787705-f294-4f3c-9acf-8af7f6e0d579 -# ╟─54d5f92c-de07-4c06-8bc5-e11e2b2c5764 -# ╠═e0562699-d065-4ae1-9c39-f9e0bf561fa3 -# ╟─6b3ea888-1238-46ae-9eaf-52e86c76bc1c -# ╠═abe11178-7177-418c-98b4-da1afc56842e -# ╟─23804c2a-d031-4025-92d3-5cdb74f87353 -# ╠═02173eaa-1178-4383-b57a-03e53ce38af8 -# ╟─6d1aa79b-8614-4a77-a327-f7e6962d1944 -# ╠═d04b8d97-e9d3-4428-a695-bfde4b44a291 -# ╟─70fd136e-5ded-4c30-818e-a5de61fbbf86 -# ╟─7872701b-8506-4303-b615-32d3c1afa50d -# ╟─0d6c2233-35c5-4257-9dfd-398704080ba3 -# ╟─029a2024-1ec7-4af9-9e73-717f13483468 diff --git a/src/exercises/ode_model_diver_mtk.jl b/src/exercises/ode_model_diver_mtk.jl new file mode 100644 index 00000000..17750cf8 --- /dev/null +++ b/src/exercises/ode_model_diver_mtk.jl @@ -0,0 +1,886 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "3" +#> title = "1. ODE_model_diver" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "modeling of pressure on diver with MTK" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 758484ec-9945-11f0-2834-e37380616e53 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 0ee79db9-1934-4c18-b0ed-28057a852614 +using Markdown, InteractiveUtils + +# ╔═╡ e430b466-ca2f-42f9-b9cf-f73c429506f6 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 8d55ad5c-cdfa-45dc-9abc-81cc7f1899be +using OrdinaryDiffEq, ModelingToolkit + +# ╔═╡ 7d8cd22d-fc4e-4f4d-9640-fbe4f0fc075b +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ de2a2b81-1896-4faa-8936-37e0a176978e +md""" +# Exercise: Diver +""" + +# ╔═╡ 047f4c07-527f-416d-b5c6-f9f7a26122e5 +solution(text) = Markdown.MD(Markdown.Admonition("hint", "Solution", [text])); + +# ╔═╡ ece4a09b-781a-48d1-994c-856674b6ef71 +md""" +A diver is pulled up from a depth of $30\;m$ to a ship with a constant velocity $v$. The pressure change that takes place in the body is proportional to the difference between the ambient pressure (at a certain depth) and the internal pressure in the diver's body through a coefficient gradient of $c_2\;[s^{−1}]$. We are interested in the rate of pressure change in the body, as it has to stay below a certain critical value (estimated to be $0.02\;bar/s$) if we want to avoid the Caisson disease. +""" + +# ╔═╡ 92e3c710-9cd2-4441-a395-f4303d3c122e +# PlutoUI.LocalResource("fig/diver_position.png") +# https://users.ugent.be/~gvhaelew/fig/diver_position.png +md""" +![Diver](https://users.ugent.be/~gvhaelew/fig/diver_position.png) +""" + +# ╔═╡ 25ab9b2b-c594-4a48-ab77-910897597782 +md""" +Consider a $Z$-axis pointing upwards with the water surface at position $z=0\;m$. Hence, the position of the diver (beneath the water surface) is negative ($z < 0$). The pressure that the water exerts on the diver's body is given by Pascal's law: $p = p_a - \rho g z$, with $p_a$ the air pressure. It is easy to verify that $p = p_a$ at $z = 0$. + +**Important remaks:** +- When the diver is residing for a relatively long period of time at a certain position $z$, its internal body pressure $p_b$ will equal the ambient pressure $p$ ($= p_a - \rho \, g \, z$). +- When the diver is (suddenly) pulled up, it takes time for the internal body pressure of the diver to adapt to the (new) ambient pressure. Assume a linear relationship between the rate of change in internal body pressure and the difference between the ambient and the internal body pressure: $$\cfrac{dp_b}{dt} = c_2 \left(p - p_b\right) \, ,$$ with $c_2$ the adaption coefficient. +- Since the adaption of $p_b$ is not instant (cf. adaptation coefficient $c_2$), the factor $p - p_b$ will be negative, and hence, also $\cfrac{dp_b}{dt}$. It is the absolute value of $\cfrac{dp_b}{dt}$ that must stay below a certain critical value in order to avoid the Caisson disease. +""" + +# ╔═╡ 8bba353f-557c-4b42-a12c-666fa35fbd47 +md""" +## Part 1: constant speed +""" + +# ╔═╡ 79bdf2b6-1b34-4543-902d-58b14adc9c92 +md""" +#### Deriving the model equations +""" + +# ╔═╡ 28a230f6-78be-4d0c-9258-bf7b8290e33a +md""" +!!! task + Derive the model equations the position $z$ and the body pressure $p_b$. +""" + +# ╔═╡ bccfb4eb-5b3d-4027-b367-d6f035f5db29 +md""" +!!! hint + - Set up: + ``` math + \begin{align} + \cfrac{d z}{dt} &= \cdots \\ + \cfrac{d p_b}{dt} &= \cdots + \end{align} + ``` +""" + +# ╔═╡ 12744c81-58da-43b4-88f3-33190a57d79c +solution(md""" +``` math +\begin{align} + \cfrac{d z}{dt} &= v \\ + \cfrac{d p_b}{dt} &= c_2 \left( p_a - \rho g z - p_b\right) +\end{align} +``` +""") + +# ╔═╡ 3c1ce356-19b0-4ac2-ad01-24be3f2baaa3 +md""" +### Setting up the equations +""" + +# ╔═╡ 67b148b0-777f-4d34-b5af-c8f92e869ebe +md""" +Consider for this part the velocity $v$ being constant with a value of $0.15\;m/s$. Set up a model for the position $z\;[m]$ and the body pressure $p_b\;[bar]$. Include in your model equations also a variable keeping track of the rate of change in body pressure $dp_b/dt\;[bar/s]$. +""" + +# ╔═╡ c58a62a3-6519-4a81-a851-04ce6ec8a9db +md""" +Define variables for the position $z$, the body pressure $p_b$ and the rate of change in body pressure $dp_b/dt$. Use the following variable names: `z`, `pb` and `dpbdt`. +""" + +# ╔═╡ 7be849c3-d841-4967-936b-2e42dc332de5 +# @variables missing + +# ╔═╡ d548f583-5f9b-4921-8e89-85995b892e51 +md""" +Define the parameters for this model and assign their corresponding values. + +| Parameter | Value | Unit | Meaning | +|:---------- |:---------- |:------------|:------------| +| $p_a$ | 101325*1e-5 | $bar$ | Air pressure | +| $\rho$ | 1000.0 | $kg/m^3$ | water density | +| $g$ | 9.81 | $m/s^2$ | gravitational constant | +| $c_2$ | 0.05 | $1/s$ | coefficient gradient | +| $v$ | 0.15 | $m/s$ | pull-up velocity | +""" + +# ╔═╡ 66ed6b4f-2af3-4ec9-bd6f-4861129ae379 +md"Use the following names: `pa`, `ρ`, `g`, `c₂` and `v_const`." + +# ╔═╡ fb444a48-63ec-4bf4-aa55-8e8ae04640cd +# @parameters missing + +# ╔═╡ 1916a1ca-565b-4692-82c2-5484a486e435 +md""" +Define the initial diver position. +""" + +# ╔═╡ 9a289ab4-87b5-452e-890e-2cad5c16ab4a +z₀ = missing + +# ╔═╡ 3a4501ed-79dc-4b6e-ba09-df08d78f8924 +md""" +Define the initial body pressure. Don't forget to include `*1e-5` in the hydrostatic term in order to have everything in $bar$. +""" + +# ╔═╡ 07b8e2e3-3fad-4602-a3f0-55461ed1b5fd +pb₀ = missing + +# ╔═╡ 962f3a24-cecf-4c99-9850-4706d41ed70f +md""" +In order to have an idea of this initial body pressure, it is preferable to calculate it in a `let` ... `end` block. Everything in this block will be local scope and won't interfere with variable names defined elsewhere. +""" + +# ╔═╡ 3b460c68-cd31-4adf-985a-057508b40105 +# let +# pa=101325*1e-5; ρ=1000.0; g=9.81; +# missing +# end + +# ╔═╡ fd38ceaf-ca26-43a7-8e8b-52aa63f389a9 +md""" +Set up the equation for the rate of change in position. +""" + +# ╔═╡ 3c1b7030-9e95-421f-b818-ede261ff8ff9 +eq1_position = missing + +# ╔═╡ 57aa99f6-bfa0-46a5-9565-ecc4ea04dffb +md""" +Set up the equation for the rate of change in body pressure. Don't forget to include `*1e-5` in one of the terms in order to have the pressure in $bar$. +""" + +# ╔═╡ ba1b330b-d57d-4f40-841d-a05840a4c059 +eq1_body_pressure = missing + +# ╔═╡ 3a43debf-ed0b-4fcf-8aaf-496de76c3914 +md""" +Set up the equation that will keep track of the rate of change in body pressure. +""" + +# ╔═╡ e59c36db-83c3-4ac8-81a1-2a5c175c75ff +eq1_dpbdt = missing + +# ╔═╡ f42f55a5-4063-4f14-ae30-0d0468e32f61 +md""" +Bundle all equations. +""" + +# ╔═╡ 32f4e0d7-b512-409a-9831-fb3073fe3c3d +eqns1_diver = missing + +# ╔═╡ ef793c81-0e60-46af-b681-376265f333e8 +md""" +### Building the ODE system +""" + +# ╔═╡ 4ca01607-0a0a-4ada-96de-44286565f6be +md""" +Build the model and include the (continuous) event that when $z$ hits zero, the pull-up velocity must become zero as well. Name the model `sys1_diver`. +""" + +# ╔═╡ fafc4370-569b-4e05-9683-8e54340713d2 +# @mtkbuild missing + +# ╔═╡ e8e2b1a6-a436-45c0-a4d3-1a7baf0ec99d +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ d42afda6-edd4-4fb6-93b9-8663adc0c96d +md""" +Create the ODE problem, specifying the initial values for the diver's position and internal body pressure. Use a simulation time span of `500.0` seconds. Use an empty vector `[]` for the parameters argument to use their default values. +""" + +# ╔═╡ df8fda53-7226-409f-89a4-7375a26468e6 +oprob1_diver = missing + +# ╔═╡ 84f365dd-04f8-4763-b5a3-95226eba9a98 +md""" +Solve the ODE problem. Make a deepcopy of the ODE problem, use `Tsit5()` and `saveat=1`. +""" + +# ╔═╡ 700103f1-e0a7-4d09-8b1e-c152e9485c65 +sol1_diver = missing + +# ╔═╡ b45e8caf-e0e6-4484-b858-1129dd5b7834 +md""" +### Plotting results +""" + +# ╔═╡ df0f1383-425e-4f4b-8e30-54e072fe481e +md""" +Plot the position of the diver over time. Include a y-label (cf. `ylabel="..."`) and a title (cf. `title="..."`) in all plots. +""" + +# ╔═╡ abfe5216-48b7-4c57-8c51-79c49493e419 +missing + +# ╔═╡ 53dd78a4-40d3-48e7-94fb-731a53f6eb79 +md""" +Plot the body pressure of the diver over time. +""" + +# ╔═╡ dd525893-627f-4e40-8674-e80cb51da255 +missing + +# ╔═╡ 6f7d3199-d223-4069-a41c-aa774566d990 +md""" +Plot the rate of change in body pressure over time. +""" + +# ╔═╡ 97eff5f5-5c6f-4cf6-9d3e-8ceb216eab63 +missing + +# ╔═╡ f3d946f8-54e0-4e48-b6a9-4b5eb89ca412 +md""" +Calculate the maximum rate of change in the body pressure in absolute value. +""" + +# ╔═╡ c8481d75-945a-4d89-b3d2-f32f5e65bef2 +missing + +# ╔═╡ 087a0928-e8f9-48a4-b946-c517a404d3dc +md""" +!!! question + Is this maximum rate of change in body pressure for a diver that is pulled up at a speed of $0.15\;m/s$ a safe value? +""" + +# ╔═╡ a3ec7991-b130-4917-b5f7-2b77a45fffb2 +md""" +Answer: missing +""" + +# ╔═╡ a41f2eec-5a73-4510-b189-f8d11d2aa771 +md""" +## Part 2: external forces +""" + +# ╔═╡ 1d07eb14-dc16-42b0-9707-2499cdcd106e +md""" +Suppose that the diver is now attached with a rope to a motor on a boat crane to be pulled up. + +Four forces now act on the diver: gravity, a frictional force, the force exerted by acceleration and the Archimedean force. The frictional force on the diver is proportional to the speed of the diver through a coefficient $c_1,[kg/s]$, while the upward Archimedean force is given by $F_{arch}=\rho V g$, with $V$ the volume of the diver. +""" + +# ╔═╡ 91734c50-f848-448b-ad13-5b26a41fa0eb +md""" +### Deriving the model equations +""" + +# ╔═╡ 9bb2ec9d-9661-4808-85b9-2b46066a5659 +md""" +In this case the velocity $v$ won't be constant in the beginning but will be governed by the external force. + +**You need to take into account the following forces:** +- The external force: $\vec{F}_{ext} = F_{ext}\, \vec{e}_z$. This force is directed upward. +- The buoyuancy force: $\vec{F}_b = \rho \, V \, g \, \vec{e}_z$. This force is always directed upward. +- The weight of the diver: $\vec{F}_g = -m\,g\,\vec{e}_z$. This force is always directed downward. +- The friction force between the diver and the water: $\vec{F}_w = -c_1\,v \,\vec{e}_z$. This force is always opposite the movement direction of the diver. In our case the diver is pulled up, hence, this force is directed downward (remember that $v=\cfrac{dz}{dt} > 0$). +""" + +# ╔═╡ 07e2d7c1-05b6-4309-a524-782dfe6bc14c +md""" +!!! task + Derive the model equations for the position $z$, the velocity $v$ and the body pressure $p_b$. +""" + +# ╔═╡ 7f32442f-3028-457c-9013-39248fb26cb9 +md""" +!!! hint + - Start setting up: + ``` math + \begin{align} + m\cfrac{d^2 z}{dt^2} &= \cdots \\ + \cfrac{d p_b}{dt} &= \cdots + \end{align} + ``` + and work toward: + ``` math + \begin{align} + \cfrac{d z}{dt} &= \cdots \\ + m \cfrac{d v}{dt} &= \cdots \\ + \cfrac{d p_b}{dt} &= \cdots + \end{align} + ``` +""" + +# ╔═╡ 0dbe6003-b50b-41af-bee7-201bf67bf7ca +solution(md""" +``` math +\begin{align} + \cfrac{d z}{dt} &= v \\ + m \cfrac{d v}{dt} &= F_{ext} + \left(\rho V - m \right)g - c_1 v\\ + \cfrac{d p_b}{dt} &= c_2 \left( p_a - \rho g z - p_b\right) +\end{align} +``` +""") + +# ╔═╡ 04d9a2cb-5f73-4ac0-8f93-1733f7f71840 +md""" +### Setting up the equations +""" + +# ╔═╡ 180a67b1-a03e-4dbc-9331-d82e688e49d0 +md""" +Define an additional variable for the velocity $v$. Use the following variable name: `v`. +""" + +# ╔═╡ c78d0438-ba53-4f37-ac5e-9a32172c501b +# @variables missing + +# ╔═╡ 588e1ad2-181d-4a1f-b974-a7f5f0f70c28 +md""" +Define the additional parameters for this model and assign their corresponding values. + +| Parameter | Value | Unit | Meaning | +|:---------- |:---------- |:------------|:------------| +| $m$ | 100 | $kg$ | mass of the diver | +| $V$ | 0.082 | $m^3$ | volume of the diver | +| $c_1$ | 20.0 | $kg/s$ | friction coefficient | +""" + +# ╔═╡ c94700b4-462e-4179-9845-1d3c52617709 +md"Use the following names: `m`, `V` and `c₁`." + +# ╔═╡ 1d3a4fba-9593-4503-8656-ffbceb8046ec +# @parameters missing + +# ╔═╡ e88c9c4e-d50b-436a-a810-76fe40ddc24d +md""" +In order to have an idea of what external force you need to pull up the diver at a constant velocity of 0.15 m/s, you can calculate it by setting $\cfrac{dv}{dt}$ to zero in the equation for the rate of change in the velocity, and solving for $F_{ext}$. +""" + +# ╔═╡ 2d5c2f05-c902-4b6d-8b73-38d092a11fd8 +# let +# ρ=1000.0; g=9.81; v_const=0.15; +# m=100.0; V=0.082; c₁=20.0; +# missing +# end + +# ╔═╡ 9db90eab-683d-4c7c-a0b9-5c7904dd0aa2 +md""" +Set up the additional parameter for the external force with a default value of 180. Name it `Fext`. +""" + +# ╔═╡ c8b34161-ecb6-4be7-a4c4-d0381198846c +# @parameters missing + +# ╔═╡ acf469f8-9765-47e9-b4f5-55715a4075c6 +md""" +Set up the equation for the rate of change in position. Multiply the right handside of the equation with $(z < 0)$ to make sure that after $z$ hits $0$, $z$ remains constant and, hence, also $v$ remains $0$. +""" + +# ╔═╡ 81bba0e7-fc3a-4304-987a-099a43fb38eb +eq2_position = missing + +# ╔═╡ 03711ec2-bab0-4917-a40a-01ac2904612b +md""" +Set up the equation for the rate of change in velocity. This includes the external force, the buoyuancy force, the weight of the diver and the frictional force. +""" + +# ╔═╡ 5a002b19-5986-4adb-84d9-90e95c655a32 +eq2_velocity = missing + +# ╔═╡ 4b3de655-674e-4a13-83f3-f09b6443b26a +md""" +Set up the equation for the rate of change in body pressure. Don't forget to include `*1e-5` in one of the terms in order to have everything in $bar$. +""" + +# ╔═╡ c50c8b9c-0e1c-4f7a-800a-33220e83dfed +eq2_body_pressure = missing + +# ╔═╡ b6c8b269-b215-4213-8248-4a0dee79a43a +md""" +Set up the equation that will keep track of the rate of change in body pressure. +""" + +# ╔═╡ bbf20a87-77f2-4818-b0bb-ad67b30a5bbc +eq2_dpbdt = missing + +# ╔═╡ 37f10ef2-d46f-499e-bbad-f0df56b70540 +md""" +Bundle all equations. +""" + +# ╔═╡ 9e29b21e-20c9-4b98-84d8-f76b42c627c8 +eqns2_diver = missing + +# ╔═╡ e3483144-c9c5-4fdb-8053-fe0c08ae8765 +md""" +### Building the ODE system +""" + +# ╔═╡ 9827f9e4-d514-454e-893f-42f9c97b5a43 +md""" +Build the model and include the (continuous) event that when $z$ hits zero, $v$ must become zero as well. Name the model `sys2_diver`. +""" + +# ╔═╡ 70a6a572-c25a-40bd-824a-80b21957efbb +# @mtkbuild missing + +# ╔═╡ fd3ae56c-34fe-4615-b73e-088e490fbb59 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ 4bb1e0f4-2544-437b-8523-4e5547caee25 +md""" +Create the ODE problem. The initial conditions for the position and body pressure are the same as before. For $v$, assume that the diver is initially at rest. Use a simulation time span of `500.0` seconds. Use the default values of the parameters again. +""" + +# ╔═╡ 26eb5e36-4026-41b8-b2fc-4be950727e29 +oprob2_diver = missing + +# ╔═╡ da2cd981-9b9e-42dc-b1b3-9ac09b53c5e1 +md""" +Solve the ODE problem. Make a deepcopy of the ODE problem, use `Tsit5()` and `saveat=1`. +""" + +# ╔═╡ 61fdda96-1c27-42aa-b6d3-dd0b2f9dae6c +sol2_diver = missing + +# ╔═╡ 790ce55b-fffb-49d7-8013-755cb647cf7d +md""" +### Plotting results +""" + +# ╔═╡ d2e5c67a-fb7a-4cf1-a849-ff11734703fe +md""" +Plot the position of the diver over time. Include a y-label (cf. `ylabel="..."`) and a title (cf. `title="..."`) in all plots. +""" + +# ╔═╡ 5e1faec8-d39b-4d41-be7e-7de077b807fe +missing + +# ╔═╡ dd8ba91e-7e9c-4857-8682-b01dbc7a595e +md""" +Plot the velocity of the diver over time. +""" + +# ╔═╡ 4f9c301a-a30e-4373-a409-70af890d52c3 +missing + +# ╔═╡ 60845e85-fb5e-4e88-b880-c51c49166684 +md""" +Plot the body pressure of the diver over time. +""" + +# ╔═╡ 8e0fd1bd-4141-49ad-b7bc-13612378e47e +missing + +# ╔═╡ 58ba6450-56e4-48a8-8629-a84b56a53269 +md""" +Plot the rate of change in body pressure over time. +""" + +# ╔═╡ a6fc94ca-76c7-478a-82ef-256ad748dac0 +missing + +# ╔═╡ 5cf2ebb6-0a84-40aa-9593-43370e665dc1 +md""" +Calculate the maximum rate of change in the body pressure in absolute value. +""" + +# ╔═╡ 852e421f-0a7d-42aa-b8d4-d903407e170b +missing + +# ╔═╡ c10eb86b-9637-458d-aa60-40e66924fbac +md""" +!!! question + Is this maximum rate of change in body pressure for a diver that is pulled up with $F_{ext} = 180.0\;N$ a safe value? +""" + +# ╔═╡ 66178148-0e65-497d-b001-a6421faee5f1 +md""" +Answer: missing +""" + +# ╔═╡ 0f8ba58d-2632-4892-8d9c-b95a905aad6a +md""" +## Part 3: plot of maximum dpb/dt vs Fext +""" + +# ╔═╡ 942faab9-9d07-4322-ba1a-d1b614f8abd7 +md""" +Make a plot of the maximum rate of change in body pressure versus the external force in the range [177, 187] $N$ with a step size of 0.5 $N$. Use the same initial conditions and time span as in Part 2 but iteratively modify the parameter value of $F_{ext}$. Instead of `Tsit5()`, use now the `Rosenbrock32()` solver. +""" + +# ╔═╡ dd93fc6c-d0f2-431d-a0fc-1c744cf85850 +md""" +!!! hints + - Append during each iteration the maximum rate of change in body pressure to the vector `dpbdt_max_vals`. + - Define a range object for the external forces as `Fext_vals = 178:0.5:184`. + - Use the model `sys2_diver` from Part 2, but name the ODE problem as `oprob3_diver`. + - Change the parameter value of `Fext` at each iteration step. +""" + +# ╔═╡ 693c4b30-8bcc-4ee2-9557-6fcd638a7888 +# begin +# dpbdt_max_vals = []; +# Fext_vals = missing +# for Fext_val in Fext_vals +# oprob3_diver = missing +# sol3_diver = missing +# append!(dpbdt_max_vals, missing) +# end +# end + +# ╔═╡ ad818822-45c3-40eb-ba6d-f0279aa0b457 +md""" +Plot the maximum rate of change in body pressure vs the external force. +""" + +# ╔═╡ 3ed42f54-55c7-48b8-83c0-dec8fd089457 +missing + +# ╔═╡ 7a0ae4a1-85cc-4000-92ac-8cce972ac999 +md""" +!!! task + Incept graphically from the plot the value of the external force so that the rate of change in body pressure is 0.02. +""" + +# ╔═╡ 40fbc323-049f-40b9-b44d-26fce4135c19 +md""" +Response: missing +""" + +# ╔═╡ 9c611e9f-d355-48f3-b5d6-c8acd186151a +md""" +## Part 4: more accurate value of Fext +""" + +# ╔═╡ c06096c4-3f44-4ddc-9f25-f916d27e2a5b +md""" +Find a more accurate value for the external force so that the maximum rate of change in body pressure is 0.02. +""" + +# ╔═╡ 2e521e5f-ef58-4d5b-9fd6-2a87c77cb276 +md""" +!!! hints + - Use a `while` loop. + - The start value of the external force is defined as `Fext_val = 180.0`, its step size as `DFext_val = 0.05`. + - Assign within the `while` loop the value of the maximum rate of change in body pressure to `dpbdt_max_val`. + - Use the model `sys2_diver` from Part 2, but name the ODE problem as `oprob4_diver`. + - Within the `while` loop you need to put `global` before `dpbdt_max_val` and `Fext_val` because they were define outside the `while` loop. +""" + +# ╔═╡ 52d10672-20a4-4516-a632-d49e54d8fc4b +# begin +# Fext_val = missing +# DFext_val = missing +# dpbdt_max_val = 0.00 +# while missing +# oprob4_diver = missing +# sol4_diver = missing +# global dpbdt_max_val = missing +# global Fext_val += missing +# end +# end + +# ╔═╡ 54c340f7-5f7e-4bd1-bcfd-9b10c0fb47ff +md""" +Show the value of the retrieved external force. +""" + +# ╔═╡ b87163b4-9efa-46ba-9420-94979c49abc6 +missing + +# ╔═╡ 1092d818-d725-4a34-96f9-adc1f686c039 +md""" +## Part 5: above the water surface +""" + +# ╔═╡ dfecaf29-1be5-479b-bcd2-9d21643e682c +md""" +Suppose that the diver is now pulled up to a height of $5\;m$ above the water surface. The velocity of the diver should be constant and the same as when the diver hits the surface. +""" + +# ╔═╡ 93f63315-7f6d-43cb-b95d-d12ce103f9ff +md""" +### Setting up the equations +""" + +# ╔═╡ 499331aa-5062-4f9a-a4ff-9f7597be6268 +md""" +Set up the equation for the rate of change in position. Multiply the right handside of the equation with `(z<5)` to make sure that after $z$ hits $5$, $z$ remains constant and, hence, also $v$ remains $0$. +""" + +# ╔═╡ 1e34dfa5-b9ca-479c-8a86-fdd064dfe408 +eq5_position = missing + +# ╔═╡ 8947f891-b3c5-4472-b312-dba04face3d5 +md""" +Set up the equation for the rate of change in velocity. This equation only holds when $z < 0$, hence multiply it with `(z<0)`. When $0 \leq z < 5$, the velocity should remain constant, hence, $\cfrac{dv}{dt} = 0$. +""" + +# ╔═╡ 0fa60678-66c6-442a-8ab0-63c3060dbd6e +eq5_velocity = missing + +# ╔═╡ 1a9e0ba4-52b6-4dc0-844b-88949e69befc +md""" +Set up the equation for the rate of change in body pressure. The term with $\rho\,g\,z$ has to do with the hydrostatic pressure and should vanish when the diver is above the water surface. Hence, include the factor `(z<0)` to this term. +""" + +# ╔═╡ 61959657-80cd-4b73-b57b-4dc11a9fd6d2 +eq5_body_pressure = missing + +# ╔═╡ 70b8a56e-6377-44bc-b5a4-af2a006d3dd3 +md""" +Set up the equation that will keep track of the rate of change in body pressure. +""" + +# ╔═╡ 5cb47905-7379-4453-900b-92bf98a94ad1 +eq5_dpbdt = missing + +# ╔═╡ c807bf81-afc7-46b0-b073-26a27da9a55c +md""" +Bundle all equations. +""" + +# ╔═╡ 3b11cd98-0317-4a9f-87af-7195c71e4fbc +eqns5_diver = missing + +# ╔═╡ 9fa0030c-6099-4fad-9d5b-800d5e197cf5 +md""" +### Building the ODE system +""" + +# ╔═╡ 6d13fdf7-7214-483b-9de3-9fd7accab4a1 +md""" +Build the model and include the (continuous) event that when $z$ hits $5$, $v$ must become zero. Name the model `sys5_diver`. +""" + +# ╔═╡ 6a87b974-4ce6-45b0-b7e8-ce007c45659c +# @mtkbuild missing + +# ╔═╡ abad5c96-060d-4a37-8da0-6d6b97dd1926 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ 68a2f5cf-3e0b-4dbc-ad66-ab728cbe7984 +md""" +Create the ODE problem. All initial conditions are the same as before. Use a simulation time span of `500.0` seconds. Use the default parameter values. +""" + +# ╔═╡ 0a447fbc-c31d-4eac-b272-65818efb62d7 +oprob5_diver = missing + +# ╔═╡ 56ba3f8d-2c63-4b04-a389-37ff62e125d5 +md""" +Solve the ODE problem. Make a deepcopy of the ODE problem, use `Tsit5()`, `saveat=1` and `reltol=1e-9`. +""" + +# ╔═╡ 1fd0aa0c-94d0-465d-a213-f788fa9572b7 +sol5_diver = missing + +# ╔═╡ 269b83aa-e7d2-4b04-b141-40997b6ba2a9 +md""" +### Plotting results +""" + +# ╔═╡ 251ac174-9ed8-41dc-be7c-db919e29515e +md""" +Plot the position of the diver over time. Include y-labels (cf. `ylabel="..."`) and titles (cf. `title="..."`). +""" + +# ╔═╡ 893e77ec-6a36-47f2-9c84-6e723fe1a542 +missing + +# ╔═╡ 905f0f37-5443-434d-899d-a78e46bb1344 +missing + +# ╔═╡ 0f9b59cc-a8c6-4443-9080-aae4d8cd967b +missing + +# ╔═╡ ff722e23-a476-4149-832f-ca875112a302 +missing + +# ╔═╡ 2a5e302e-2c44-4fdc-bd9f-cbfef0df5763 +md""" +!!! questions + 1. Which plots do you expect to be slightly different compared to those in Part 2. + 2. Are the plots that are different according to your expectations? +""" + +# ╔═╡ c0335f76-d167-4444-9bcc-27f0dc825db2 +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ Cell order: +# ╟─de2a2b81-1896-4faa-8936-37e0a176978e +# ╠═0ee79db9-1934-4c18-b0ed-28057a852614 +# ╠═758484ec-9945-11f0-2834-e37380616e53 +# ╠═e430b466-ca2f-42f9-b9cf-f73c429506f6 +# ╠═8d55ad5c-cdfa-45dc-9abc-81cc7f1899be +# ╠═7d8cd22d-fc4e-4f4d-9640-fbe4f0fc075b +# ╟─047f4c07-527f-416d-b5c6-f9f7a26122e5 +# ╟─ece4a09b-781a-48d1-994c-856674b6ef71 +# ╟─92e3c710-9cd2-4441-a395-f4303d3c122e +# ╟─25ab9b2b-c594-4a48-ab77-910897597782 +# ╟─8bba353f-557c-4b42-a12c-666fa35fbd47 +# ╟─79bdf2b6-1b34-4543-902d-58b14adc9c92 +# ╟─28a230f6-78be-4d0c-9258-bf7b8290e33a +# ╟─bccfb4eb-5b3d-4027-b367-d6f035f5db29 +# ╟─12744c81-58da-43b4-88f3-33190a57d79c +# ╟─3c1ce356-19b0-4ac2-ad01-24be3f2baaa3 +# ╟─67b148b0-777f-4d34-b5af-c8f92e869ebe +# ╟─c58a62a3-6519-4a81-a851-04ce6ec8a9db +# ╠═7be849c3-d841-4967-936b-2e42dc332de5 +# ╟─d548f583-5f9b-4921-8e89-85995b892e51 +# ╟─66ed6b4f-2af3-4ec9-bd6f-4861129ae379 +# ╠═fb444a48-63ec-4bf4-aa55-8e8ae04640cd +# ╟─1916a1ca-565b-4692-82c2-5484a486e435 +# ╠═9a289ab4-87b5-452e-890e-2cad5c16ab4a +# ╟─3a4501ed-79dc-4b6e-ba09-df08d78f8924 +# ╠═07b8e2e3-3fad-4602-a3f0-55461ed1b5fd +# ╟─962f3a24-cecf-4c99-9850-4706d41ed70f +# ╠═3b460c68-cd31-4adf-985a-057508b40105 +# ╟─fd38ceaf-ca26-43a7-8e8b-52aa63f389a9 +# ╠═3c1b7030-9e95-421f-b818-ede261ff8ff9 +# ╟─57aa99f6-bfa0-46a5-9565-ecc4ea04dffb +# ╠═ba1b330b-d57d-4f40-841d-a05840a4c059 +# ╟─3a43debf-ed0b-4fcf-8aaf-496de76c3914 +# ╠═e59c36db-83c3-4ac8-81a1-2a5c175c75ff +# ╟─f42f55a5-4063-4f14-ae30-0d0468e32f61 +# ╠═32f4e0d7-b512-409a-9831-fb3073fe3c3d +# ╟─ef793c81-0e60-46af-b681-376265f333e8 +# ╟─4ca01607-0a0a-4ada-96de-44286565f6be +# ╠═fafc4370-569b-4e05-9683-8e54340713d2 +# ╟─e8e2b1a6-a436-45c0-a4d3-1a7baf0ec99d +# ╟─d42afda6-edd4-4fb6-93b9-8663adc0c96d +# ╠═df8fda53-7226-409f-89a4-7375a26468e6 +# ╟─84f365dd-04f8-4763-b5a3-95226eba9a98 +# ╠═700103f1-e0a7-4d09-8b1e-c152e9485c65 +# ╟─b45e8caf-e0e6-4484-b858-1129dd5b7834 +# ╟─df0f1383-425e-4f4b-8e30-54e072fe481e +# ╠═abfe5216-48b7-4c57-8c51-79c49493e419 +# ╟─53dd78a4-40d3-48e7-94fb-731a53f6eb79 +# ╠═dd525893-627f-4e40-8674-e80cb51da255 +# ╟─6f7d3199-d223-4069-a41c-aa774566d990 +# ╠═97eff5f5-5c6f-4cf6-9d3e-8ceb216eab63 +# ╟─f3d946f8-54e0-4e48-b6a9-4b5eb89ca412 +# ╠═c8481d75-945a-4d89-b3d2-f32f5e65bef2 +# ╟─087a0928-e8f9-48a4-b946-c517a404d3dc +# ╠═a3ec7991-b130-4917-b5f7-2b77a45fffb2 +# ╟─a41f2eec-5a73-4510-b189-f8d11d2aa771 +# ╟─1d07eb14-dc16-42b0-9707-2499cdcd106e +# ╟─91734c50-f848-448b-ad13-5b26a41fa0eb +# ╟─9bb2ec9d-9661-4808-85b9-2b46066a5659 +# ╟─07e2d7c1-05b6-4309-a524-782dfe6bc14c +# ╟─7f32442f-3028-457c-9013-39248fb26cb9 +# ╟─0dbe6003-b50b-41af-bee7-201bf67bf7ca +# ╟─04d9a2cb-5f73-4ac0-8f93-1733f7f71840 +# ╟─180a67b1-a03e-4dbc-9331-d82e688e49d0 +# ╠═c78d0438-ba53-4f37-ac5e-9a32172c501b +# ╟─588e1ad2-181d-4a1f-b974-a7f5f0f70c28 +# ╟─c94700b4-462e-4179-9845-1d3c52617709 +# ╠═1d3a4fba-9593-4503-8656-ffbceb8046ec +# ╟─e88c9c4e-d50b-436a-a810-76fe40ddc24d +# ╠═2d5c2f05-c902-4b6d-8b73-38d092a11fd8 +# ╟─9db90eab-683d-4c7c-a0b9-5c7904dd0aa2 +# ╠═c8b34161-ecb6-4be7-a4c4-d0381198846c +# ╟─acf469f8-9765-47e9-b4f5-55715a4075c6 +# ╠═81bba0e7-fc3a-4304-987a-099a43fb38eb +# ╟─03711ec2-bab0-4917-a40a-01ac2904612b +# ╠═5a002b19-5986-4adb-84d9-90e95c655a32 +# ╟─4b3de655-674e-4a13-83f3-f09b6443b26a +# ╠═c50c8b9c-0e1c-4f7a-800a-33220e83dfed +# ╟─b6c8b269-b215-4213-8248-4a0dee79a43a +# ╠═bbf20a87-77f2-4818-b0bb-ad67b30a5bbc +# ╟─37f10ef2-d46f-499e-bbad-f0df56b70540 +# ╠═9e29b21e-20c9-4b98-84d8-f76b42c627c8 +# ╟─e3483144-c9c5-4fdb-8053-fe0c08ae8765 +# ╟─9827f9e4-d514-454e-893f-42f9c97b5a43 +# ╠═70a6a572-c25a-40bd-824a-80b21957efbb +# ╟─fd3ae56c-34fe-4615-b73e-088e490fbb59 +# ╟─4bb1e0f4-2544-437b-8523-4e5547caee25 +# ╠═26eb5e36-4026-41b8-b2fc-4be950727e29 +# ╟─da2cd981-9b9e-42dc-b1b3-9ac09b53c5e1 +# ╠═61fdda96-1c27-42aa-b6d3-dd0b2f9dae6c +# ╟─790ce55b-fffb-49d7-8013-755cb647cf7d +# ╟─d2e5c67a-fb7a-4cf1-a849-ff11734703fe +# ╠═5e1faec8-d39b-4d41-be7e-7de077b807fe +# ╟─dd8ba91e-7e9c-4857-8682-b01dbc7a595e +# ╠═4f9c301a-a30e-4373-a409-70af890d52c3 +# ╟─60845e85-fb5e-4e88-b880-c51c49166684 +# ╠═8e0fd1bd-4141-49ad-b7bc-13612378e47e +# ╟─58ba6450-56e4-48a8-8629-a84b56a53269 +# ╠═a6fc94ca-76c7-478a-82ef-256ad748dac0 +# ╟─5cf2ebb6-0a84-40aa-9593-43370e665dc1 +# ╠═852e421f-0a7d-42aa-b8d4-d903407e170b +# ╟─c10eb86b-9637-458d-aa60-40e66924fbac +# ╠═66178148-0e65-497d-b001-a6421faee5f1 +# ╟─0f8ba58d-2632-4892-8d9c-b95a905aad6a +# ╟─942faab9-9d07-4322-ba1a-d1b614f8abd7 +# ╟─dd93fc6c-d0f2-431d-a0fc-1c744cf85850 +# ╠═693c4b30-8bcc-4ee2-9557-6fcd638a7888 +# ╟─ad818822-45c3-40eb-ba6d-f0279aa0b457 +# ╠═3ed42f54-55c7-48b8-83c0-dec8fd089457 +# ╟─7a0ae4a1-85cc-4000-92ac-8cce972ac999 +# ╠═40fbc323-049f-40b9-b44d-26fce4135c19 +# ╟─9c611e9f-d355-48f3-b5d6-c8acd186151a +# ╟─c06096c4-3f44-4ddc-9f25-f916d27e2a5b +# ╟─2e521e5f-ef58-4d5b-9fd6-2a87c77cb276 +# ╠═52d10672-20a4-4516-a632-d49e54d8fc4b +# ╟─54c340f7-5f7e-4bd1-bcfd-9b10c0fb47ff +# ╠═b87163b4-9efa-46ba-9420-94979c49abc6 +# ╟─1092d818-d725-4a34-96f9-adc1f686c039 +# ╟─dfecaf29-1be5-479b-bcd2-9d21643e682c +# ╟─93f63315-7f6d-43cb-b95d-d12ce103f9ff +# ╟─499331aa-5062-4f9a-a4ff-9f7597be6268 +# ╠═1e34dfa5-b9ca-479c-8a86-fdd064dfe408 +# ╟─8947f891-b3c5-4472-b312-dba04face3d5 +# ╠═0fa60678-66c6-442a-8ab0-63c3060dbd6e +# ╟─1a9e0ba4-52b6-4dc0-844b-88949e69befc +# ╠═61959657-80cd-4b73-b57b-4dc11a9fd6d2 +# ╟─70b8a56e-6377-44bc-b5a4-af2a006d3dd3 +# ╠═5cb47905-7379-4453-900b-92bf98a94ad1 +# ╟─c807bf81-afc7-46b0-b073-26a27da9a55c +# ╠═3b11cd98-0317-4a9f-87af-7195c71e4fbc +# ╟─9fa0030c-6099-4fad-9d5b-800d5e197cf5 +# ╟─6d13fdf7-7214-483b-9de3-9fd7accab4a1 +# ╠═6a87b974-4ce6-45b0-b7e8-ce007c45659c +# ╟─abad5c96-060d-4a37-8da0-6d6b97dd1926 +# ╟─68a2f5cf-3e0b-4dbc-ad66-ab728cbe7984 +# ╠═0a447fbc-c31d-4eac-b272-65818efb62d7 +# ╟─56ba3f8d-2c63-4b04-a389-37ff62e125d5 +# ╠═1fd0aa0c-94d0-465d-a213-f788fa9572b7 +# ╟─269b83aa-e7d2-4b04-b141-40997b6ba2a9 +# ╟─251ac174-9ed8-41dc-be7c-db919e29515e +# ╠═893e77ec-6a36-47f2-9c84-6e723fe1a542 +# ╠═905f0f37-5443-434d-899d-a78e46bb1344 +# ╠═0f9b59cc-a8c6-4443-9080-aae4d8cd967b +# ╠═ff722e23-a476-4149-832f-ca875112a302 +# ╟─2a5e302e-2c44-4fdc-bd9f-cbfef0df5763 +# ╠═c0335f76-d167-4444-9bcc-27f0dc825db2 diff --git a/src/exercises/ode_model_fermenter_firstorder.jl b/src/exercises/ode_model_fermenter_firstorder.jl deleted file mode 100644 index 28ba97d9..00000000 --- a/src/exercises/ode_model_fermenter_firstorder.jl +++ /dev/null @@ -1,206 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "6" -#> title = "EXTRA. ODE fermentor first order" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of a first order fermentor" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ e99680dc-73af-40aa-bf57-a06d3a7372be -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 2e58f4ae-f711-11ee-2598-7f3a6f2e2013 -using Markdown - -# ╔═╡ 539c1823-16d0-4355-97b6-fe9f0b106864 -using InteractiveUtils - -# ╔═╡ e66518ee-b6f6-4cca-a224-30e01cffddbe -using Catalyst - -# ╔═╡ bd648109-f042-42de-9e0e-017b502fab95 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 7856d878-8586-4cfd-9cf6-d61234450e41 -md""" -# Exercise: Fermenter - First order kinetics -""" - -# ╔═╡ 8500c35e-6bc3-4900-81bf-7705ddd61532 -md""" -In a fermenter reactor biomass $X$ grows on substrate $S$. The reactor is fed with a inlet flow rate $Q_{in}$ [$L/h$], which consist of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. Inside the reactor, biomass, with a concentration of $X$ [$g/L$], is produced through first-order kinetics (first-order in $S$): - -$$\begin{eqnarray*} -% S \xrightarrow[\quad\quad]{\beta} Y \, X -S \xrightarrow[\quad\quad]{\text{r}} Y \, X \quad\quad\quad\quad r = \beta \, S -\end{eqnarray*}$$ - -with $\beta$ [$h^{-1}$] the reaction rate constant, and $Y$ [$gX/gS$] the yield coefficient which is defined here by the amount of produced biomass by consumption of one unit of substrate. Futhermore, the reactor is drained with an outlet flow $Q$ [$L/h$], which consist of the current concentrations of substrate $S$ [$g/L$] and biomass $X$ [$g/L$] inside the reactor. The volume $V$ [$L$] of the reactor content is kept constant by setting $Q_{in} = Q$. -""" - -# ╔═╡ f1350528-07a5-4860-ad2d-627588186abc -md""" -Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of substrate $S$ and biomass $X$ with time. Name it `fermenter_firstorder`. -""" - - -# ╔═╡ 331a34f4-89d4-4193-896c-c14ab0bf04e7 -# Uncomment and complete the instruction -# fermenter_firstorder = @reaction_network begin -# missing # Y*X is created from one S at a rate β -# missing # S is created at a rate Q/V*Sin -# missing # S and X are degraded at a rate Q/V*S -# end - -# ╔═╡ 55746566-2d46-4475-851a-02b7fad87a1a -md""" -Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. -""" - -# ╔═╡ ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 67117a27-dcea-4b43-b962-9ad9fd07f4f4 -md""" -The parameter values are $\beta = 0.98$, $Y = 0.80$, $Q = 2.0$, $V = 40.0$ and $S_{in} = 2.2\;g/L$. With the latter values, the fermenter reactor is in steady state operation with concentrations for substrate $S = 0.1068\;g/L$ and biomass $X = 1.6746\;g/L$. Suppose that at timepoint $t = 20\;h$, the concentration of substrate in the inlet flow (cf. $S_{in}$) is suddently increased to $3.4\;g/L$. Simulate the evolution of $S$ and $X$ during 120 hours. -""" - - -# ╔═╡ d13e6e38-037e-4812-85e9-2c18bed360f6 -md""" -Initialize a vector `u₀` with the initial conditions: -""" - -# ╔═╡ 4b556cf0-8fad-434d-be56-dc1848d898ae -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ ea55d648-7575-43c3-a385-5f4979996ef2 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 1365c12e-e662-4858-983b-02ba94cd9f0d -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 3941bd60-a83c-4f72-84b3-28e28cb845d0 -md""" -Initialize a vector `params` with the parameter values: -""" - -# ╔═╡ d6c1316a-cf96-43d1-854a-f25925cf4a55 -# params = missing # Uncomment and complete the instruction - -# ╔═╡ eeb8ec6e-154e-4fe9-8b5b-edbe71914985 -md""" -Create the *condition* that contains the timepoint for the sudden change in $S_{in}$. Store it in `condition`: -""" - -# ╔═╡ 7ca8efaa-97b6-46f2-b4d3-6ca8aa97dda7 -# condition = missing - -# ╔═╡ d864bfc3-05b2-483b-9a55-da026931703f -md""" -Make a new *reaction system* where the discrete event is included. Name it `fermenter_firstorder2`. -""" - -# ╔═╡ d758b918-d184-473f-9b9e-ed6da7b0f088 -# @named fermenter_firstorder_c = missing - -# ╔═╡ ef596d8c-efdc-4b5c-9584-335d799acfe8 -md""" -Complete the new *reaction system*. Name it `fermenter_firstorder_c_com`. -""" - -# ╔═╡ c07b3121-4c27-454c-b9e4-7dfa27371ebd -# fermenter_firstorder_c_com = missing - -# ╔═╡ c6e81f41-a244-48c9-9d18-b3b9e0984fbb -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ ed56f8d6-2260-4829-9190-69b60b7d7599 -# oprob = missing - -# ╔═╡ 8b73b16b-7f7d-4d2e-a1c2-7e1adf2336e9 -md""" -Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol`: -""" - -# ╔═╡ 433db8d1-f038-4e7b-9133-90bfeccabd07 -# osol = missing - -# ╔═╡ fc824241-f718-46a8-b50b-a680470c062b -md""" -Plot the results: -""" - -# ╔═╡ e2609226-5f2f-4802-89e3-0efeac740081 -# missing - -# ╔═╡ 5fa47281-4c1f-4b8e-ab99-c92f4dc7ec65 -md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of the increase in $S_{in}$? -""" - -# ╔═╡ ae8e5a4a-59d8-4746-accb-c9b09099bc2b -md"- Answer: missing" - -# ╔═╡ 822ac3d0-9430-4763-9e94-d77b9e77c79c -md""" -2. Can you argue, by means of reasoning or by determining and analyzing the operating point, why the increase of $X$ is larger than the increase of $S$? -""" - -# ╔═╡ a5d1f9e9-cd84-4221-9295-5c739cb289b2 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═2e58f4ae-f711-11ee-2598-7f3a6f2e2013 -# ╠═539c1823-16d0-4355-97b6-fe9f0b106864 -# ╠═e99680dc-73af-40aa-bf57-a06d3a7372be -# ╟─7856d878-8586-4cfd-9cf6-d61234450e41 -# ╟─8500c35e-6bc3-4900-81bf-7705ddd61532 -# ╠═e66518ee-b6f6-4cca-a224-30e01cffddbe -# ╟─f1350528-07a5-4860-ad2d-627588186abc -# ╠═331a34f4-89d4-4193-896c-c14ab0bf04e7 -# ╟─55746566-2d46-4475-851a-02b7fad87a1a -# ╠═ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac -# ╠═bd648109-f042-42de-9e0e-017b502fab95 -# ╟─67117a27-dcea-4b43-b962-9ad9fd07f4f4 -# ╟─d13e6e38-037e-4812-85e9-2c18bed360f6 -# ╠═4b556cf0-8fad-434d-be56-dc1848d898ae -# ╟─ea55d648-7575-43c3-a385-5f4979996ef2 -# ╠═1365c12e-e662-4858-983b-02ba94cd9f0d -# ╟─3941bd60-a83c-4f72-84b3-28e28cb845d0 -# ╠═d6c1316a-cf96-43d1-854a-f25925cf4a55 -# ╟─eeb8ec6e-154e-4fe9-8b5b-edbe71914985 -# ╠═7ca8efaa-97b6-46f2-b4d3-6ca8aa97dda7 -# ╟─d864bfc3-05b2-483b-9a55-da026931703f -# ╠═d758b918-d184-473f-9b9e-ed6da7b0f088 -# ╟─ef596d8c-efdc-4b5c-9584-335d799acfe8 -# ╠═c07b3121-4c27-454c-b9e4-7dfa27371ebd -# ╟─c6e81f41-a244-48c9-9d18-b3b9e0984fbb -# ╠═ed56f8d6-2260-4829-9190-69b60b7d7599 -# ╟─8b73b16b-7f7d-4d2e-a1c2-7e1adf2336e9 -# ╠═433db8d1-f038-4e7b-9133-90bfeccabd07 -# ╟─fc824241-f718-46a8-b50b-a680470c062b -# ╠═e2609226-5f2f-4802-89e3-0efeac740081 -# ╟─5fa47281-4c1f-4b8e-ab99-c92f4dc7ec65 -# ╟─ae8e5a4a-59d8-4746-accb-c9b09099bc2b -# ╟─822ac3d0-9430-4763-9e94-d77b9e77c79c -# ╟─a5d1f9e9-cd84-4221-9295-5c739cb289b2 diff --git a/src/exercises/ode_model_fermenter_monod.jl b/src/exercises/ode_model_fermenter_monod.jl deleted file mode 100644 index 9eb647a3..00000000 --- a/src/exercises/ode_model_fermenter_monod.jl +++ /dev/null @@ -1,477 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "7" -#> title = "EXTRA. ODE fermentor monod" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of a fermentor monod" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ e99680dc-73af-40aa-bf57-a06d3a7372be -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 2e58f4ae-f711-11ee-2598-7f3a6f2e2013 -using Markdown - -# ╔═╡ 539c1823-16d0-4355-97b6-fe9f0b106864 -using InteractiveUtils - -# ╔═╡ e66518ee-b6f6-4cca-a224-30e01cffddbe -using Catalyst - -# ╔═╡ bd648109-f042-42de-9e0e-017b502fab95 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 7856d878-8586-4cfd-9cf6-d61234450e41 -md" -### Exercise: Fermenter - Monod kinetics -" - -# ╔═╡ 8500c35e-6bc3-4900-81bf-7705ddd61532 -md""" -In a fermenter reactor biomass grows on substrate. The reactor is fed with a inlet flow rate $Q_{in}$ [$L/h$], which consist of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. Inside the reactor, biomass, with a concentration of $X$ [$g/L$], is produced through **Monod** kinetics: - -$$\begin{eqnarray*} -%S \xrightarrow[\quad\quad]{\beta} Y \, X -S \xrightarrow[\quad\quad]{r} Y \, X \quad\quad\quad\quad r = \mu \, X -\end{eqnarray*}$$ - -where - -$$\mu = \mu_{max} \, \cfrac{S}{S + K_s}$$ - -is called the specific growth rate [$h^{-1}$]. Therein, $\mu_{max}$ is the maximum speficic growth rate, and $K_s$ [$g/L$] is the so-called *half-velocity constant* (i.e. the value of $S$ when $\mu/\mu_{max} = 0.5$). Futhermore, $Y$ [$gX/gS$] is the yield coefficient which is defined here by the amount of produced biomass by consumption of one unit of substrate. The reactor is drained with an outlet flow $Q$ [$L/h$], which consist of the current concentrations of substrate $S$ [$g/L$] and biomass $X$ [$g/L$] inside the reactor. The volume $V$ [$L$] of the reactor content is kept constant by setting $Q_{in} = Q$. -""" - -# ╔═╡ f1350528-07a5-4860-ad2d-627588186abc -md""" -Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of substrate $S$ and biomass $X$ with time. Name it `fermenter_monod`. - -Tip: The specific growth rate $\mu = \mu_{max} \, \cfrac{S}{S + K_s}$ can be implemented with `mm(S, μmax, Ks)`. The function `mm` stands for the Michaelis-Menten kinetics, whcih is equivalent to Monod kinetics. -""" - -# ╔═╡ 331a34f4-89d4-4193-896c-c14ab0bf04e7 -# fermenter_monod = @reaction_network begin -# ... # Y*X is created from one S at a rate mm(S, μmax, Ks)*X -# ... # S is created at a rate Q/V*Sin -# ... # S and X are degraded at a rate Q/V*S -# end - -# ╔═╡ 55746566-2d46-4475-851a-02b7fad87a1a -md""" -Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. - -Keep in mind that `mm(S, μmax, Ks)` stands for $\mu_{max} \, \cfrac{S}{S + K_s}$. -""" - -# ╔═╡ ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac -# osys = missing - -# ╔═╡ 67117a27-dcea-4b43-b962-9ad9fd07f4f4 -md""" -The parameter values are $\mu_{max} = 0.40$, $K_s = 0.015$, $Y = 0.67$, $Q = 2.0$, $V = 40.0$ and $S_{in} = 0.02\;g/L$. Suppose that at $t=0\;h$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concetration of $0.0005\;g/L$. Simulate the evolution of $S$ and $X$ during $200$ hours. -""" - -# ╔═╡ d13e6e38-037e-4812-85e9-2c18bed360f6 -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ 4b556cf0-8fad-434d-be56-dc1848d898ae -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ ea55d648-7575-43c3-a385-5f4979996ef2 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 1365c12e-e662-4858-983b-02ba94cd9f0d -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 3941bd60-a83c-4f72-84b3-28e28cb845d0 -md""" -Initialize a vector `param` with the parameter values: -""" - -# ╔═╡ d6c1316a-cf96-43d1-854a-f25925cf4a55 -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 4926b941-c3b6-4804-b4a4-11e13e5186f2 -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ ab2a9842-6a9c-46bd-812b-db01629d6a1c -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ b6a526bd-6ee5-442b-9fb8-3fbe1e280dd4 -md""" -#### Part 1 - -Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol1`: -""" - -# ╔═╡ 1f62e66d-571f-41ca-9f02-f36a8ca10ab9 -# osol1 = missing # Uncomment and complete the instruction - -# ╔═╡ 37ced6e3-b435-4546-a720-a1ec1af23a65 -md""" -Plot the results: -""" - -# ╔═╡ d609bed4-94cf-4167-80fe-924501a5835c -# missing # Uncomment and complete the instruction - -# ╔═╡ 3c638fc4-8ebc-4e26-984e-b4513035287e -md""" -Inspect the final values in both the $S$ and $X$ vector.\ -Tip: use something like: `(osol1[...][...], osol1[...][...])` -""" - -# ╔═╡ 25ef069e-5a08-4e85-acea-a5b64e0890f6 -# (osol1[...][...], osol1[...][...]) # Uncomment and complete the instruction - -# ╔═╡ d8234337-7516-4817-8ce5-194af694e3e3 -md""" -We will now show you how to determine the steady state values for $S$ and $X$ under the current conditions (cf. current initial values and current parameter values). -""" - -# ╔═╡ b63fa707-6023-44fc-aac9-25c76e075f90 -md""" -First, we initialize a vector `u_guess1` with the final values for $S$ and $X$: -""" - -# ╔═╡ 8d96d79f-6ddb-4bba-a6ea-821588e13107 -# u_guess1 = missing - -# ╔═╡ e8969045-27ac-460e-86a2-7494903534e8 -md""" -Then we make a so-called SteadyStateProblem based on the ODEProblem but now with `u_guess1` as initial conditions! Finally we use `solve` to solve the steady state problem. The outputs are the steady state values for $S$ and $X$ which we have denoted as `Seq1` and `Xeq1`. -""" - -# ╔═╡ 1777503e-b793-4be2-b80b-b4edcd7041b5 -# Seq1, Xeq1 = missing - -# ╔═╡ 1d5a2118-96e9-49b2-9931-5d4b201cb8f5 -md""" -Next, we can just inspect these values: -""" - -# ╔═╡ cb57997e-c3ec-47e0-b9a0-b10aa9f5608d -# missing - -# ╔═╡ bb2d06f8-1940-4585-961a-54068da50e91 -md""" -Interpret the results. Ask yourself the following questions: - -1. Explain why $S$ first increases and then decreases while $X$ only increases during the first 50 hours. -""" - -# ╔═╡ 7c29c97d-dea5-4ea3-b5aa-bdccfe93939c -md"- Answer: missing" - -# ╔═╡ 34018fc4-af3e-4f5a-9f24-a73293af2e85 -md""" -2. What are the steady state values of $S$ and $X$. -""" - -# ╔═╡ eebef095-ead8-4193-889b-53cbcab84514 -md"- Answer: missing" - -# ╔═╡ 9bb450c6-5499-42f6-8356-bdc4985b74e7 -md""" -#### Part 2 - -Suppose that the substrate inlet concentration $S_{in}$ suddenly increases to $0.022\;g/L$ at $t = 100\;h$. Simulate the evolution of $S$ and $X$. -""" - -# ╔═╡ 0298953a-b90c-41cd-8613-cb47ce752e43 -md""" -Create the *condition* that contains the timepoint for the sudden change in $S_{in}$. Store it in `condition2`: -""" - -# ╔═╡ c85e505d-99c6-4616-b7c1-42c05b4894fc -# condition2 = missing # Uncomment and complete the instruction - -# ╔═╡ 7c96bead-9f7b-4e84-abb9-9b6651208667 -md""" -Make a new *reaction system* where the discrete event is included. Name it `fermenter_monod2`. -""" - -# ╔═╡ 439dbeef-55b9-4fa4-aef5-fa0bb5a2ccf1 -# @named fermenter_monod2 = missing # Uncomment and complete the instruction - -# ╔═╡ 43614c69-3fb5-4bef-b2a2-9805a5545fb8 -md""" -Complete the new *reaction system*. Name it `fermenter_monod2_com`. -""" - -# ╔═╡ 532306b5-2a71-4fcf-93ac-9dc52457a3f9 -# fermenter_monod2_com = missing # Uncomment and complete the instruction - -# ╔═╡ 9e8f4500-0b6a-47f0-a1f5-a74daea9d117 -md""" -Create the ODE problem and store it in `oprob2`: -""" - -# ╔═╡ 7af72709-2f82-4971-8342-f02943f947c8 -# oprob2 = missing # Uncomment and complete the instruction - -# ╔═╡ e019f797-a6ad-4f8f-8f9e-69db00ed3c39 -md""" -Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol2`: -""" - -# ╔═╡ 5f77450b-aa96-41b0-8017-a3d29fd7023a -# osol2 = missing # Uncomment and complete the instruction - -# ╔═╡ 310a78a5-94ce-4a29-b7a1-37831ce5c64e -md""" -Plot the results: -""" - -# ╔═╡ 85742dc1-24cb-42d1-a70b-70f1be6b6c1e -# missing - -# ╔═╡ 7f358845-c9fc-4e77-9882-94506f9338d6 -md""" -Calculate the state state values for $S$ and $X$. -""" - -# ╔═╡ 1102146d-abbe-43ce-9602-c863d1a91071 -md""" -Inspect the final values in both the $S$ and $X$ vector.\ -Tip: use something like: `(osol2[...][...], osol2[...][...])` -""" - -# ╔═╡ 196edf3a-b220-4a54-8137-b136b509617e -# (osol2[...][...], osol2[...][...]) # Uncomment and complete the instruction - -# ╔═╡ 3c7bf6f2-4ffd-4678-a930-94cf1322ba9f -md""" -Initialize a vector `u_guess2` with the final values for $S$ and $X$: -""" - -# ╔═╡ 56eaa343-03b6-4cae-868b-e5c36ac66546 -# u_guess2 = missing # Uncomment and complete the instruction - -# ╔═╡ 4d8c05d8-ee29-4cb8-84cd-4342cb1db289 -md""" -Initialize a vector `param_mod` with the parameter values. Notice that all parameter values will be the same, **except** the one of $S_{in}$. -""" - -# ╔═╡ f121efc5-4e64-4e82-8672-2765ad85443e -# params_mod = missing # Uncomment and complete the instruction - -# ╔═╡ 9fe054e0-cf21-49ba-a777-a8200b34b7dd -md""" -Make and solve the steady state problem. Call the output values `Seq2` and `Xeq2`. -""" - -# ╔═╡ 0b992750-a446-447b-b2a1-26658c11c0bf -# Seq2, Xeq2 = missing # Uncomment and complete the instruction - -# ╔═╡ 4ed59602-ad9d-4aae-8a21-83dabbfd3846 -md""" -Inspect those values. -""" - -# ╔═╡ 47a63fd8-f805-4c7d-8695-c9ee6550f24f -# missing # Uncomment and complete the instruction - -# ╔═╡ 4d962d0f-da41-405e-9438-733d5668cde3 -md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of the increase in $S_{in}$? -""" - -# ╔═╡ 0bdf383c-e742-46ef-baba-11059e64f9c9 -md"- Answer: missing" - -# ╔═╡ d5e8e268-2dc1-4979-a800-9b733a7f7818 -md""" -2. Find the steady state values of $S$ and $X$. Is the steady state value of $S$ influenced by the increase of $S_{in}$? Show how you can deduce that from the differential equations. -""" - -# ╔═╡ 4c141769-65af-4821-a8d3-0e263eccaf8f -md"- Answer: missing" - -# ╔═╡ 716362c9-54c4-49ef-bccc-d69425792c63 -md""" -3. Can you explain why $X$ increased permanently? -""" - -# ╔═╡ 8a0e6b54-bf50-4fc8-a7b9-fbfd2deb8d06 -md"- Answer: missing" - -# ╔═╡ 53980767-a84f-44f2-a878-2a7d57e0e2ae -md""" -#### Part 3 - -Suppose that the inlet/outlet flow $Q$ is suddenly doubled at $t = 100\;h$. Simulate the evolution of $S$ and $X$. -""" - -# ╔═╡ 31b64d91-f8ff-413a-9c0c-402fe2215a81 -md""" -Create the *condition* that contains the timepoint for the sudden change in $Q$. Store it in `condition3`: -""" - -# ╔═╡ 6e771231-abf1-41c0-9aa8-ac7220f2a9cd -# condition3 = missing # Uncomment and complete the instruction - -# ╔═╡ 97b0de4f-c300-44f1-97fc-804d3263d8b5 -md""" -Make a new *reaction system* where the discrete event is included. Name it `fermenter_monod3`. -""" - -# ╔═╡ 837e27f1-a2d8-4d2c-aaa5-e82b6761e4fd -# @named fermenter_monod3 = missing # Uncomment and complete the instruction - -# ╔═╡ d8002843-03c6-4fa4-b8e5-b42eac27588c -md""" -Complete the new *reaction system*. Name it `fermenter_monod3_com`. -""" - -# ╔═╡ 508f1dfe-3a92-4d80-b48d-e84a8738f97f -# fermenter_monod3_com =missing # Uncomment and complete the instruction - -# ╔═╡ 5d4c2573-4e57-455b-bdcc-1cee79b08ce2 -md""" -Create the ODE problem and store it in `oprob3`: -""" - -# ╔═╡ dd388e88-53af-48d3-800e-09b5c182a83b -# oprob3 = missing # Uncomment and complete the instruction - -# ╔═╡ 5133d846-e6a6-4b50-9ce1-cb91cf04cbd1 -md""" -Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol3`: -""" - -# ╔═╡ b3bc3348-a524-41e6-9fdb-865055246cd9 -# osol3 = missing # Uncomment and complete the instruction - -# ╔═╡ 39ef225d-3222-4998-bfd4-5ff88f74a0f9 -md""" -Plot the results: -""" - -# ╔═╡ 76849cf5-170b-452b-acdd-c4017feaad18 -# missing # Uncomment and complete the instruction - -# ╔═╡ 040f040f-aa28-442e-9f44-2a897e22ed4f -md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of doubling of $Q$? -""" - -# ╔═╡ 1b792c99-0165-49a3-8466-91082aa514bc -md"- Answer: missing" - -# ╔═╡ f9e232c0-a74b-48b9-854d-f507717f8cdd -md""" -2. Can you argue, by means of reasoning, why $S$ increases and $X$ decreases? -""" - -# ╔═╡ 7f773577-99f7-4aee-b53c-08cd9a25a236 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═2e58f4ae-f711-11ee-2598-7f3a6f2e2013 -# ╠═539c1823-16d0-4355-97b6-fe9f0b106864 -# ╠═e99680dc-73af-40aa-bf57-a06d3a7372be -# ╟─7856d878-8586-4cfd-9cf6-d61234450e41 -# ╟─8500c35e-6bc3-4900-81bf-7705ddd61532 -# ╠═e66518ee-b6f6-4cca-a224-30e01cffddbe -# ╟─f1350528-07a5-4860-ad2d-627588186abc -# ╠═331a34f4-89d4-4193-896c-c14ab0bf04e7 -# ╟─55746566-2d46-4475-851a-02b7fad87a1a -# ╠═ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac -# ╠═bd648109-f042-42de-9e0e-017b502fab95 -# ╟─67117a27-dcea-4b43-b962-9ad9fd07f4f4 -# ╟─d13e6e38-037e-4812-85e9-2c18bed360f6 -# ╠═4b556cf0-8fad-434d-be56-dc1848d898ae -# ╟─ea55d648-7575-43c3-a385-5f4979996ef2 -# ╠═1365c12e-e662-4858-983b-02ba94cd9f0d -# ╟─3941bd60-a83c-4f72-84b3-28e28cb845d0 -# ╠═d6c1316a-cf96-43d1-854a-f25925cf4a55 -# ╟─4926b941-c3b6-4804-b4a4-11e13e5186f2 -# ╠═ab2a9842-6a9c-46bd-812b-db01629d6a1c -# ╟─b6a526bd-6ee5-442b-9fb8-3fbe1e280dd4 -# ╠═1f62e66d-571f-41ca-9f02-f36a8ca10ab9 -# ╟─37ced6e3-b435-4546-a720-a1ec1af23a65 -# ╠═d609bed4-94cf-4167-80fe-924501a5835c -# ╟─3c638fc4-8ebc-4e26-984e-b4513035287e -# ╠═25ef069e-5a08-4e85-acea-a5b64e0890f6 -# ╟─d8234337-7516-4817-8ce5-194af694e3e3 -# ╟─b63fa707-6023-44fc-aac9-25c76e075f90 -# ╠═8d96d79f-6ddb-4bba-a6ea-821588e13107 -# ╟─e8969045-27ac-460e-86a2-7494903534e8 -# ╠═1777503e-b793-4be2-b80b-b4edcd7041b5 -# ╟─1d5a2118-96e9-49b2-9931-5d4b201cb8f5 -# ╠═cb57997e-c3ec-47e0-b9a0-b10aa9f5608d -# ╟─bb2d06f8-1940-4585-961a-54068da50e91 -# ╟─7c29c97d-dea5-4ea3-b5aa-bdccfe93939c -# ╟─34018fc4-af3e-4f5a-9f24-a73293af2e85 -# ╟─eebef095-ead8-4193-889b-53cbcab84514 -# ╟─9bb450c6-5499-42f6-8356-bdc4985b74e7 -# ╟─0298953a-b90c-41cd-8613-cb47ce752e43 -# ╠═c85e505d-99c6-4616-b7c1-42c05b4894fc -# ╟─7c96bead-9f7b-4e84-abb9-9b6651208667 -# ╠═439dbeef-55b9-4fa4-aef5-fa0bb5a2ccf1 -# ╟─43614c69-3fb5-4bef-b2a2-9805a5545fb8 -# ╠═532306b5-2a71-4fcf-93ac-9dc52457a3f9 -# ╟─9e8f4500-0b6a-47f0-a1f5-a74daea9d117 -# ╠═7af72709-2f82-4971-8342-f02943f947c8 -# ╟─e019f797-a6ad-4f8f-8f9e-69db00ed3c39 -# ╠═5f77450b-aa96-41b0-8017-a3d29fd7023a -# ╟─310a78a5-94ce-4a29-b7a1-37831ce5c64e -# ╠═85742dc1-24cb-42d1-a70b-70f1be6b6c1e -# ╟─7f358845-c9fc-4e77-9882-94506f9338d6 -# ╟─1102146d-abbe-43ce-9602-c863d1a91071 -# ╠═196edf3a-b220-4a54-8137-b136b509617e -# ╟─3c7bf6f2-4ffd-4678-a930-94cf1322ba9f -# ╠═56eaa343-03b6-4cae-868b-e5c36ac66546 -# ╟─4d8c05d8-ee29-4cb8-84cd-4342cb1db289 -# ╠═f121efc5-4e64-4e82-8672-2765ad85443e -# ╟─9fe054e0-cf21-49ba-a777-a8200b34b7dd -# ╠═0b992750-a446-447b-b2a1-26658c11c0bf -# ╟─4ed59602-ad9d-4aae-8a21-83dabbfd3846 -# ╠═47a63fd8-f805-4c7d-8695-c9ee6550f24f -# ╟─4d962d0f-da41-405e-9438-733d5668cde3 -# ╟─0bdf383c-e742-46ef-baba-11059e64f9c9 -# ╟─d5e8e268-2dc1-4979-a800-9b733a7f7818 -# ╟─4c141769-65af-4821-a8d3-0e263eccaf8f -# ╟─716362c9-54c4-49ef-bccc-d69425792c63 -# ╟─8a0e6b54-bf50-4fc8-a7b9-fbfd2deb8d06 -# ╟─53980767-a84f-44f2-a878-2a7d57e0e2ae -# ╟─31b64d91-f8ff-413a-9c0c-402fe2215a81 -# ╠═6e771231-abf1-41c0-9aa8-ac7220f2a9cd -# ╟─97b0de4f-c300-44f1-97fc-804d3263d8b5 -# ╠═837e27f1-a2d8-4d2c-aaa5-e82b6761e4fd -# ╟─d8002843-03c6-4fa4-b8e5-b42eac27588c -# ╠═508f1dfe-3a92-4d80-b48d-e84a8738f97f -# ╟─5d4c2573-4e57-455b-bdcc-1cee79b08ce2 -# ╠═dd388e88-53af-48d3-800e-09b5c182a83b -# ╟─5133d846-e6a6-4b50-9ce1-cb91cf04cbd1 -# ╠═b3bc3348-a524-41e6-9fdb-865055246cd9 -# ╟─39ef225d-3222-4998-bfd4-5ff88f74a0f9 -# ╠═76849cf5-170b-452b-acdd-c4017feaad18 -# ╟─040f040f-aa28-442e-9f44-2a897e22ed4f -# ╟─1b792c99-0165-49a3-8466-91082aa514bc -# ╟─f9e232c0-a74b-48b9-854d-f507717f8cdd -# ╟─7f773577-99f7-4aee-b53c-08cd9a25a236 diff --git a/src/exercises/ode_model_infection.jl b/src/exercises/ode_model_infection.jl deleted file mode 100644 index 80686b6a..00000000 --- a/src/exercises/ode_model_infection.jl +++ /dev/null @@ -1,751 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "3" -#> title = "1. ODE infection" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of infection" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). -macro bind(def, element) - #! format: off - quote - local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end - local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) - el - end - #! format: on -end - -# ╔═╡ 989fd8c8-25d9-47b9-ade6-6c7f21a7dceb -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 18a4df05-0349-400d-a29e-b3fa71aa4d88 -using Markdown - -# ╔═╡ 65b3536b-c9b3-485e-87e1-4f6657381503 -using InteractiveUtils - -# ╔═╡ e968f6d5-962c-4de8-a9a3-ec116805d5e1 -using PlutoUI; TableOfContents() - -# ╔═╡ e97637b0-446c-44ac-bd08-c56632f9b57f -using Catalyst - -# ╔═╡ c992a16d-80d3-48d6-bf43-2dbfa8e2f081 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ c54c7e3c-f3d1-40a5-88f7-bedcab3269ba -md" -# Exercises - infection model -" - -# ╔═╡ fab49cb7-c41e-498a-98f6-33b4821ceb90 -md""" -We will work here with the same infection model as in the **Introdution to Catalyst** (revisit the concerned notebook if necessay). We shortly summarize some important aspects of the model and give a condensed version of the solution method and the examples. -""" - -# ╔═╡ 8ff0b57e-acc1-4ebd-a562-a82c9efa7ffc -md""" -The **state variables**: - -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``S`` | *persons* | number of susceptible persons | -| ``I`` | *persons* | number of infected persons | -| ``D`` | *persons* | number of deceased persons | -| ``R`` | *persons* | number of recovered persons | -""" - -# ╔═╡ e36b150a-b4fc-476f-bab7-e1162a4b263d -md""" -The **parameters**: - -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``\alpha`` | ``\frac{persons}{contact}`` | chances of getting infected after contact | -| ``\beta`` | ``\frac{contact}{persons^2\,day}`` | contact rate | -| ``r`` | ``\frac{1}{day}`` | rate of leaving infection period | -| ``m`` | ``\frac{person}{person}`` | fraction of persons deceasing | -| ``1-m`` | ``\frac{person}{person}`` | fraction of persons becoming resistant | -""" - -# ╔═╡ bdefa657-88bd-43d6-8e54-cc6321a9e720 -md""" -The infection model has three reaction events: - -- Infection, where a susceptible persons meets an infected persons and also becomes infected. The infection rate is $\alpha \beta$. -- Deceasing, where an infected person die. The death rate is $m r$. -- Recovery, where an infected person recovers. The recovery rate: $(1-m) r$. -""" - -# ╔═╡ afb751b9-39b8-4430-af4b-02f010667518 -md""" -The *infection reactions* are: - -$$S + I \xrightarrow[]{\alpha \beta} 2I$$ -$$I \xrightarrow[]{mr} D$$ -$$I \xrightarrow[]{(1-m)r} R$$ -""" - -# ╔═╡ aab497eb-c0bd-49e3-a0c1-71e13f5b0ad5 -md""" -Load the Catalyst package: -""" - -# ╔═╡ a91f1024-4800-4ffe-8bb2-5f6cf3c2bde1 -md""" -## Examples -""" - -# ╔═╡ 47f4a7cc-1a74-4ff0-ae21-2a5e4c495935 -md" -### Implementation of the system -" - -# ╔═╡ 3f6080e1-ab43-47f7-a82b-95956bf4cafd -infection_model = @reaction_network begin - α * β, S + I --> 2I - r * m, I --> D - r * (1 - m), I --> R -end - -# ╔═╡ 5d5fae40-a4ed-4908-ba65-f3a16c38ab4f -md""" -The species: -""" - -# ╔═╡ fd347a58-5cad-4073-aadb-176fa54dcdfa -species(infection_model) - -# ╔═╡ bd5bd448-8741-4528-989f-9754b0e55b9b -md""" -Alternatively: -""" - -# ╔═╡ a78b178e-6952-4057-bd8c-6d5b0c5e0518 -@unpack S, I, D, R = infection_model; - -# ╔═╡ cbfe8833-786c-48f7-b849-705006d4c41d -md""" -The parameters: -""" - -# ╔═╡ 96f3dd68-da80-4f07-af2b-e3b9ec05cc0c -parameters(infection_model) - -# ╔═╡ cc4d514a-dfed-4424-9982-8315954b38ff -md" -Convert the *reaction model* if you want to see the symbolic differential equation model: -" - -# ╔═╡ 3aad1f32-7daf-49a5-9dd3-9ddbba1a50de -osys = convert(ODESystem, infection_model) - -# ╔═╡ a432a88a-8db3-4729-b9a3-8ca9322fc81e -md""" -Getting a list of the differential equations, the state variables and the parameters: -""" - -# ╔═╡ 7e268605-8314-4e7b-8c38-a65212e148a9 -equations(osys) - -# ╔═╡ 0707b599-c1ca-459b-b87b-956f8f49564b -unknowns(osys) - -# ╔═╡ 113ff67e-6dc8-45cb-9617-7768e132f6e9 -parameters(osys) - -# ╔═╡ 39e396e1-20fa-4e78-b0ab-99774ce55f0f -md""" -### Simulating the system as an ODE-problem - -Load the packages Differential and Plot: -""" - -# ╔═╡ 4a581cce-9a1b-4516-9cf1-6a41ea6566e7 -md""" -### Setting initial conditions, timespan and parameter values -""" - -# ╔═╡ c5ee2780-c652-4c9b-981b-53e5f91e1766 -u0 = [:S => 9_999_000.0, :I => 1_000.0, :D => 0.0, :R => 0.0] - -# ╔═╡ fc380000-33a0-419d-9226-7a0c136de8c6 -tspan = (0.0, 90.0) - -# ╔═╡ a8756838-93d7-4909-84c2-6d89be4fa711 -params = [:α => 0.08, :β => 1.0e-6, :r => 0.2, :m => 0.4] - -# ╔═╡ 6d2580f7-f882-449d-a9b8-629ab41e2671 -md""" -### Creating and solving the ODEProblem and plotting results -""" - -# ╔═╡ d3f39b29-71f5-4674-a52f-3ba63279fca7 -oprob = ODEProblem(infection_model, u0, tspan, params) - -# ╔═╡ 6173506b-8f56-4100-a0a0-117c1ee8aa0f -osol = solve(oprob, Tsit5(), saveat=0.5) - -# ╔═╡ e6b17756-a7e9-4f50-98dc-b69f9a54903f -plot(osol) - -# ╔═╡ 006bfc98-3c95-4763-b0ed-f4714ad5bd02 -plot(osol, idxs=[:S, :I]) # only S and I - -# ╔═╡ 4c5d2e5d-3237-4886-b852-3142fb996dd6 -plot(osol, idxs=(:S, :I), xlab="S", ylab="I") # fase plot I vs S - -# ╔═╡ f9bd32f5-c473-44d4-af9d-4d99032a2d21 -osol.u[end] - -# ╔═╡ 000495a6-e91d-438e-ab6d-7a5e95a99f27 -md""" -### Example 1 - Influence of $r$ - -Influence of the duration of infection $1/r$ for average infection periods of between $10$, days and $1$ day contagious ($r$ between $0.1$ and $1.0$, step $0.1$, default value $0.1$). -""" - -# ╔═╡ d73f8ed8-ac25-4c1c-b834-989a2a929df3 -@bind r Slider(0.1:0.1:1, default=0.1, show_value=true) - -# ╔═╡ 4ca0e1df-7765-4f36-a442-2749ea493426 -params1 = [:α => 0.08, :β => 1.0e-6, :r => r, :m => 0.4] - -# ╔═╡ d56b486e-44e5-4db7-9c4e-00db5c1b733f -oprob1 = ODEProblem(infection_model, u0, tspan, params1); - -# ╔═╡ 298ada89-4581-41c5-a89f-f01ae431c5ec -osol1 = solve(oprob1, Tsit5(), saveat=0.5); - -# ╔═╡ 5b4ca642-0f89-40ab-9a2f-c35fc603a91d -plot(osol1, ylim=(0, 1e7)) - -# ╔═╡ 171208da-69a3-4737-85e0-fae0805bbaa8 -md""" -Change the value of $r$ in the `params1` vector to visualize the effect in the plot. -""" - -# ╔═╡ 98aedcf5-e0de-442f-b19b-c5b3d9e3d475 -md""" -### Example 2 - Discrete event - -Suppose that regulations are such that on day 14, people need to reduce their contacts by 50%. -""" - -# ╔═╡ 462b2786-98d6-40cb-887d-aff6b63881e8 -condition2 = [14.0] => [infection_model.β ~ infection_model.β/2] - -# ╔═╡ efd74885-c25c-42e5-bef5-c9654329c220 -@named infection_model2 = ReactionSystem(equations(infection_model), discrete_events=condition2) - -# ╔═╡ ecf20050-12d3-48e7-84f1-6919a9aab865 -infection_model2_com = complete(infection_model2) - -# ╔═╡ fdea5a92-e115-4dbe-a885-95772857c686 -oprob2 = ODEProblem(infection_model2_com, u0, tspan, params) - -# ╔═╡ 35d6bce7-9778-4f42-bdcd-271cedd8bad9 -osol2 = solve(deepcopy(oprob2), Tsit5(), saveat=0.5) - -# ╔═╡ 31ceb303-dbb1-4f4d-9082-14ea1d83ab66 -plot(osol2) - -# ╔═╡ 14143212-53f1-4753-8c25-e22559cdbccd -osol2.u[end] - -# ╔═╡ bdf9cfa0-9dc4-4ea5-9024-63813ec30d09 -md""" -### Example 3 - Continuous event - -Suppose that when the number of infected individuals reaches $1\,000\,000$, then $999\,000$ of them are promptly put into isolation (or removed from the population). -""" - -# ╔═╡ cbf0a768-f6ac-43fb-8d05-e7dac51d4cc0 -infection_model3 = @reaction_network begin - @species pwc(t)=true - α * β, S + I --> 2I - r * m, I --> D - r * (1 - m), I --> R -end - -# ╔═╡ fee597e0-97cb-40bc-a5b9-166631e8b9f6 -condition3 = [infection_model3.I ~ 1e6*infection_model3.pwc] => [infection_model3.I ~ infection_model3.I - 0.999e6, infection_model3.pwc ~ false] - -# ╔═╡ 51363f3c-7aa9-48ed-808e-d1f7a4aadc0c -@named infection_model3_c = ReactionSystem(equations(infection_model3), continuous_events=condition3) - -# ╔═╡ cd060e93-7a92-4824-b812-080391fbf554 -infection_model3_c_com = complete(infection_model3_c) - -# ╔═╡ a41878a2-2fd3-4a2a-ba85-d2a09c67da33 -oprob3 = ODEProblem(infection_model3_c_com, u0, tspan, params) - -# ╔═╡ fc2dc11e-7022-4c6f-b7ed-5b077fc21ac5 -osol3 = solve(deepcopy(oprob3), Tsit5(), saveat=0.5) - -# ╔═╡ 2b511237-7739-44e5-b6bd-26a2fdc76bb5 -plot(osol3) - -# ╔═╡ 3a131f92-aa09-4e8c-98a1-0c4c1c74878a -osol3.u[end] - -# ╔═╡ ad83a851-e424-4e8f-bae9-38b9283db2fa -md""" -## Exercises -""" - -# ╔═╡ b580ef9b-7d53-4bf0-8511-213920d59ee2 -md""" -### Exercise 1 - Influence of $\alpha$ - -Evaluate the effect of a decreasing risk of infection after contact with an infected person, i.e. $r = 0.2$, $\beta = 0.1$ and $\alpha$ between $8\%$ and $20\%$. - -Use the same initial values and timespan as before. -""" - -# ╔═╡ 090a949f-c18f-4e07-8c94-21a71bb0ceed -md""" -Make a slider for $\alpha$ in the range of $0.08$ and $0.20$ with a step of $0.02$. Take a default value of $0.08$. -""" - -# ╔═╡ 85e81822-2ea6-4d05-b879-1791bff255b9 -# missing # Uncomment and complete the instruction - -# ╔═╡ a2a109e3-ea74-4e74-91d0-6ea12ddf28d4 -md" -Initialize vector `params_ex1` with parameter values: -" - -# ╔═╡ 27a4c4f5-0b2b-4d23-9bb0-a1a8ff6cfc4d -# params_ex1 = missing # Uncomment and complete the instruction - -# ╔═╡ e0a8a397-4500-47de-8be3-49d3174648b1 -md""" -Create the ODE problem and store it in `oprob_ex1`: -""" - -# ╔═╡ 7955722d-fc35-4cce-8bf2-ee40ee7fbc82 -# oprob_ex1 = missing; # Uncomment and complete the instruction - -# ╔═╡ 0590bc0b-45d6-4a8e-8078-af057984523f -md""" -Solve the ODE problem and store the solution in `osol_ex1`: -""" - -# ╔═╡ f7b09e96-cf30-4bd0-841b-46621df693ab -# osol_ex1 = missing; # Uncomment and complete the instruction - -# ╔═╡ e7947165-b244-4ff6-bdf3-61d03aefe696 -md""" -Plot the solutions: -""" - -# ╔═╡ adc0aa15-e0a9-4549-9609-967a9dc78b78 -# missing # Uncomment and complete the instruction - -# ╔═╡ 7a1effe4-776c-4143-8085-f98a213a2cc3 -md""" -Change the value of $\alpha$ in the `params_ex1` vector to visualize the effect in the plot. -""" - -# ╔═╡ c14d45b8-3af1-4e05-b10a-081ccd62a34d -md""" -Try to interpret the results yourself. -Ask yourself the following questions: -1. What are the trends in the obtained results? -""" - -# ╔═╡ 04f89fb3-f856-4206-9871-4d92f2816332 -md"- Answer: missing" - -# ╔═╡ a39eeec9-d816-4d35-90ae-e602a24bb056 -md""" -2. How can this be explained from the model structure? -""" - -# ╔═╡ fcad8ede-8295-46d6-a637-728b3986a761 -md"- Answer: missing" - -# ╔═╡ b3f08d61-187d-4f99-b95f-508ff1b3d52d -md""" -### Exercise 2 - Administration of medicinal products - -Scientists have developed a medicine that heals sick people and makes them immune to -the disease. After administering medication, the infection duration is reduced to two -days. All treated patients heal and acquire immunity to the virus. The model will have -to be extended with two additional parameters. - -- Parameter $b$: the fraction of infected persons undergoing treatment. -- Parameter $h$: the rate at which the infected persons treated are no longer contagious ($day^{-1}$). - -Administering the drug to a fraction of the infected individuals affects two *reactions*: $I \rightarrow D$ and $I \rightarrow R$, with the following assumptions: - -- The fraction of infected persons treated ($b$) has a reduced infection duration. -- The fraction of infected individuals not receiving treatment ($1 − b$) still has the same duration of infection. -- The mortality rate $m$ only affects the group of sick people who were not given any medication. -- All treated individuals recover. -- A fraction of the untreated individuals also heals. - -Check the effect on the epidemic when $0\%$, $25\%$, $50\%$, $75\%$ and $100\%$ of infected individuals are treated with $h = 0.5$. Use the same initial conditions and timespan as before. -""" - -# ╔═╡ 08ba4614-e4fa-4203-9e28-c5c4bd9fcdd5 -md""" -Set-up the new *reaction network/model* and name it `infection_med`: -""" - -# ╔═╡ e4087441-7d0d-4716-b9fa-eef45e9f3b87 -# infection_med = @reaction_network begin # Uncomment and complete the instruction -# α * β, S + I --> 2I -# ..., I --> D -# (..., ...), I --> R -# end - -# ╔═╡ 87b3796d-16c5-4fe9-a32e-d8bf7c754254 -md""" -Convert to an ODE system. Check the differential equations and make sure you understand each term. -""" - -# ╔═╡ 2a66417b-79a2-4909-bd89-5650c49b9411 -# osys_ex2 = missing # Uncomment and complete the instruction - -# ╔═╡ 35956a99-5729-4bf7-bf09-b2232fc958da -md""" -Set-up parameter values: -""" - -# ╔═╡ cc31567f-631b-41b9-aa2d-3408ca951bcf -# params_ex2 = missing # Uncomment and complete the instruction - -# ╔═╡ 82f8c50c-511d-4399-ac87-0890b678c6c1 -md""" -Create the ODE problem and store it in `oprob_ex2`: -""" - -# ╔═╡ ec08adb2-561c-4f17-a0b5-019d7b1f1098 -# oprob_ex2 = missing; # Uncomment and complete the instruction - -# ╔═╡ c55ec157-6ec8-45db-9a54-0f39aceb9607 -md""" -Solve the ODE problem and store the solution in `osol_ex2`: -""" - -# ╔═╡ 67668918-624e-4adf-be5d-6fdbc62555f6 -# osol_ex2 = missing; # Uncomment and complete the instruction - -# ╔═╡ 832f984a-176d-4187-8476-5026d29a63d8 -md""" -Plot the solutions: -""" - -# ╔═╡ 5f9a9838-099d-457f-9402-915f42d0cd33 -# missing # Uncomment and complete the instruction - -# ╔═╡ ef3ca25d-c110-452d-877a-6304ae6cd5a7 -md""" -Change the value of $b$ in the `params_ex2` vector to visualize the effect in the plot. Interpret the obtained plots. -""" - -# ╔═╡ f8dbab24-55aa-466f-a978-5f8db93b4b93 -md""" -Try to answer the following questions: - -1. Why does the peak in the number of infected individuals shift to the right when the value of $b$ increases? -""" - -# ╔═╡ 381d3a54-0e4f-4203-874f-4a4716d481d4 -md"- Answer: missing" - -# ╔═╡ fd7d89f3-9ab8-4b17-b26f-571bad862718 -md""" -2. Why does the number of recovered individuals first rise when the value of $b$ increases and then fall when the value of $b$ continues to increase? -""" - -# ╔═╡ 96de4f0d-c2d1-4fad-bded-a314cbca848b -md"- Answer: missing" - -# ╔═╡ dedb1ac1-cb54-416f-a092-12a50080c69f -md""" -Check the number of fatalities: -""" - -# ╔═╡ a912f449-6c9d-4595-863f-a2ba523bad95 -# missing - -# ╔═╡ 089707c3-1df6-4799-a87b-0d4872a0267d -md""" -### Exercise 3 - Adding vaccination to the model - -Scientists have developed a vaccine that makes healthy people immediately immune to -the disease. - -Vaccination affects several differential equations: -- Susceptible individuals are vaccinated at a rate of $v$ (with unit $day^{-1}$). These persons can therefore no longer be infected. -- The vaccinated persons become resistant. - -We are going to use a vaccination rate $v$ so that the number of fatalities is about 10 times smaller after a period of 90 days compared to those in absence of vaccination (cf. Exercise 2). - -The vaccination programme is launched $2$ days after the outbreak of the disease. - -Assume that individuals are still being treated ($b = 0.2$ and $h = 0.5$). Extend the model obtained in the previous exercise for the launch of a vaccination campaign after the outbreak of the disease. - -Find out via trial and error what the minimum vaccination rate need to be so that the number of fatalities is $10$ times smaller after a period of $90$ days compared to those in absence of vaccination (cf. Exercise 2). Consider an initial step size in $v$ of $0.01$ and then fine tune with a step size of $0.001$. - -Use the same initial values and timespan as before. -""" - -# ╔═╡ 381a714d-699e-45c6-909a-02689b2a7e6b -md""" -Set-up the new *reaction network/model* and name it `infection_med_vac`: -""" - -# ╔═╡ a1795123-876b-4bd3-ac3a-711367b500d1 -# Uncomment and complete the instruction -# infection_med_vac = @reaction_network begin -# α * β, S + I --> 2I -# ..., I --> D -# (..., ...), I --> R -# ..., ... --> ... -# end - -# ╔═╡ 6b46d116-0da3-428a-b25f-221622dfa7e0 -md""" -Convert to an ODE system. Check the differential equations and make sure you understand each term. -""" - -# ╔═╡ 710fe13c-bd43-4d1e-867e-72330722ac1e -# osys_ex3 = missing # Uncomment and complete the instruction - -# ╔═╡ 2ee20493-2dc2-4bd4-8ec3-78032f1fcc2d -md""" -Make a slider and bind it to the variable `v`. Use a range $[0.0, 0.1]$, step size $0.001$ and default value of $0.0$. -""" - -# ╔═╡ 2943b515-fb4b-45a2-88dd-d0267ec95b09 -# missing # Uncomment and complete the instruction - -# ╔═╡ 0274fbd8-c025-4e36-bbb0-f65f21b962c7 -md""" -Set-up parameter values: -""" - -# ╔═╡ 34c4d746-45f7-43c4-a0f0-6e570373a35d -# params_ex3 = missing # Uncomment and complete the instruction - -# ╔═╡ 060a5c5b-9834-42e2-98e9-ac23e7403b60 -md""" -Create the ODE problem and store it in `oprob_ex3`: -""" - -# ╔═╡ c61ae8b6-2324-43fa-a5ae-274a919af559 -# oprob_ex3 = missing; # Uncomment and complete the instruction - -# ╔═╡ 69983b90-b6ef-4732-a4af-a772bb1364e5 -md""" -Solve the ODE problem for (step wise) increasing values of $v$ and store the solution in `osol_ex3_vac`. Consider an initial step size in $v$ of $0.01$ and then fine tune with a step size of $0.001$. -""" - -# ╔═╡ 16211bc6-cd97-43c0-8faf-25bb490930b6 -# osol_ex3_no_vac = missing; # Uncomment and complete the instruction - -# ╔═╡ 461eada9-5f9c-4439-8b65-226382b6d148 -md""" -Compare the latter with the number of fatalities when no vaccination is/was available (cf. Exercise 2) by setting up a condition (a boolean expression return either `true` or `false`) here below where the final number of fatalities (with vaccination) divided by 10 is compared with (use larger than or smaller than) the number of fatalities (without vaccination): -""" - -# ╔═╡ 0994d790-0fe8-46ab-bba2-a0a4ea466f64 -# missing # Uncomment and complete the instruction - -# ╔═╡ 8bb5dafd-6c26-4634-8be4-c2963efba056 -md""" -Once you have found the required value of $v$ launch the vaccination programme $2$ days after the outbreak. Set-up the $2$-day time condition and store it in `condition_ex3`: -""" - -# ╔═╡ 26c77206-fe3c-45c5-a627-f2b346c66be7 -# condition_ex3 = missing # Uncomment and complete the instruction - -# ╔═╡ 527e55a8-1744-4c40-a412-054bed6d77f0 -md""" -Make a new *reaction system* where the discrete event is included. Name it `infection_med_vac_c`. -""" - -# ╔═╡ ceae9947-ed0f-4975-a944-cc938cf22dde -# @named infection_med_vac_c = missing # Uncomment and complete the instruction - -# ╔═╡ 6e767e36-f54e-4472-881a-ab04ad9c9d09 -md""" -Complete the new *reaction system*. Name it `infection_med_vac_c_com`. -""" - -# ╔═╡ 870c9d65-2229-40b7-9959-59afecb4f3bf -# infection_med_vac_c_com = missing # Uncomment and complete the instruction - -# ╔═╡ 93ca3970-bebd-4192-bc62-6ca08d58a6c2 -md""" -Create the ODE problem and store it in `oprob_ex3_c`: -""" - -# ╔═╡ 7e4114d6-932a-4def-9bb5-b534072ea513 -# oprob_ex3_c = missing # Uncomment and complete the instruction - -# ╔═╡ 1bf8bc58-6848-4783-a614-7ac11646de92 -md""" -Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol_ex3`. -""" - -# ╔═╡ 8814c42b-16c7-466a-8ad8-58ee1c0921b2 -# osol_ex3 = missing; # Uncomment and complete the instruction - -# ╔═╡ 25158ea5-1930-4fee-aab4-490b475d8635 -md""" -Plot the solutions: -""" - -# ╔═╡ ed9472a0-09c9-4d1c-8f1d-1b7d87b97640 -# missing # Uncomment and complete the instruction - -# ╔═╡ Cell order: -# ╠═18a4df05-0349-400d-a29e-b3fa71aa4d88 -# ╠═65b3536b-c9b3-485e-87e1-4f6657381503 -# ╠═989fd8c8-25d9-47b9-ade6-6c7f21a7dceb -# ╠═e968f6d5-962c-4de8-a9a3-ec116805d5e1 -# ╟─c54c7e3c-f3d1-40a5-88f7-bedcab3269ba -# ╟─fab49cb7-c41e-498a-98f6-33b4821ceb90 -# ╟─8ff0b57e-acc1-4ebd-a562-a82c9efa7ffc -# ╟─e36b150a-b4fc-476f-bab7-e1162a4b263d -# ╟─bdefa657-88bd-43d6-8e54-cc6321a9e720 -# ╟─afb751b9-39b8-4430-af4b-02f010667518 -# ╟─aab497eb-c0bd-49e3-a0c1-71e13f5b0ad5 -# ╠═e97637b0-446c-44ac-bd08-c56632f9b57f -# ╟─a91f1024-4800-4ffe-8bb2-5f6cf3c2bde1 -# ╟─47f4a7cc-1a74-4ff0-ae21-2a5e4c495935 -# ╠═3f6080e1-ab43-47f7-a82b-95956bf4cafd -# ╟─5d5fae40-a4ed-4908-ba65-f3a16c38ab4f -# ╠═fd347a58-5cad-4073-aadb-176fa54dcdfa -# ╟─bd5bd448-8741-4528-989f-9754b0e55b9b -# ╠═a78b178e-6952-4057-bd8c-6d5b0c5e0518 -# ╟─cbfe8833-786c-48f7-b849-705006d4c41d -# ╠═96f3dd68-da80-4f07-af2b-e3b9ec05cc0c -# ╟─cc4d514a-dfed-4424-9982-8315954b38ff -# ╠═3aad1f32-7daf-49a5-9dd3-9ddbba1a50de -# ╟─a432a88a-8db3-4729-b9a3-8ca9322fc81e -# ╠═7e268605-8314-4e7b-8c38-a65212e148a9 -# ╠═0707b599-c1ca-459b-b87b-956f8f49564b -# ╠═113ff67e-6dc8-45cb-9617-7768e132f6e9 -# ╟─39e396e1-20fa-4e78-b0ab-99774ce55f0f -# ╠═c992a16d-80d3-48d6-bf43-2dbfa8e2f081 -# ╟─4a581cce-9a1b-4516-9cf1-6a41ea6566e7 -# ╠═c5ee2780-c652-4c9b-981b-53e5f91e1766 -# ╠═fc380000-33a0-419d-9226-7a0c136de8c6 -# ╠═a8756838-93d7-4909-84c2-6d89be4fa711 -# ╟─6d2580f7-f882-449d-a9b8-629ab41e2671 -# ╠═d3f39b29-71f5-4674-a52f-3ba63279fca7 -# ╠═6173506b-8f56-4100-a0a0-117c1ee8aa0f -# ╠═e6b17756-a7e9-4f50-98dc-b69f9a54903f -# ╠═006bfc98-3c95-4763-b0ed-f4714ad5bd02 -# ╠═4c5d2e5d-3237-4886-b852-3142fb996dd6 -# ╠═f9bd32f5-c473-44d4-af9d-4d99032a2d21 -# ╟─000495a6-e91d-438e-ab6d-7a5e95a99f27 -# ╠═d73f8ed8-ac25-4c1c-b834-989a2a929df3 -# ╠═4ca0e1df-7765-4f36-a442-2749ea493426 -# ╠═d56b486e-44e5-4db7-9c4e-00db5c1b733f -# ╠═298ada89-4581-41c5-a89f-f01ae431c5ec -# ╠═5b4ca642-0f89-40ab-9a2f-c35fc603a91d -# ╟─171208da-69a3-4737-85e0-fae0805bbaa8 -# ╟─98aedcf5-e0de-442f-b19b-c5b3d9e3d475 -# ╠═462b2786-98d6-40cb-887d-aff6b63881e8 -# ╠═efd74885-c25c-42e5-bef5-c9654329c220 -# ╠═ecf20050-12d3-48e7-84f1-6919a9aab865 -# ╠═fdea5a92-e115-4dbe-a885-95772857c686 -# ╠═35d6bce7-9778-4f42-bdcd-271cedd8bad9 -# ╠═31ceb303-dbb1-4f4d-9082-14ea1d83ab66 -# ╠═14143212-53f1-4753-8c25-e22559cdbccd -# ╟─bdf9cfa0-9dc4-4ea5-9024-63813ec30d09 -# ╠═cbf0a768-f6ac-43fb-8d05-e7dac51d4cc0 -# ╠═fee597e0-97cb-40bc-a5b9-166631e8b9f6 -# ╠═51363f3c-7aa9-48ed-808e-d1f7a4aadc0c -# ╠═cd060e93-7a92-4824-b812-080391fbf554 -# ╠═a41878a2-2fd3-4a2a-ba85-d2a09c67da33 -# ╠═fc2dc11e-7022-4c6f-b7ed-5b077fc21ac5 -# ╠═2b511237-7739-44e5-b6bd-26a2fdc76bb5 -# ╠═3a131f92-aa09-4e8c-98a1-0c4c1c74878a -# ╟─ad83a851-e424-4e8f-bae9-38b9283db2fa -# ╟─b580ef9b-7d53-4bf0-8511-213920d59ee2 -# ╟─090a949f-c18f-4e07-8c94-21a71bb0ceed -# ╠═85e81822-2ea6-4d05-b879-1791bff255b9 -# ╟─a2a109e3-ea74-4e74-91d0-6ea12ddf28d4 -# ╠═27a4c4f5-0b2b-4d23-9bb0-a1a8ff6cfc4d -# ╟─e0a8a397-4500-47de-8be3-49d3174648b1 -# ╠═7955722d-fc35-4cce-8bf2-ee40ee7fbc82 -# ╟─0590bc0b-45d6-4a8e-8078-af057984523f -# ╠═f7b09e96-cf30-4bd0-841b-46621df693ab -# ╟─e7947165-b244-4ff6-bdf3-61d03aefe696 -# ╠═adc0aa15-e0a9-4549-9609-967a9dc78b78 -# ╟─7a1effe4-776c-4143-8085-f98a213a2cc3 -# ╟─c14d45b8-3af1-4e05-b10a-081ccd62a34d -# ╟─04f89fb3-f856-4206-9871-4d92f2816332 -# ╟─a39eeec9-d816-4d35-90ae-e602a24bb056 -# ╟─fcad8ede-8295-46d6-a637-728b3986a761 -# ╟─b3f08d61-187d-4f99-b95f-508ff1b3d52d -# ╟─08ba4614-e4fa-4203-9e28-c5c4bd9fcdd5 -# ╠═e4087441-7d0d-4716-b9fa-eef45e9f3b87 -# ╟─87b3796d-16c5-4fe9-a32e-d8bf7c754254 -# ╠═2a66417b-79a2-4909-bd89-5650c49b9411 -# ╟─35956a99-5729-4bf7-bf09-b2232fc958da -# ╠═cc31567f-631b-41b9-aa2d-3408ca951bcf -# ╟─82f8c50c-511d-4399-ac87-0890b678c6c1 -# ╠═ec08adb2-561c-4f17-a0b5-019d7b1f1098 -# ╟─c55ec157-6ec8-45db-9a54-0f39aceb9607 -# ╠═67668918-624e-4adf-be5d-6fdbc62555f6 -# ╟─832f984a-176d-4187-8476-5026d29a63d8 -# ╠═5f9a9838-099d-457f-9402-915f42d0cd33 -# ╟─ef3ca25d-c110-452d-877a-6304ae6cd5a7 -# ╟─f8dbab24-55aa-466f-a978-5f8db93b4b93 -# ╟─381d3a54-0e4f-4203-874f-4a4716d481d4 -# ╟─fd7d89f3-9ab8-4b17-b26f-571bad862718 -# ╟─96de4f0d-c2d1-4fad-bded-a314cbca848b -# ╟─dedb1ac1-cb54-416f-a092-12a50080c69f -# ╠═a912f449-6c9d-4595-863f-a2ba523bad95 -# ╟─089707c3-1df6-4799-a87b-0d4872a0267d -# ╟─381a714d-699e-45c6-909a-02689b2a7e6b -# ╠═a1795123-876b-4bd3-ac3a-711367b500d1 -# ╟─6b46d116-0da3-428a-b25f-221622dfa7e0 -# ╠═710fe13c-bd43-4d1e-867e-72330722ac1e -# ╟─2ee20493-2dc2-4bd4-8ec3-78032f1fcc2d -# ╠═2943b515-fb4b-45a2-88dd-d0267ec95b09 -# ╟─0274fbd8-c025-4e36-bbb0-f65f21b962c7 -# ╠═34c4d746-45f7-43c4-a0f0-6e570373a35d -# ╟─060a5c5b-9834-42e2-98e9-ac23e7403b60 -# ╠═c61ae8b6-2324-43fa-a5ae-274a919af559 -# ╟─69983b90-b6ef-4732-a4af-a772bb1364e5 -# ╠═16211bc6-cd97-43c0-8faf-25bb490930b6 -# ╟─461eada9-5f9c-4439-8b65-226382b6d148 -# ╠═0994d790-0fe8-46ab-bba2-a0a4ea466f64 -# ╟─8bb5dafd-6c26-4634-8be4-c2963efba056 -# ╠═26c77206-fe3c-45c5-a627-f2b346c66be7 -# ╟─527e55a8-1744-4c40-a412-054bed6d77f0 -# ╠═ceae9947-ed0f-4975-a944-cc938cf22dde -# ╟─6e767e36-f54e-4472-881a-ab04ad9c9d09 -# ╠═870c9d65-2229-40b7-9959-59afecb4f3bf -# ╟─93ca3970-bebd-4192-bc62-6ca08d58a6c2 -# ╠═7e4114d6-932a-4def-9bb5-b534072ea513 -# ╟─1bf8bc58-6848-4783-a614-7ac11646de92 -# ╠═8814c42b-16c7-466a-8ad8-58ee1c0921b2 -# ╟─25158ea5-1930-4fee-aab4-490b475d8635 -# ╠═ed9472a0-09c9-4d1c-8f1d-1b7d87b97640 diff --git a/src/exercises/ode_model_irrigation.jl b/src/exercises/ode_model_irrigation.jl deleted file mode 100644 index a7956b85..00000000 --- a/src/exercises/ode_model_irrigation.jl +++ /dev/null @@ -1,243 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "5" -#> title = "1. ODE irrigation" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of irrigation" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 806ed5ad-788b-4f4b-b72f-3720766b6959 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ f3ae43dc-f7e1-11ee-3615-695b9e85b621 -using Markdown - -# ╔═╡ 28217175-2968-4219-81c9-eca0aa624196 -using InteractiveUtils - -# ╔═╡ 38b9cdf9-7d7a-44e5-aee4-e0b3a1c82464 -using PlutoUI; TableOfContents() - -# ╔═╡ 5d812731-8340-49c9-b181-aef5299388b5 -using Catalyst - -# ╔═╡ 63c243ff-649d-4494-98ef-e4dee5e88030 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 2e8b2af9-2fed-49aa-a1cd-76c822664a5d -md""" -# Exercise: Irrigation experiment -""" - -# ╔═╡ a7bb2db8-8e7e-45b6-843d-2db43f1a7ad1 -md""" -An irrigation experiment is carried out on a soil column consisting of two layers of soil, each with specific soil characteristics. An adjustable volume of water per unit of time, $r$, is irrigated evenly over the soil column, starting with $5\;mm\,h^{-1}$ ($mm$ indicates a volume of water: $1\;mm = 10^{-3}\,m^3$). After $60\;h$ the added flow rate is increased to $10\;mm\,h^{-1}$. The water falls on the upper layer and percolates to the lower layer. The **relative moisture content** in both layers (i.e., relative to their residual moisture contents) is denoted by $S_1$ and $S_2$. Initially a moisture content of $30\;mm$ is present in the upper layer (cf. $S_1$) and of $25\;mm$ in the lower layer (cf. $S_2$). The residual moisture content in the upper layer is $S_{1,res}=10 \;mm$. - -A model description of the relative moisture content in both soil layers is given by: - -$$\begin{align} -\frac{dS_1}{dt} &= r\left(1-\cfrac{S_{1,res}}{S_{max}}\right) - \cfrac{r}{S_{max}}S_1 - \cfrac{k}{S_{max}}S_1 \\ -\frac{dS_2}{dt} &= \cfrac{k}{S_{max}}S_1 - v \,S_2^2 -\end{align}$$ - -Here $S_{max}$ ($150\;mm$) is the saturated water quantity for the top soil layer, $k$ is the percolation ratio ($3\;mm\,h^{-1}$) and $v$ is the flow factor into the groundwater ($10^{-3}\;h^{-1}\,mm^{-1}$). - -Three measurements are made over the duration ($150\;h$) of the experiment: -- The excess running water (runoff): $r \cfrac{S_1 + S_{1,res}}{S_{max}}$, -- The underground outflow into groundwater: $v\,S_2^2$, -- The amount of percolation to deeper soil layers: $\cfrac{k}{S_{max}} S_1$. - -The latter three are called *observables*. -""" - -# ╔═╡ 072d96fa-4933-43ed-a4fd-162f64be5cdd -md""" -Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of the three afore mentioned measurements during $150\;h$. Name it `irrigation_mod`. - -Tips: -- You can use any kind of expression for the reaction rates. -- The term $- v \,S_2^2$ is created by the reaction `v, 2S₂ --> 0` -""" - -# ╔═╡ e557a067-52a1-42d5-98ae-40a0c63a2611 -# Uncomment and complete the instruction -# irrigation_mod = @reaction_network begin -# missing -# ... -# end - -# ╔═╡ 79277f44-ffca-44e3-867d-07a95dcb538c -md""" -Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. -""" - -# ╔═╡ 2f5b954b-1615-4b14-bc3b-3426c9296221 -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ c163afdb-9411-4879-945a-8ee722ddb15a -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ 32dcda4d-2e05-4451-86c2-22c2ea1d9b63 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ f974745b-5292-4bfa-aa8a-9836ae5870c3 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 97c84665-c6ff-4c7a-8dfb-772244aa4986 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 0ceef9b8-4647-43a4-b9c3-11959834305b -md""" -Initialize a vector `params` with the parameter values: -""" - -# ╔═╡ d172fc24-0fbe-46b4-a030-1ba44b4b57cd -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 493d0d3d-cc43-4c2e-931f-f4acbc2f41ae -md""" -Unpack the variables and parameters so that we can use them in an intuitive way to calculate to observables. For $R$ we will take the value of $(5+10)/2=7.5$. -""" - -# ╔═╡ 67f62789-a5f0-4e05-a4fb-dbade2ad7891 -# @unpack ..., ..., ..., ..., ..., ... = ... # Uncomment and complete the instruction - -# ╔═╡ 7793eb61-3a2a-471a-b283-20db4a059b70 -md""" -Create the *condition* that contains the timepoint for the sudden change in $R$. Store it in `condition`: -""" - -# ╔═╡ c2bb92b9-ad9e-4bbc-8914-822848aea552 -# condition = missing # Uncomment and complete the instruction - -# ╔═╡ 62ef97da-aedf-446d-ba2c-8c0ac72c9f05 -md""" -Make a new *reaction system* where the discrete event is included. Name it `irrigation_mod_c`. -""" - -# ╔═╡ 596b411b-2e76-48af-b3ce-8b73aa6f8700 -# @named irrigation_mod_c = missing - -# ╔═╡ a38e9488-af43-49c6-9e7b-51f15e9a65ab -md""" -Complete the new *reaction system*. Name it `irrigation_mod_c_com`. -""" - -# ╔═╡ a376c25d-059c-4a39-8c71-c8eb4310df72 -# irrigation_mod_c_com = missing # Uncomment and complete the instruction - -# ╔═╡ 147b012a-298f-4885-9e71-453151c12bee -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ f222f889-d602-4654-9c90-a813738377c3 -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ 18717738-ae99-4b41-8414-4c1823307dde -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol`: -""" - -# ╔═╡ dd5ffca7-4b39-4089-afee-dd73e4a9ac15 -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ ba5adc9e-5339-45e2-a126-fbdcdd0f283e -md""" -Calculate the observables. Take $r=(5.0+10.0)/2 = 7.5$. We summarize the observables here. - -- The excess running water (runoff): $R \cfrac{S_1 + S_{1,res}}{S_{max}}$, -- The underground outflow into groundwater: $v\,S_2^2$, -- The amount of percolation to deeper soil layers: $\cfrac{k}{S_{max}} S_1$. -""" - -# ╔═╡ aeebc2d2-2052-4b80-a537-90d073ac3fe2 -# Uncomment and complete the instruction -# begin -# runoff = ...; -# outflow = ...; -# percolation = ...; -# end - -# ╔═╡ 3e1beea6-0fe4-4d77-9e5d-0ca78a662253 -md""" -Plot the runoff, outflow and percolation. -""" - -# ╔═╡ 15f14688-cf6e-4562-bcca-d3d0774c2d5c -# plot(osol; idxs=[..., ..., ...], labels=["..." "..." "..."]) - -# ╔═╡ d1a9c363-3ef6-4bbb-a150-63b44f9e7cf1 -md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the effect of the increase in $r$? -""" - -# ╔═╡ 63ca9bf9-8b49-41ee-a062-eecc54f88e28 -md"- Answer: missing" - -# ╔═╡ 85e8869e-5859-4b8f-a72d-f0c979191ec7 -md""" -2. Argue why the outflow and the percolation tend to the same value. -""" - -# ╔═╡ 8b2459ca-9982-4c3a-b872-184c34289ec9 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═f3ae43dc-f7e1-11ee-3615-695b9e85b621 -# ╠═28217175-2968-4219-81c9-eca0aa624196 -# ╠═806ed5ad-788b-4f4b-b72f-3720766b6959 -# ╠═38b9cdf9-7d7a-44e5-aee4-e0b3a1c82464 -# ╟─2e8b2af9-2fed-49aa-a1cd-76c822664a5d -# ╟─a7bb2db8-8e7e-45b6-843d-2db43f1a7ad1 -# ╠═5d812731-8340-49c9-b181-aef5299388b5 -# ╟─072d96fa-4933-43ed-a4fd-162f64be5cdd -# ╠═e557a067-52a1-42d5-98ae-40a0c63a2611 -# ╟─79277f44-ffca-44e3-867d-07a95dcb538c -# ╠═2f5b954b-1615-4b14-bc3b-3426c9296221 -# ╠═63c243ff-649d-4494-98ef-e4dee5e88030 -# ╟─c163afdb-9411-4879-945a-8ee722ddb15a -# ╠═32dcda4d-2e05-4451-86c2-22c2ea1d9b63 -# ╟─f974745b-5292-4bfa-aa8a-9836ae5870c3 -# ╠═97c84665-c6ff-4c7a-8dfb-772244aa4986 -# ╟─0ceef9b8-4647-43a4-b9c3-11959834305b -# ╠═d172fc24-0fbe-46b4-a030-1ba44b4b57cd -# ╟─493d0d3d-cc43-4c2e-931f-f4acbc2f41ae -# ╠═67f62789-a5f0-4e05-a4fb-dbade2ad7891 -# ╟─7793eb61-3a2a-471a-b283-20db4a059b70 -# ╠═c2bb92b9-ad9e-4bbc-8914-822848aea552 -# ╟─62ef97da-aedf-446d-ba2c-8c0ac72c9f05 -# ╠═596b411b-2e76-48af-b3ce-8b73aa6f8700 -# ╟─a38e9488-af43-49c6-9e7b-51f15e9a65ab -# ╠═a376c25d-059c-4a39-8c71-c8eb4310df72 -# ╟─147b012a-298f-4885-9e71-453151c12bee -# ╠═f222f889-d602-4654-9c90-a813738377c3 -# ╟─18717738-ae99-4b41-8414-4c1823307dde -# ╠═dd5ffca7-4b39-4089-afee-dd73e4a9ac15 -# ╟─ba5adc9e-5339-45e2-a126-fbdcdd0f283e -# ╠═aeebc2d2-2052-4b80-a537-90d073ac3fe2 -# ╟─3e1beea6-0fe4-4d77-9e5d-0ca78a662253 -# ╠═15f14688-cf6e-4562-bcca-d3d0774c2d5c -# ╟─d1a9c363-3ef6-4bbb-a150-63b44f9e7cf1 -# ╟─63ca9bf9-8b49-41ee-a062-eecc54f88e28 -# ╟─85e8869e-5859-4b8f-a72d-f0c979191ec7 -# ╟─8b2459ca-9982-4c3a-b872-184c34289ec9 diff --git a/src/exercises/ode_model_irrigation_mtk.jl b/src/exercises/ode_model_irrigation_mtk.jl new file mode 100644 index 00000000..c1fa31bb --- /dev/null +++ b/src/exercises/ode_model_irrigation_mtk.jl @@ -0,0 +1,278 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "2" +#> title = "1. ODE_model_irrigation" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Modeling of irrigation with MTK" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 715c1fae-2a4d-11f0-0569-bd3495497826 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 34856dc0-e961-4e3e-a996-1966e4191e35 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ bc7f57f2-bb4d-44d4-b693-f4cf412b470a +using OrdinaryDiffEq, ModelingToolkit + +# ╔═╡ 45c84728-ff11-4345-9c71-fab084e3159f +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 4252c39a-ab0f-4392-8e77-1c2e2c3e36e6 +md""" +# Exercise: Irrigation experiment +""" + +# ╔═╡ 869e0a97-45da-459d-b9e2-cd519241fcd0 +md""" +![](https://users.ugent.be/~gvhaelew/fig/irrigation_model.png) +""" + +# ╔═╡ 22a32fa7-d9e3-4ed0-bc69-0f82b229a524 +md""" +An irrigation experiment is carried out on a soil column consisting of two layers of soil, each with specific soil characteristics. An adjustable volume of water per unit of time, $r$, is irrigated evenly over the soil column, starting with $5\;mm\,h^{-1}$ ($mm$ indicates a volume of water: $1\;mm = 10^{-3}\,m^3$). After $60\;h$ the added flow rate is increased to $10\;mm\,h^{-1}$. The water falls on the upper layer and percolates to the lower layer. The **relative moisture content** in both layers (i.e., relative to their residual moisture contents) is denoted by $S_1$ and $S_2$. Initially a moisture content of $30\;mm$ is present in the upper layer (cf. $S_1$) and of $25\;mm$ in the lower layer (cf. $S_2$). The residual moisture content in the upper layer is $S_{1,res}=10 \;mm$. + +A model description of the relative moisture content in both soil layers is given by: + +$$\begin{align} +\frac{dS_1}{dt} &= r\left(1-\cfrac{S_{1,res}}{S_{max}}\right) - \cfrac{r}{S_{max}}S_1 - \cfrac{k}{S_{max}}S_1 \\ +\frac{dS_2}{dt} &= \cfrac{k}{S_{max}}S_1 - v \,S_2^2 +\end{align}$$ + +Here $S_{max}$ ($150\;mm$) is the saturated water quantity for the top soil layer, $k$ is the percolation ratio ($3\;mm\,h^{-1}$) and $v$ is the flow factor into the groundwater ($10^{-3}\;h^{-1}\,mm^{-1}$). + +Three measurements are made over the duration ($150\;h$) of the experiment: +- The excess running water (runoff): $R = r \cfrac{S_1 + S_{1,res}}{S_{max}}$, +- The underground outflow into groundwater: $O = v\,S_2^2$, +- The amount of percolation to deeper soil layers: $P = \cfrac{k}{S_{max}} S_1$. + +The latter three are called *observables*. +""" + +# ╔═╡ 1bebe2ff-603a-42ba-8526-ab100fa3880d +md""" +Model the aforementioned system of differential equations using ModelingToolkit. +""" + +# ╔═╡ f5389efd-941f-488b-8d39-407f223277ec +md""" +## Setting up the equations +""" + +# ╔═╡ 832e65cb-5029-48f5-9ed2-41c46e759a6c +md""" +Define the variables $S_1$ and $S_2$ with their corresponding initial values. Consider the observables $R$, $O$ and $P$ also as variables, as well as the 'parameter' $r$. +""" + +# ╔═╡ 51fbf7e9-00f6-4864-9844-eabe8fdc6f1f +# @variables missing + +# ╔═╡ 3c396ae5-bbbf-40f4-9203-f7fdce4cfca8 +md""" +Define the parameters for this model and assign their corresponding values. Mind that $r$ is a variable and, hence, should not be listed as a parameter. + +| Parameter | Value | Unit | +|:---------- |:---------- |:------------| +| `k` | 3 | $mm\,h^{-1}$ | +| `v` | 1.0e-3 | $h^{-1}\,mm^{-1}$ | +| `S₁res`| 10 | $mm$ | +| `Smax` | 150 | $mm$ | +""" + +# ╔═╡ 69b88c66-1b0c-4881-83cc-44b66b6b3935 +# @parameters missing + +# ╔═╡ ffffbbcb-29d2-4c70-8c68-97be874608e8 +md""" +Set up de model equations. +""" + +# ╔═╡ d7e2b0d6-cdbc-49cd-a2fa-d055c6476b5b +change_S1 = missing + +# ╔═╡ 64516f9d-3ce2-4fee-a5b7-3f89e5df992f +change_S2 = missing + +# ╔═╡ e326c749-2b10-4f15-b10b-49b4b7253c92 +md""" +Set up an equation for the variable $r$. + +!!! hint + The equation contains two terms: a constant term $5$ and a another term that will add $5$ when $t > 60$. +""" + +# ╔═╡ caa7a0bb-4d0e-450a-8057-8efb4fbfdf58 +eq_flow_rate = missing + +# ╔═╡ 3905ea2d-bd3c-4043-a802-0d68386011c4 +md""" +Set up the equations for the runoff $R$, the outflow $O$ and the percolation $P$. +""" + +# ╔═╡ 289135c1-b97b-46b3-87b2-a0a02538fa95 +eq_runoff = missing + +# ╔═╡ 1523b4c5-0c02-4848-bfe3-68695f5a30b7 +eq_outflow = missing + +# ╔═╡ 817dad8d-99fe-415c-87dd-a52ad4b2bf9f +eq_percolation = missing + +# ╔═╡ a97c2de8-1873-45f5-a708-3b34f9a216ae +md""" +Bundle all equations. +""" + +# ╔═╡ 8236ec1e-5236-4c3f-a5ed-63e7aef63d01 +eqs_irrigation = missing + +# ╔═╡ e0456a9a-dbb3-4f9c-889c-7e8dfc5e082b +md""" +## Building the ODE system +""" + +# ╔═╡ 872ca902-45b6-496e-ae96-fa1b1c6e16bd +md""" +Build a system of equations with `@mtkbuild`. Name it `sys_irrigation`. +""" + +# ╔═╡ 9c70c339-c2ac-4983-8564-4a02fdb73e8a +# @mtkbuild missing + +# ╔═╡ 5840e484-447d-4ed7-bdf1-98c71141ba55 +md""" +## Create and solve the ODE problem +""" + +# ╔═╡ 72f30ddc-d442-49e9-8b18-ac94cb561a7f +md""" +Create the ODE problem for your system and name it `oprob_irrigation`. +""" + +# ╔═╡ fa1c55a1-3020-44f0-88b4-3e089fd691ec +oprob_irrigation = missing + +# ╔═╡ a8c3ae37-a2d0-438e-8acf-36ba19f981ac +md""" +Solve the ODE problem. Use `Tsit5()`, `saveat=1.0` and `reltol=1e-9`. Store the solution in `osol_irrigation`: +""" + +# ╔═╡ 0e382f7c-cf9c-4ed2-b51a-32539a9eb8dc +osol_irrigation = missing + +# ╔═╡ 9ec65f94-4577-4f54-9565-e7fd2061dfd0 +md""" +## Plotting results +""" + +# ╔═╡ cebac4b0-1ca1-4e5c-adb6-73fbe2b3ad1f +md""" +Plot $R$, $O$ and $P$. +""" + +# ╔═╡ c9aa4a15-5fd4-4598-ad46-6680585965f7 +missing + +# ╔═╡ 8954cc45-2feb-40fe-a622-7ccd13661ad9 +md""" +Check out the solution for $S_1$ and $S_2$. +""" + +# ╔═╡ ff59a8f8-dc05-4b2e-be7f-3cef50ea5605 +missing + +# ╔═╡ 86c80ab7-a7d9-4224-817b-8b84de3885a9 +missing + +# ╔═╡ fd18fb8a-f0d5-417a-9854-8d8f0f0bb3ce +md""" +What is the value of $S_1$ for $t=100\;h$? +""" + +# ╔═╡ 90586677-2b9a-4fef-b9ef-098eb288ee64 +missing + +# ╔═╡ 19d5382d-a657-4993-bc87-75e9edfd1dbd +md""" +Calculate the value of $R$ for $t=100\;h$ using the value for $S_1$ at $t=100\;h$ and the parameters. +""" + +# ╔═╡ ccf44020-d61a-4bb0-addd-4b27a9dbc0ad +missing + +# ╔═╡ 7cac3f5c-684f-4edc-b55f-6cf343556291 +md""" +!!! question + Does this value correspond to the value you can determine from the plot of $R$? +""" + +# ╔═╡ 9f3cce18-4af3-4270-803b-b6b1c773bb4c +md""" +Answers: missing +""" + +# ╔═╡ 0f774f27-6251-46d3-a9e1-64e3d20ea9be +md""" +Make a plot of $S_1$ and $S_2$. +""" + +# ╔═╡ 2ac192f4-fa55-44bd-9b26-854a0f3266ca +missing + +# ╔═╡ Cell order: +# ╟─4252c39a-ab0f-4392-8e77-1c2e2c3e36e6 +# ╠═715c1fae-2a4d-11f0-0569-bd3495497826 +# ╠═34856dc0-e961-4e3e-a996-1966e4191e35 +# ╠═bc7f57f2-bb4d-44d4-b693-f4cf412b470a +# ╠═45c84728-ff11-4345-9c71-fab084e3159f +# ╟─869e0a97-45da-459d-b9e2-cd519241fcd0 +# ╟─22a32fa7-d9e3-4ed0-bc69-0f82b229a524 +# ╟─1bebe2ff-603a-42ba-8526-ab100fa3880d +# ╟─f5389efd-941f-488b-8d39-407f223277ec +# ╟─832e65cb-5029-48f5-9ed2-41c46e759a6c +# ╠═51fbf7e9-00f6-4864-9844-eabe8fdc6f1f +# ╟─3c396ae5-bbbf-40f4-9203-f7fdce4cfca8 +# ╠═69b88c66-1b0c-4881-83cc-44b66b6b3935 +# ╟─ffffbbcb-29d2-4c70-8c68-97be874608e8 +# ╠═d7e2b0d6-cdbc-49cd-a2fa-d055c6476b5b +# ╠═64516f9d-3ce2-4fee-a5b7-3f89e5df992f +# ╟─e326c749-2b10-4f15-b10b-49b4b7253c92 +# ╠═caa7a0bb-4d0e-450a-8057-8efb4fbfdf58 +# ╟─3905ea2d-bd3c-4043-a802-0d68386011c4 +# ╠═289135c1-b97b-46b3-87b2-a0a02538fa95 +# ╠═1523b4c5-0c02-4848-bfe3-68695f5a30b7 +# ╠═817dad8d-99fe-415c-87dd-a52ad4b2bf9f +# ╟─a97c2de8-1873-45f5-a708-3b34f9a216ae +# ╠═8236ec1e-5236-4c3f-a5ed-63e7aef63d01 +# ╟─e0456a9a-dbb3-4f9c-889c-7e8dfc5e082b +# ╟─872ca902-45b6-496e-ae96-fa1b1c6e16bd +# ╠═9c70c339-c2ac-4983-8564-4a02fdb73e8a +# ╟─5840e484-447d-4ed7-bdf1-98c71141ba55 +# ╟─72f30ddc-d442-49e9-8b18-ac94cb561a7f +# ╠═fa1c55a1-3020-44f0-88b4-3e089fd691ec +# ╟─a8c3ae37-a2d0-438e-8acf-36ba19f981ac +# ╠═0e382f7c-cf9c-4ed2-b51a-32539a9eb8dc +# ╟─9ec65f94-4577-4f54-9565-e7fd2061dfd0 +# ╟─cebac4b0-1ca1-4e5c-adb6-73fbe2b3ad1f +# ╠═c9aa4a15-5fd4-4598-ad46-6680585965f7 +# ╟─8954cc45-2feb-40fe-a622-7ccd13661ad9 +# ╠═ff59a8f8-dc05-4b2e-be7f-3cef50ea5605 +# ╠═86c80ab7-a7d9-4224-817b-8b84de3885a9 +# ╟─fd18fb8a-f0d5-417a-9854-8d8f0f0bb3ce +# ╠═90586677-2b9a-4fef-b9ef-098eb288ee64 +# ╟─19d5382d-a657-4993-bc87-75e9edfd1dbd +# ╠═ccf44020-d61a-4bb0-addd-4b27a9dbc0ad +# ╟─7cac3f5c-684f-4edc-b55f-6cf343556291 +# ╠═9f3cce18-4af3-4270-803b-b6b1c773bb4c +# ╟─0f774f27-6251-46d3-a9e1-64e3d20ea9be +# ╠═2ac192f4-fa55-44bd-9b26-854a0f3266ca diff --git a/src/exercises/ode_model_mtk_intro.jl b/src/exercises/ode_model_mtk_intro.jl new file mode 100644 index 00000000..882e581d --- /dev/null +++ b/src/exercises/ode_model_mtk_intro.jl @@ -0,0 +1,640 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "1" +#> title = "1. ODE_model_MTK_intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Introduction to ModelingToolkit" + +using Markdown +using InteractiveUtils + +# ╔═╡ 1c46508e-2354-11f0-08db-d373e43929bd +using Pkg; Pkg.activate("..") + +# ╔═╡ 2d125aad-334c-4210-a921-1acacefc5cfa +using StatsPlots, PlutoUI; TableOfContents() +# packages needed for example to make plots + +# ╔═╡ d0f32979-01f6-4f07-acb3-627077c7c029 +using OrdinaryDiffEq, ModelingToolkit +# packages needed to solve differential equations with ModelingToolkit + +# ╔═╡ 43c4f402-45a9-48d5-b1c6-8a6ea8107ce2 +using ModelingToolkit: t_nounits as t, D_nounits as D +# t will be the symbol for the time +# D will be the differentiation operator + +# ╔═╡ f998d6c0-0791-4484-b088-a810d6e89a40 +md""" +# Introduction to ModelingToolkit (ODE) +""" + +# ╔═╡ 8f48f7a2-c9d7-4293-9966-4d52cc4e6b1e +TableOfContents() # displays that table of contents on the right + +# ╔═╡ ee985cd8-86d1-462a-b4f1-3c25be215b57 +md""" +**ModelingToolkit.jl** is a Julia package for **symbolic and numeric modeling of complex systems**, such as those described by for example **differential equations** and **algebraic equations**. It’s part of the **SciML ecosystem** (Scientific Machine Learning). + +At its core, it lets you: + +1. **Define models symbolically** – You describe variables, parameters, and equations symbolically, rather than writing them out procedurally. +2. **Automatically generate efficient code** – The toolkit simplifies, differentiates, and compiles your model into optimized Julia code for simulation or analysis. +3. **Compose models** – You can build complex systems by connecting smaller subsystems (useful in fields like biology, mechanics, or electrical circuits). +4. **Perform advanced analyses** – It supports symbolic simplification, Jacobian and sensitivity computation, structural analysis, and automatic differentiation. + +In short, **ModelingToolkit** bridges the gap between symbolic math and high-performance simulation — enabling you to describe models declaratively, and then solve them efficiently using Julia’s differential equation solvers. + +This is a barebones tutorial to ModelingToolkit, helping you to build simple models based on ODEs. See the [documentations](https://docs.sciml.ai/ModelingToolkit/stable/) for more details. + +Below we will illustrate the use of ModelingToolkit for three different cases. +""" + +# ╔═╡ f4db6481-80c9-4d56-b5a4-50e620d95143 +md""" +## Case 1: Conical tank + +![](https://i.ibb.co/7xYxhD7f/conical-tank.png) +""" + +# ╔═╡ 5c1b77bb-5ef3-4434-a66d-89c86ca30216 +md""" +As the figure here above illustrates, a conical tank with height $H$ and base radius $R$ has a constant inlet flow rate $q_{in}=q$. At the bottom, the outlet flow rate is proportional (proportionality factor: $k$) to the height $h$ of the liquid in the tank. At the liquid's surface level in the tank its radius is denoted as $r$. Hence, the liquid's volume is $V = \frac{1}{3}r^2 h \pi$. It also hold that $r=\frac{Rh}{H}$. The rate of change in $V$ is given by the following differential equation: +``` math +\cfrac{d V(t)}{dt} = q_{in} - q_{out} = q - k h(t) +``` +We are not going to substitute $r$ in the equation for the volume and/or simplify $\frac{dV(t)}{dt}$. Instead, we are going to define the relevant variables that change over time and define their relation to eachother. +""" + +# ╔═╡ e12df3dd-f896-4713-9328-551c949c34b1 +md""" +### Defining variables and parameters +""" + +# ╔═╡ 2c3e60ef-f738-43b4-bf14-0d723a389f09 +md""" +We define variables for the height $h$, the radius $r$ and the volume $V$ as follow. Mind that you need to explicitely write their dependency on the time $t$. Here we have also provided a default initial value (initial condition) for $V$. *This is optional as later when creating the ODE problem you can overwrite these values*. The initial values for $h$ and $r$ will be calculated when solving the problem. +""" + +# ╔═╡ 41af0644-4611-4880-950d-0b6ff2c58a15 +@variables h(t) r(t) V(t)=0.1 + +# ╔═╡ ea09ad52-79f6-43c1-9389-e8efe2c73e21 +md""" +We define the parameters. Optionally, as with the variables, you can also provide default values for the parameters and/or overwrite them later when creating the ODE problem. Here we have provided default parameter values for $H$ and $R$. +""" + +# ╔═╡ 38602497-c591-4e22-af60-f9622096067f +@parameters k q H=5.0 R=2.0 + +# ╔═╡ c6f8bea8-948e-42f8-9730-d36d0fc3adb0 +md""" +### Defining the equations +""" + +# ╔═╡ 33d63418-855c-4ce9-98d9-df8e0184e752 +md""" +Now we will define all necessary equations. +""" + +# ╔═╡ 638e2962-115d-4b95-947e-ace3f2495bc7 +md""" +We define the equation that relates $r$ to $h$. The equation is named `eq_radius`. You can choose an arbitrary name but use one that makes sense. Mind that between the lefthandside and the righthandside a `~` (tilde) is used and not a `=` (equal sign). The `=` is only used to *name* the equation. Furthermore, write the name of the variables without explicitely stating their dependency on the time. +""" + +# ╔═╡ f98ecb14-9df2-4ba3-a1c7-3ab75e3e8055 +eq_radius = r ~ h * R / H + +# ╔═╡ 3c4ac6b0-08d2-4f93-bd98-b52a03e9af7d +md""" +Next, we define the equation that relates $V$ to $r$ and $h$. +""" + +# ╔═╡ c18b1610-eadf-474e-8da9-9c1f21845bc1 +eq_volume = V ~ π * r^2 * h / 3 + +# ╔═╡ f6cb6648-95bd-4a8e-85ad-626b97bb6cb4 +md""" +Finally, we will define the differential equation that relates the rate of change in $V$ to the inlet flow rate and the outlet flow rate. The symbolic operator `D` takes the symbolic differentiation of `V` in `D(V)`. +""" + +# ╔═╡ e87ea53c-8629-4f49-af3b-1c753940e27d +change_vol = D(V) ~ q - h * k # change in volume = inflow - outflow + +# ╔═╡ 74fc9ede-c032-4d5a-b508-c6de4021f460 +md""" +After all equations have been defined, we will bundle them in a vector using square brackets as below. +""" + +# ╔═╡ 8e6efab4-ac91-40bd-9805-88e0dabb2cd1 +eqns_tank = [eq_radius, eq_volume, change_vol] + +# ╔═╡ 168f79ba-ca24-45af-9ac4-dfa10d48a3d7 +md""" +### Building the MTK system and creating the ODE problem +""" + +# ╔═╡ 9d302a6c-3b1b-47b8-85df-a658baaca050 +md""" +We will build an ODE system for MTK (ModelingToolkit). You need to provide the list of equations (cf. here: `eqns_tank`) and the symbol `t` for the time to the function `ODESystem`. Optionally you can also provide so-called *continuous* and *discrete* events, but this will be illustrated later. The name of the MTK model (or system) given is here `tank`. +""" + +# ╔═╡ c6bfc46c-7ca5-4d9e-88fb-3e7065ec6bb0 +@mtkbuild tank = ODESystem(eqns_tank, t) + +# ╔═╡ c3fce45c-f4d6-49e7-82cf-79a58412bc57 +md""" +After having built the MTK model we need to create the ODE problem. You need to provide the MTK model (cf. here: `tanks`), a vector with the initial conditions, a time span and a vector of parameter values to the function `ODEProblem`. Because $V$, $r$ and $h$ are related to each other, we provide the initial condition for $V$ only and provide guessed values for $r$ and $h$. + +Some remarks: +- Since the initial value for $V$ was set when defining the variables, we could as well have written `[]` instead of `[V=>0.1]` if you want to use the default value. You can overwrite the initial value here if you wish. +- Beware that $V$, $r$ and $h$ are related with eachother and that only one of them must be given an initial value. The other two values need to be 'guessed' (cf. the keyword argument `guesses=...`). +- We have intentionally not specified the parameters `R` and `H` in the vector of parameters, because we want to use their default values. You can overwrite the default parameter values here if you wish. +""" + +# ╔═╡ d6a153d4-d41f-47d4-88b0-b10ac2e22b04 +tank_prob = ODEProblem(tank, [V=>0.1], + (0.0, 100.0), [q=>0.1, k=>0.05], + guesses=[r=>0.1, h=>0.1]) + +# ╔═╡ 816fda45-635b-4354-9437-c2efd7374782 +md""" +### Solving and plotting +""" + +# ╔═╡ f6e0db08-723d-4b55-ac40-6828105ed6a7 +md""" +We will now solve the ODE problem using the function `solve`. The function expects two inputs: an `ODEProblem` and an ODE solver. It is also possible to call `solve` with just the `ODEProblem`, in which case it will try to automatically select a suitable solver for the ODE problem. This function also has many keyword arguments, which you can find in the docs page of the function (type `?solve` in a cell or write in directly in the `🔍 Live docs` in the bottom right corner). One we will use often is `saveat`, which decides at what timesteps the solution will be returned. For example, if you provide the option `saveat=1`, then the solution will be approximated every time unit. +""" + +# ╔═╡ 9492a458-2e7c-4828-85a3-20ed266e794c +tank_sol = solve(tank_prob) +# tank_sol = solve(tank_prob, saveat=1) + +# ╔═╡ 61e22259-4a15-44cc-ab2b-e21030502135 +md""" +If you want to check the number of time instances at which the solution was approximated, you can use the function `length`: +""" + +# ╔═╡ 66947904-6b80-4279-ae2b-a3c20886062d +length(tank_sol) + +# ╔═╡ baf2cc98-0a10-4c30-8d32-66b93b4c37aa +md""" +If you plan on further analyzing the solution for one of the variables, you can access their values by indexing the solution object with the symbolic variable. For example: +""" + +# ╔═╡ 258ad571-6e2d-470f-ae5f-5c5ac34afbab +tank_sol[V] + +# ╔═╡ b5ef6331-0239-4c4c-a7e2-8df1d1077b07 +md"You can also use the variable name as a `Symbol` (created by adding a leading `:`):" + +# ╔═╡ 3240e248-06ad-4720-b0d0-14665a0ee128 +tank_sol[V] == tank_sol[:V] + +# ╔═╡ 9bbaf346-0195-405d-bf3a-8360d49f03ab +md""" +You can plot the evolution of $V$, $h$ and $r$ using the function `plot` and providing the name of the solution object. The keyword argument `idxs` allows you to choose what variables to plot. +""" + +# ╔═╡ 99158cd8-753c-4d6b-9288-a78dfc1fa86a +plot(tank_sol, idxs=[V, r, h]) +# plot(tank_sol) + +# ╔═╡ ce573c77-ff84-446e-9460-1aff19b324f2 +md""" +You can calculate the steady state (equilibrium) values by solving a steady state problem in the way below. The first argument to `SteadyStateProblem` is the name of the MTK system/model, the second argument consists of inital guesses of the steady state values and the third argument consists of the parameter values. +""" + +# ╔═╡ 08a72b39-81c8-4b3a-90a7-761bf6398e80 +equil_val_tank = solve(SteadyStateProblem(tank, [V=>1.0, h=>2.0, r=>0.5], [q=>0.1, k=>0.05])) + +# ╔═╡ 24087790-80c0-41fb-af35-1b49078705e7 +md""" +Then you can retrieve the steady state value of each variable in the following way: +""" + +# ╔═╡ f3afa935-f194-42f0-989c-48593f8ef8eb +equil_val_tank[V] + +# ╔═╡ fb554cd3-8974-4cb2-9ed1-71fdfb52911a +equil_val_tank[h] + +# ╔═╡ 12fc3604-1871-4197-bf95-5674dc9d9afb +md""" +You can also provide an expression to `idxs` consisting of variables and/or parameters. Below, we show the ratio $V/h$. +""" + +# ╔═╡ 8c9e806b-ee8b-4c84-b9bb-113e1d8ab4e4 +plot(tank_sol, idxs=V/h) + +# ╔═╡ b108c4a6-ee94-4773-8b88-fe02b70addfb +md""" +## Case 2: Mass-and-spring system + +![](https://i.ibb.co/MDTGngfq/damped-spring.png) +""" + +# ╔═╡ abda4552-c4d2-4b30-92bf-c17c5bfd1535 +md""" +In this case we will simulate an under-damped harmonic oscillation. In an under-damped harmonic oscillator, the system experiences oscillatory motion while the amplitude gradually decreases over time due to damping. The deviation $y(t)$ describes the displacement of the oscillator from its equilibrium position as a function of time, typically following an exponentially decaying sinusoidal form. The total mechanical energy $E(t)$, composed of both kinetic and potential contributions, also decays exponentially as energy is dissipated by the damping mechanism. Studying $y(t)$ and $E(t)$ provides insight into how the oscillation amplitude and system energy evolve under the influence of damping. + +The motion of an under-damped harmonic oscillator is governed by Newton’s second law, leading to the differential equation +``` math +m y''(t) = -k y(t) - \mu y'(t) +``` +where $m$ is the mass, $\mu$ the damping coefficient, and $k$ the spring constant. The under-damped condition corresponds to $\mu^2 < 4mk$, resulting in oscillatory motion with an exponentially decaying amplitude. We will make sure that the parameter values in this example meets this condition. + +The total mechanical energy is given by +``` math +E(t) = \cfrac{1}{2} m y'(t)^2 + \cfrac{1}{2} k y(t)^2 +``` +and, due to damping, it decreases over time as energy is continuously dissipated by the resistive force. +""" + +# ╔═╡ ab2c91b9-56e8-4935-91a2-06519c2dcf0a +md""" +### Defining variables and parameters +""" + +# ╔═╡ 497fe740-385e-4bd1-b5c6-1203b00eb1ab +md""" +We define variables for the position $y$ and the total energy $E$ as follow. Don't forget to mention the dependency on the time $t$. Here, you could provide a default initial condition for $y$ but this is optional. $E$ doesn't need an initial condition because $E$ will be calculated at each iteration step when $y$ and $y'$ were computed. +""" + +# ╔═╡ a0f5eeee-3b5f-4588-bd31-1cff6c993f6b +@variables y(t) E(t) # position and energy + +# ╔═╡ b1277bf9-9958-4c07-aba1-77a9e5252b20 +md""" +We define the parameters. Optionally, as with the variables, you can also provide default values for the parameters and/or overwrite them later when creating the ODE problem. An other option is to provide an informative *description* string with the meaning of each of the parameters. +""" + +# ╔═╡ 26921be4-4438-4be7-b6c0-d9ea28af0865 +md""" +!!! note + We can't define a parameter `k` here because we already defined one in the previous section, and Pluto doesn't allow variables to be defined multiple times in different places (this guarantees the code returns the same output no matter what order it is run in). Because of this, we call it `k_s` instead. +""" + +# ╔═╡ d3954593-63ae-4dfe-b752-01339b44adcd +@parameters μ [description="friction"] m [description="mass"] k_s [description="spring constant"] + +# ╔═╡ f67ffde7-622a-45bf-b987-5802a7413610 +md""" +### Defining the equations +""" + +# ╔═╡ b195f1cb-c87b-407b-9840-a2e3c3da4fba +md""" +In the differential equation (Newton's second law) we use the second derivative of $y$: $y''=\cfrac{d^2y}{dt^2}$. You can use the following notations to write the symbolic second derivative of $y$: +""" + +# ╔═╡ b4c29986-4d70-4551-bef3-4b79f032c3ce +D(D(y)) # second-order derivative wrt time + +# ╔═╡ 268ab13e-2f15-4b1e-90b7-cbbcf5496163 +(D^2)(y) # second-order derivative wrt time + +# ╔═╡ b9a3e9c0-577b-45ae-82bd-79d33879343f +md""" +We will define the second-order differential equation for the position (i.e. the equation based on Newton’s second law). +""" + +# ╔═╡ ed48e70a-a608-46a1-9339-0bb3a93a4ea1 +# spring_eq = m * D(D(y)) ~ - k_s * y - μ * D(y) +eq_spring = m * (D^2)(y) ~ - k_s * y - μ * D(y) + +# ╔═╡ caada693-9aca-41ac-bd37-bc3fee641125 +md""" +Next, we will define the expression for the total energy of the system. +""" + +# ╔═╡ dc7e26c6-2c88-475d-a4ab-64dbb4f9a956 +exp_energy = E ~ m*D(y)^2/2 + y^2*k_s/2 # we can add quantities to keep track off. + +# ╔═╡ ecc6d921-ae18-4a25-b2f8-3636ae8fc85c +md""" +### Building the MTK system and creating the ODE problem +""" + +# ╔═╡ 3e7eeaec-ee16-4078-829e-61eb9acba0e8 +md""" +We will now build an ODE system for MTK (ModelingToolkit). You will need to provide the vector of equations and the symbol `t` for the time to the function `ODESystem`. In addition we will introduce a discrete event: at time t=`40`, the mass is reduced by 75%. You could image that part of the mass fell from the spring during the movement. The name of the MTK model (or system) given is here `spring_ode`. +If you have multiple discrete events you can include them in the following way: +`[[...]=>[...~...], [...]=>[...~...], ...]`. +""" + +# ╔═╡ 75a3db3f-818a-4cb8-948a-dfbcd0582a45 +@mtkbuild spring_ode = ODESystem([eq_spring, exp_energy], t; discrete_events=[[40]=>[m~(1-0.75)*m]]) + +# ╔═╡ 2bcd3e8f-1915-4247-81a4-f7803c55e252 +md""" +As you can notice, the second order ODE has been converted into a system of two first order ODEs. A new variable `y_t` was introduced such that `y_t` $= \cfrac{dy}{dt}$. This new variable is nothing else than $y'$. +""" + +# ╔═╡ 9746751b-144f-4216-8916-d64dce6ce747 +md""" +After having built the MTK model we need to create the ODE problem. You need to provide the name of the MTK model (cf. here: `spring_ode`), a vector with the initial conditions, a time span and a vector of parameter values to the function `ODEProblem`. Remark that since the second order ODE was converted into a system of two ODEs, you need to provide two initial conditions: one for $y$ and one for $y'$. The symbolic notation for $y'$ is `D(y)` and the latter should be used while creating the ODE problem. +""" + +# ╔═╡ 30b3ed77-0709-4410-8d56-d576d69b5e0b +spring_prob = ODEProblem(spring_ode, [y=>2.0, D(y)=>-1.0], (0.0, 100.), [m=>3, k_s=>0.6, μ=>1e-1]) + +# ╔═╡ 53913925-13d2-4a70-b310-8c19ae628581 +md""" +### Solving and plotting +""" + +# ╔═╡ 85d111eb-5452-43de-a674-a6242e7dc5ca +md""" +We will now solve the ODE problem using the function `solve`. In this case we have provided a solver (cf. `Tsit5()`) and a relative tolerance to be met by the solver (cf. `reltol=1e-9`). The solver `Tsit5()` is a recommended solver for non-stiff problems. See the [documentation](https://docs.sciml.ai/DiffEqDocs/dev/solvers/ode_solve/) for more details. +""" + +# ╔═╡ 15ba5472-72da-4ae4-9438-fe8a1d88d9c0 +sol_spring = solve(deepcopy(spring_prob), Tsit5(), reltol=1e-9) + +# ╔═╡ cf253b87-8a15-4e4e-b372-f427793143bb +md""" +!!! note + When working with events, it is good practice to take a `deepcopy` of the ODE problem before solving it. This is because events can change parameter values of the problem while solving, which do not reset when solving is done. Therefore solving the same problem a second time would use a changed set of initial parameters, and give different results. Copying the problem before solving prevents this issue. +""" + +# ╔═╡ 4b04ccc3-94c4-4331-a781-d02be313ef73 +md""" +You can plot the evolution of $y$ and $y'$ using the function `plot` by just providing the name of the solution object. +""" + +# ╔═╡ d29f022c-924b-4d26-ab8c-65f11eb85b6a +plot(sol_spring) + +# ╔═╡ ab4aca36-5d8a-43b5-9e7a-c9c6f8288fef +md""" +You can clearly notice some change in the oscillatory at t=40. +""" + +# ╔═╡ f954d1ff-d9dd-4ba2-8dc2-a5ccd87e1ce3 +md""" +If you want to see the evolution of $E$, you can provide it to the `idxs` keyword argument. +""" + +# ╔═╡ ff3645ab-12d1-46ac-b4d8-ce431ba27491 +plot(sol_spring, idxs=E) + +# ╔═╡ 27670f6a-9f2c-4768-a1ea-986f9ba5301c +md""" + +## Case 3: Lotka-Volterra + +Classical model for prey-predator relations +""" + +# ╔═╡ 718d6e46-7e5e-4f08-a3c6-f14c9bd64cb2 +md""" +The **Lotka–Volterra model**, also known as the **predator–prey model**, is a pair of first-order, nonlinear differential equations that describe the dynamic interaction between two biological species: one as a prey population and the other as its predator. Developed independently by Alfred J. Lotka and Vito Volterra in the 1920s, the model captures the cyclical nature of population sizes — with predator numbers rising and falling in response to changes in prey abundance, and vice versa. Despite its simplicity, the Lotka–Volterra framework remains a cornerstone of theoretical ecology, providing insight into population oscillations, stability, and the balance of ecosystems. + +In this case we will consider a population of rabbits ($R$) as prey and foxes ($F$) as predators. Suppose that their evolution over time is governed by the following equations: +``` math +\begin{align} +\cfrac{dR}{dt} &= \alpha \left(1 - \cfrac{R}{K}\right) R - \beta R F \\ +\cfrac{dF}{dt} &= \gamma R F - \delta F +\end{align} +``` +The rabbit population grows at rate with $\alpha$ but is limited by a carrying capacity $K$, while predation by foxes reduces it at a rate $\beta$. The fox population increases proportionally to the number of hunts ($\gamma R F$) and declines naturally at rate $\delta$. +""" + +# ╔═╡ 8644c5c2-efe3-42b1-86f9-ef3c3e37a4cd +md""" +### Defining variables and parameters +""" + +# ╔═╡ 4537da11-be37-42d2-b4d8-5e08272e9f65 +md""" +We will define the variables for this model. For the rabbits we will use the symbol 🐰 and for the foxes the symbol 🦊. In order to get the first symbol, type a back slash `\` and then type `:rabbit:` followed by the TAB-key. The second symbol you can get analogously, type `\` and then type `:fox_face:` followed by the TAB-key. If you hit the TAB-key before finishing the name of the symbol, you can see different options. Don't forget to specify the dependence on the time `t`. +""" + +# ╔═╡ 3100925e-d5dc-4d56-af84-ffab2059a4c5 +@variables 🐰(t) 🦊(t) + +# ╔═╡ 454c13d8-6938-4a92-b553-db83d47793c8 +md""" +Next, we will define the parameters. To get the greek letters is similar to getting the rabbit and foxes symbols. For example, to get α, type `\` and then `alpha` followed by the TAB-key. The other ones you can get with `beta`, `gamma` and `delta`. +""" + +# ╔═╡ 298c0f1b-4237-4c17-993b-a2969257790f +@parameters α β γ δ K + +# ╔═╡ 62266eb6-a6b1-404a-926f-870925e9f429 +md""" +### Defining the equations +""" + +# ╔═╡ 087eb724-7f8a-4c99-be7a-5924a449cd29 +md""" +Now we will define the equations. We will readily define and bundle both equations simultanuously and name the vector of equations `LV_eqs`. +""" + +# ╔═╡ 130f2711-9ac8-491f-b580-28fe0889257a +LV_eqs = [ + D(🐰) ~ α * 🐰 * (1 - 🐰 / K) - β * 🐰 * 🦊, + D(🦊) ~ γ * 🐰 * 🦊 - δ * 🦊 +] + +# ╔═╡ 8181f5ca-3774-4cf2-bcf3-24d86774b88e +md""" +### Building the MTK system and creating the ODE problem +""" + +# ╔═╡ 6d10c03d-0795-4458-88a6-f7484efe9478 +md""" +In this example we will introduce a so-called continuous event. The continuous event is formulated here below. It states that when the population of rabbits hits 300, then its population is brought back to 100. You can imagine that if you have enough rabbits, say 300, hunters will hunt the rabbits and bring its population back to 100. In our case there is only one continuous event, but it is possible to include multiple continuous events in the following way: +`[[...~...]=>[...~...], [...~...]=>[...~...], ...]` +""" + +# ╔═╡ 1a920b39-00f6-4455-b226-b6961af5c6d1 +rabbitmanagment = [[🐰 ~ 300.0] => [🐰 ~ 100.0]] + +# ╔═╡ 3f2170d3-be36-415e-8c65-4f492baccac0 +md""" +We will now build an ODE system for MTK. You will need to provide the list of equations, the symbol `t` for the time and the continuous event to the function `ODESystem`. +""" + +# ╔═╡ 1cdf417d-daea-4684-89bd-7deb951c6b3a +@mtkbuild lv_sys = ODESystem(LV_eqs, t; continuous_events=rabbitmanagment) + +# ╔═╡ b7299cae-799b-4840-8501-e935dca06183 +md""" +Next, we will create the ODE problem by providing the name of the MTK system, the initial conditions for 🐰 and 🦊, the time span and the parameter values. +""" + +# ╔═╡ 08bfcddc-703f-4ef4-a44e-6386eb91de76 +# lv_prob = ODEProblem(lv_sys, [🐰=>1.0, 🦊=>1e-2], (0, 100), [α=>0.6, β=>0.6, γ=>0.04, δ=>0.5, K=>1000]) +lv_prob = ODEProblem(lv_sys, [🐰=>100, 🦊=>4], (0.0, 100.0), [α=>0.6, β=>0.016, γ=>0.004, δ=>0.6, K=>1000]) + +# ╔═╡ f5d834ca-e79a-4f96-bebf-049171c625a4 +md""" +### Solving and plotting +""" + +# ╔═╡ 1eebace8-9332-4555-b6a9-1f386059816b +md""" +We will now solve the ODE problem using the function `solve`. In this case we have provided a solver (cf. `Tsit5()`) and a relative tolerance to be met by the solver (cf. `reltol=1e-9`). Also notice the `deepcopy` of the ODE problem when dealing with events. +""" + +# ╔═╡ 5c927b82-1133-4508-996a-7ed7111cc4d5 +sol_LV = solve(deepcopy(lv_prob), Tsit5(), reltol=1e-9) + +# ╔═╡ d57502bc-46e2-4268-a8cc-09ccb8f7c21d +md""" +You can plot the evolution of 🐰 and 🦊 using the function `plot` by just providing the name of the solution object. You can clearly see that when 🐰 hits 300, its population is brought back to 100. This happens three times. In this period of time the population of 🦊 has grown in a way that the 🐰 cannot reach a population of 300 anymore. Instead, both populations go to steady state values. + +We have provided written labels this time because the fancy symbols don't come through in the legend of the plot. +""" + +# ╔═╡ 9db55fe4-9f67-4d23-85ec-fbafe21f09a7 +plot(sol_LV, label=["rabbits" "foxes"]) + +# ╔═╡ a218fb57-a64b-47ca-b24a-df1a549aaa9c +md""" +Here below we calculate the steady state values for the rabbits and the foxes. +""" + +# ╔═╡ a6de7f90-5349-44db-b681-dc35433344e2 +equil_val_LV = solve(SteadyStateProblem(lv_sys, [🐰=>100, 🦊=>40], [α=>0.6, β=>0.016, γ=>0.004, δ=>0.6, K=>1000])) + +# ╔═╡ 055e5609-ab93-483a-a494-d3587802c92b +md""" +The steady state value of the rabbits: +""" + +# ╔═╡ 3b3d890f-7648-4d8e-abfb-bb1ef386bbea +equil_val_LV[🐰] + +# ╔═╡ 68b6c3ea-9093-49bb-8e4f-b4f9544b1ad6 +md""" +The steady state value of the foxes: +""" + +# ╔═╡ 43a0eb8c-af3d-4cc3-bc5a-18a4ce121a40 +equil_val_LV[🦊] + +# ╔═╡ 8d7f21e3-6512-4fc2-a290-82b7bbeefa7b +md""" +You can also plot the ratio of the rabbits over the foxes in the following way. +""" + +# ╔═╡ cc65899f-f16c-4194-b2d8-bcfe3dcbd8c8 +plot(sol_LV, idxs=🐰/🦊, label="rabbits/foxes") + +# ╔═╡ Cell order: +# ╟─f998d6c0-0791-4484-b088-a810d6e89a40 +# ╠═1c46508e-2354-11f0-08db-d373e43929bd +# ╠═2d125aad-334c-4210-a921-1acacefc5cfa +# ╠═d0f32979-01f6-4f07-acb3-627077c7c029 +# ╠═43c4f402-45a9-48d5-b1c6-8a6ea8107ce2 +# ╠═8f48f7a2-c9d7-4293-9966-4d52cc4e6b1e +# ╟─ee985cd8-86d1-462a-b4f1-3c25be215b57 +# ╟─f4db6481-80c9-4d56-b5a4-50e620d95143 +# ╟─5c1b77bb-5ef3-4434-a66d-89c86ca30216 +# ╟─e12df3dd-f896-4713-9328-551c949c34b1 +# ╟─2c3e60ef-f738-43b4-bf14-0d723a389f09 +# ╠═41af0644-4611-4880-950d-0b6ff2c58a15 +# ╟─ea09ad52-79f6-43c1-9389-e8efe2c73e21 +# ╠═38602497-c591-4e22-af60-f9622096067f +# ╟─c6f8bea8-948e-42f8-9730-d36d0fc3adb0 +# ╟─33d63418-855c-4ce9-98d9-df8e0184e752 +# ╟─638e2962-115d-4b95-947e-ace3f2495bc7 +# ╠═f98ecb14-9df2-4ba3-a1c7-3ab75e3e8055 +# ╟─3c4ac6b0-08d2-4f93-bd98-b52a03e9af7d +# ╠═c18b1610-eadf-474e-8da9-9c1f21845bc1 +# ╟─f6cb6648-95bd-4a8e-85ad-626b97bb6cb4 +# ╠═e87ea53c-8629-4f49-af3b-1c753940e27d +# ╟─74fc9ede-c032-4d5a-b508-c6de4021f460 +# ╠═8e6efab4-ac91-40bd-9805-88e0dabb2cd1 +# ╟─168f79ba-ca24-45af-9ac4-dfa10d48a3d7 +# ╟─9d302a6c-3b1b-47b8-85df-a658baaca050 +# ╠═c6bfc46c-7ca5-4d9e-88fb-3e7065ec6bb0 +# ╟─c3fce45c-f4d6-49e7-82cf-79a58412bc57 +# ╠═d6a153d4-d41f-47d4-88b0-b10ac2e22b04 +# ╟─816fda45-635b-4354-9437-c2efd7374782 +# ╟─f6e0db08-723d-4b55-ac40-6828105ed6a7 +# ╠═9492a458-2e7c-4828-85a3-20ed266e794c +# ╟─61e22259-4a15-44cc-ab2b-e21030502135 +# ╠═66947904-6b80-4279-ae2b-a3c20886062d +# ╟─baf2cc98-0a10-4c30-8d32-66b93b4c37aa +# ╠═258ad571-6e2d-470f-ae5f-5c5ac34afbab +# ╟─b5ef6331-0239-4c4c-a7e2-8df1d1077b07 +# ╠═3240e248-06ad-4720-b0d0-14665a0ee128 +# ╟─9bbaf346-0195-405d-bf3a-8360d49f03ab +# ╠═99158cd8-753c-4d6b-9288-a78dfc1fa86a +# ╟─ce573c77-ff84-446e-9460-1aff19b324f2 +# ╠═08a72b39-81c8-4b3a-90a7-761bf6398e80 +# ╟─24087790-80c0-41fb-af35-1b49078705e7 +# ╠═f3afa935-f194-42f0-989c-48593f8ef8eb +# ╠═fb554cd3-8974-4cb2-9ed1-71fdfb52911a +# ╟─12fc3604-1871-4197-bf95-5674dc9d9afb +# ╠═8c9e806b-ee8b-4c84-b9bb-113e1d8ab4e4 +# ╟─b108c4a6-ee94-4773-8b88-fe02b70addfb +# ╟─abda4552-c4d2-4b30-92bf-c17c5bfd1535 +# ╟─ab2c91b9-56e8-4935-91a2-06519c2dcf0a +# ╟─497fe740-385e-4bd1-b5c6-1203b00eb1ab +# ╠═a0f5eeee-3b5f-4588-bd31-1cff6c993f6b +# ╟─b1277bf9-9958-4c07-aba1-77a9e5252b20 +# ╟─26921be4-4438-4be7-b6c0-d9ea28af0865 +# ╠═d3954593-63ae-4dfe-b752-01339b44adcd +# ╟─f67ffde7-622a-45bf-b987-5802a7413610 +# ╟─b195f1cb-c87b-407b-9840-a2e3c3da4fba +# ╠═b4c29986-4d70-4551-bef3-4b79f032c3ce +# ╠═268ab13e-2f15-4b1e-90b7-cbbcf5496163 +# ╟─b9a3e9c0-577b-45ae-82bd-79d33879343f +# ╠═ed48e70a-a608-46a1-9339-0bb3a93a4ea1 +# ╟─caada693-9aca-41ac-bd37-bc3fee641125 +# ╠═dc7e26c6-2c88-475d-a4ab-64dbb4f9a956 +# ╟─ecc6d921-ae18-4a25-b2f8-3636ae8fc85c +# ╟─3e7eeaec-ee16-4078-829e-61eb9acba0e8 +# ╠═75a3db3f-818a-4cb8-948a-dfbcd0582a45 +# ╟─2bcd3e8f-1915-4247-81a4-f7803c55e252 +# ╟─9746751b-144f-4216-8916-d64dce6ce747 +# ╠═30b3ed77-0709-4410-8d56-d576d69b5e0b +# ╟─53913925-13d2-4a70-b310-8c19ae628581 +# ╟─85d111eb-5452-43de-a674-a6242e7dc5ca +# ╠═15ba5472-72da-4ae4-9438-fe8a1d88d9c0 +# ╟─cf253b87-8a15-4e4e-b372-f427793143bb +# ╟─4b04ccc3-94c4-4331-a781-d02be313ef73 +# ╠═d29f022c-924b-4d26-ab8c-65f11eb85b6a +# ╟─ab4aca36-5d8a-43b5-9e7a-c9c6f8288fef +# ╟─f954d1ff-d9dd-4ba2-8dc2-a5ccd87e1ce3 +# ╠═ff3645ab-12d1-46ac-b4d8-ce431ba27491 +# ╟─27670f6a-9f2c-4768-a1ea-986f9ba5301c +# ╟─718d6e46-7e5e-4f08-a3c6-f14c9bd64cb2 +# ╟─8644c5c2-efe3-42b1-86f9-ef3c3e37a4cd +# ╟─4537da11-be37-42d2-b4d8-5e08272e9f65 +# ╠═3100925e-d5dc-4d56-af84-ffab2059a4c5 +# ╟─454c13d8-6938-4a92-b553-db83d47793c8 +# ╠═298c0f1b-4237-4c17-993b-a2969257790f +# ╟─62266eb6-a6b1-404a-926f-870925e9f429 +# ╟─087eb724-7f8a-4c99-be7a-5924a449cd29 +# ╠═130f2711-9ac8-491f-b580-28fe0889257a +# ╟─8181f5ca-3774-4cf2-bcf3-24d86774b88e +# ╟─6d10c03d-0795-4458-88a6-f7484efe9478 +# ╠═1a920b39-00f6-4455-b226-b6961af5c6d1 +# ╟─3f2170d3-be36-415e-8c65-4f492baccac0 +# ╠═1cdf417d-daea-4684-89bd-7deb951c6b3a +# ╟─b7299cae-799b-4840-8501-e935dca06183 +# ╠═08bfcddc-703f-4ef4-a44e-6386eb91de76 +# ╟─f5d834ca-e79a-4f96-bebf-049171c625a4 +# ╟─1eebace8-9332-4555-b6a9-1f386059816b +# ╠═5c927b82-1133-4508-996a-7ed7111cc4d5 +# ╟─d57502bc-46e2-4268-a8cc-09ccb8f7c21d +# ╠═9db55fe4-9f67-4d23-85ec-fbafe21f09a7 +# ╟─a218fb57-a64b-47ca-b24a-df1a549aaa9c +# ╠═a6de7f90-5349-44db-b681-dc35433344e2 +# ╟─055e5609-ab93-483a-a494-d3587802c92b +# ╠═3b3d890f-7648-4d8e-abfb-bb1ef386bbea +# ╟─68b6c3ea-9093-49bb-8e4f-b4f9544b1ad6 +# ╠═43a0eb8c-af3d-4cc3-bc5a-18a4ce121a40 +# ╟─8d7f21e3-6512-4fc2-a290-82b7bbeefa7b +# ╠═cc65899f-f16c-4194-b2d8-bcfe3dcbd8c8 diff --git a/src/exercises/ode_model_soil_cont_plant_uptake.jl b/src/exercises/ode_model_soil_cont_plant_uptake.jl deleted file mode 100644 index ca77cb7b..00000000 --- a/src/exercises/ode_model_soil_cont_plant_uptake.jl +++ /dev/null @@ -1,187 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "8" -#> title = "EXTRA. ODE soil" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of soil" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 6c7911b4-fec2-4139-8b54-36a4fb5916a0 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 121df656-f57a-11ee-140e-dfb61e112370 -using Markdown - -# ╔═╡ 74a1a82f-8c30-45e5-a2b7-1c1ce4d5c523 -using InteractiveUtils - -# ╔═╡ 895c1016-a8ce-43ae-8094-5d6ea75a6053 -using Catalyst - -# ╔═╡ 28e9e96c-fce6-4507-97c9-37337a0731bc -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 2f0c3dd4-9429-4f5b-9150-011970b003f4 -md""" -# Exercise: Soil Contamination with Plant Uptake -""" - -# ╔═╡ c0c35547-9eed-428b-b513-4b5166decb7e -md""" -The following system of differential equations models the decay of a pollutant in soil and its uptake by plants. The variable $C(t)$ (in $mg/kg$) is the concentration of the pollutant in the soil and $P(t)$ (in $mg/kg$) is the concentration of the pollutant in the plants at time $t$. - -$$\begin{align} -\cfrac{dC}{dt} &= r-k_1 C(t)-k_2 C(t) P(t)\\ -\cfrac{dP}{dt} &= k_2 C(t) P(t)-k_3 P(t) -\end{align}$$ - -The interpratation of the parameters is the following: - -- $r$ represents the rate at which the pollutant enters the soil from external sources. -- $k_1$ is the natural degradation rate of the pollutant in the soil. -- $k_2$ is the uptake coefficient, representing the rate at which plants absorb pollutant from the soil. -- $k_3$ is the natural degradation rate of the pollutant in the plant. - -The natural degradation of the pollutant in the soil or plant could be accounted for by processes like radiation decay, microbial degradation, volatilization, or leaching. -""" - -# ╔═╡ 34e7090b-66c6-4e07-a2ad-b4f82a3669a0 -md""" -Model the aforementioned system of differential equations using a *reaction network object*. Name it `soil_cont_plant_uptake`. -""" - -# ╔═╡ ba6c2c9b-6ba9-48b8-9137-6b43813815ec -# Uncomment and complete the instruction -# soil_cont_plant_uptake = @reaction_network begin -# missing -# end - -# ╔═╡ 16402872-3590-44dd-922f-1846640c92fa -md""" -Convert the system to a symbolic differential equation model and verify that you get the same system of differential equations as given in the problem. -""" - -# ╔═╡ 1a7c3080-5773-44b9-a5c5-bb16f25048a3 -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 7c4767ac-ee72-4f46-8699-68f4bfb15d92 -md""" -Suppose that we simulate the evoluation of the pollutant in the soil and plant during $400$ days. The inital pollutant concentrations in the soil and plant both have the value of $0.001\;mg/kg$. In the simulation, the soil is being contaminated at a rate $0.06\;mg/(kg \cdot day)$. The degradation rates and uptake coefficient have the following values: $k_1 = 4.1 \times 10^{-3}$, $k_2 = 1.9 \times 10^{-2}$, $k_3 = 2.2 \times 10^{-2}$. There units are consistent with the units of the aforementioned values. -""" - -# ╔═╡ 340328cc-78ed-4c6c-a0bf-73fd1be21d21 -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ 9d4fc31d-32b4-49c7-9e4c-577530199513 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 51ffec7c-6033-47a9-b65a-5f9a7ab96fb9 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 20b43337-58fd-4b23-8e4d-c4fd8234bb5f -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ ad63f799-bc65-4f2c-ba6c-461fa10139d0 -md""" -Initialize a vector `param` with the parameter values: -""" - -# ╔═╡ 01e33f75-fdfe-4983-a3bd-4cf074152390 -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 0f326aa7-044c-4c6f-be71-acf5c032f796 -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ 6f84b532-a718-4d42-9829-91366693b51c -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ ca65797f-a1dd-42dd-992c-ba067932a018 -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=1.0`. Store the solution in `osol`: -""" - -# ╔═╡ 3d66d40f-f627-4268-890f-ab662c0efdd6 -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ 22dc63ad-47d5-45c4-8902-e9d7abc0a4f6 -md""" -Plot the solutions: -""" - -# ╔═╡ 0f920caa-5993-448c-a449-6449feea121a -# missing # Uncomment and complete the instruction - -# ╔═╡ 62c66f8a-6561-4d50-b6d9-4dfc43cef0a8 -md""" -1. Interprate the simulation results (cf. peak in $C$ and increase of $P$) in terms of the used parameter values. -""" - -# ╔═╡ 27c1e08f-5e6a-43e3-b8e3-bba78e093556 -md"- Answer: missing" - -# ╔═╡ 73b63510-e1a1-44a6-8902-67ee383e6582 -md""" -2. How would you modify the basic model to make it a more realistic biological model (cf. hill, monod, ...). -""" - -# ╔═╡ a7068013-ed98-486a-ba59-a57f26d12d1c -md"- Answer: missing" - -# ╔═╡ ad11f590-aa18-45f6-99be-571039ccbbae -md""" -3. What are the units of the parameters k₁, k₂ and k₃? -""" - -# ╔═╡ 1ebbac82-068a-4754-8eec-1afa662feb96 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═121df656-f57a-11ee-140e-dfb61e112370 -# ╠═74a1a82f-8c30-45e5-a2b7-1c1ce4d5c523 -# ╠═6c7911b4-fec2-4139-8b54-36a4fb5916a0 -# ╟─2f0c3dd4-9429-4f5b-9150-011970b003f4 -# ╟─c0c35547-9eed-428b-b513-4b5166decb7e -# ╠═895c1016-a8ce-43ae-8094-5d6ea75a6053 -# ╟─34e7090b-66c6-4e07-a2ad-b4f82a3669a0 -# ╠═ba6c2c9b-6ba9-48b8-9137-6b43813815ec -# ╟─16402872-3590-44dd-922f-1846640c92fa -# ╠═1a7c3080-5773-44b9-a5c5-bb16f25048a3 -# ╠═28e9e96c-fce6-4507-97c9-37337a0731bc -# ╟─7c4767ac-ee72-4f46-8699-68f4bfb15d92 -# ╟─340328cc-78ed-4c6c-a0bf-73fd1be21d21 -# ╠═9d4fc31d-32b4-49c7-9e4c-577530199513 -# ╟─51ffec7c-6033-47a9-b65a-5f9a7ab96fb9 -# ╠═20b43337-58fd-4b23-8e4d-c4fd8234bb5f -# ╟─ad63f799-bc65-4f2c-ba6c-461fa10139d0 -# ╠═01e33f75-fdfe-4983-a3bd-4cf074152390 -# ╟─0f326aa7-044c-4c6f-be71-acf5c032f796 -# ╠═6f84b532-a718-4d42-9829-91366693b51c -# ╟─ca65797f-a1dd-42dd-992c-ba067932a018 -# ╠═3d66d40f-f627-4268-890f-ab662c0efdd6 -# ╟─22dc63ad-47d5-45c4-8902-e9d7abc0a4f6 -# ╠═0f920caa-5993-448c-a449-6449feea121a -# ╟─62c66f8a-6561-4d50-b6d9-4dfc43cef0a8 -# ╟─27c1e08f-5e6a-43e3-b8e3-bba78e093556 -# ╟─73b63510-e1a1-44a6-8902-67ee383e6582 -# ╟─a7068013-ed98-486a-ba59-a57f26d12d1c -# ╟─ad11f590-aa18-45f6-99be-571039ccbbae -# ╟─1ebbac82-068a-4754-8eec-1afa662feb96 diff --git a/src/exercises/ode_model_tank_h_mtk.jl b/src/exercises/ode_model_tank_h_mtk.jl new file mode 100644 index 00000000..385edacd --- /dev/null +++ b/src/exercises/ode_model_tank_h_mtk.jl @@ -0,0 +1,526 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "4" +#> title = "1. ODE_model_tank_h_mtk" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "modeling the height of the water in a tank" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ e63a0a5a-954d-11f0-1ed0-a3fcaefba117 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 1fe30acc-26dd-45e7-9264-1571c47d07c1 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 7d45ffff-e093-41c4-a0e3-50ebdba5f816 +using OrdinaryDiffEq + +# ╔═╡ d6f533fd-e593-4fec-8c87-53a157eeed48 +using ModelingToolkit + +# ╔═╡ 9c093210-cc9f-40a1-8e35-48f58a7fa491 +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 23d4f233-1305-47a3-8b8f-89873765d7bc +md""" +# Exercise: Cylindrical tank (water level) +""" + +# ╔═╡ d99457cc-8223-4970-b52d-45d42bf7f030 +solution(text) = Markdown.MD(Markdown.Admonition("hint", "Solution", [text])); + +# ╔═╡ 16d75e68-81b2-4c64-a25f-d2e1381a25d6 +md""" +![](https://users.ugent.be/~gvhaelew/fig/tank_height.png) +""" + +# ╔═╡ eee09dae-82eb-4e3b-98b3-daad57c42d49 +md""" +## Deriving the model equations +""" + +# ╔═╡ e638ef6e-00c3-4666-86d8-5803c21abe64 +md""" +A cylindrical tank is filled with water (its density is denoted $\rho$) with a constant flow rate of $Q_{in}$. The outlet flow rate $Q_{out}$ depends on the square root of the water level (height $h$) in the tank in the following manner: +``` math +Q_{out} = \cfrac{\sqrt{h}}{R} +``` +where $R$ is the resistence coefficient of the orifice. The cross section $A$ of the tank is constant, hence, the mass $M$ of water in the tank only varies with the height of the water: +``` math +M = \rho\,V = \rho\,A\,h +``` +""" + +# ╔═╡ cab795f1-f477-4d02-8428-3d26778b49ae +md""" +!!! task + Derive as an exercise a single model equation for the height $h$ of the water inside the tank. +""" + +# ╔═╡ 5ab3e018-e59c-4b80-ab19-3d2368a21bb5 +md""" +!!! hint + - Start by setting up the rate of change of the mass of water in the tank. + ``` math + \cfrac{dM}{dt} = \cdots + ``` + work toward: + ``` math + \cfrac{dh}{dt} = \cdots + ``` + +""" + +# ╔═╡ d850e7ac-dfdf-4ccb-aeaf-01cd5ae0d8d9 +solution(md""" +``` math +\cfrac{dh}{dt} = \cfrac{Q_{in}}{A} - \cfrac{\sqrt{h}}{AR} +``` +""") + +# ╔═╡ b37112ed-2436-42ba-a576-5bca422a0e27 +md""" +Apart from the height of the water, we are also interested in the hydrostatic pressure $p$ (in $bar$, note that $1\;Pa = 10^{-5}\;bar$) at the bottom of the tank: +``` math +p = \rho\,g\,h +``` +where $g$ is the gravitational constant. +""" + +# ╔═╡ 3ae63528-8a78-4884-85f6-1c6dc3ae178e +md""" +Define variables for the height $h$ of the water in the tank and the hydrostatic pressure $p$. Name them `h` and `p`. Mention the dependency on the time $t$. +""" + +# ╔═╡ fac7ad9e-a4a2-4f7c-90f1-0111f9b3639c +# @variables missing + +# ╔═╡ 1df30e1f-190e-4e26-9133-152afc60119d +md""" +Define the parameters for this model and assign their corresponding default values. + +| Parameter | Value | Unit | Meaning | +|:---------- |:---------- |:------------|:------------| +| $A$ | 0.152 | $m^2$ | cross sectional area | +| $\rho$ | 1000.0 | $kg/m^3$ | water density | +| $g$ | 9.81 | $m/s^2$ | gravitational constant | +| $Q_{in}$ | | $m^3/s$ | inlet flow | +| $R$ | | $s/m^{5/2}$ | res. coeff. orifice | + +The values of $Q_{in}$ and $R$ will be set later when creating the ODE problem using values from sliders. +""" + +# ╔═╡ b12868c1-85ef-4a51-b146-ea7be5f119f6 +# @parameters missing + +# ╔═╡ b4de5802-7cd0-496f-be3d-816511ff0281 +md""" +## Part 1: filling up +""" + +# ╔═╡ 29b1088f-4675-4538-bb78-26cfadc6ce28 +md""" +### Setting up the equations +""" + +# ╔═╡ b22c45f3-bfa8-4715-97bc-4b34affe99be +md""" +In this part we will analyze the height of the water $h$ over time for different values of the initial height of the water $h_0$, the inlet flow $Q_{in}$ and the resistance coefficient $R$ of the orifice. The latter three will be set using values from sliders (see below, just above the plot of the variables). +""" + +# ╔═╡ e5b1b418-8fba-4c58-aa8d-8a3b0e1b4e05 +md""" +Set up the equation for the rate of change in height. Multiply the term $-\cfrac{\sqrt{h}}{R}$ with $(h > 0)$. *Think about why this must be done.* +""" + +# ╔═╡ 4c14b63b-8ba3-4df3-b41b-8c5e02be8b45 +change_height = missing + +# ╔═╡ be764668-1a10-4e79-9cc0-585eeb25caf0 +md""" +Set up the equation that will keep track of the hydrostatic pressure. Don't forget to include `*1e-5` in the term in order to have it in $bar$. +""" + +# ╔═╡ 4d24b00a-ec24-4fe9-a31d-472904f2d64e +eq_pressure = missing + +# ╔═╡ f3acc97b-e501-4797-9334-fd3fb7197cbe +md""" +Bundle the equations. +""" + +# ╔═╡ 25973753-4374-41ae-a391-87c750289653 +eqns_tank = missing + +# ╔═╡ a54ebeb7-cd9d-4eac-ab2d-f516758fc045 +md""" +### Building the ODE system +""" + +# ╔═╡ a9ef1b26-e3be-45e9-8604-415dfb885a77 +md""" +Build the model. Name it `sys1_tank`. +""" + +# ╔═╡ 81069e6e-0785-41c2-bbcd-4b9bc25930e0 +# @mtkbuild missing + +# ╔═╡ 29e4b87b-00ee-4dfe-a0a8-2dbd18554f11 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ 98cf7e7c-c52a-49d8-8d00-5d3d85582c50 +md""" +Create the ODE problem. Use the variables `h₀`, `QinLmin` and `R_val` to set the initial height, the inlet flow and the resistance coefficient, respectively. + +**Important remarks:** +- The value of `QinLmin` is in liters per minute ($L/min$). Hence, you need to multiply it with `1e-3/60` to have it in $m^3/s$ before assigning it to `Qin`. +- You can use the default values for the parameters `A`, `ρ` and `g`, which were defined earlier. +""" + +# ╔═╡ 6782c5da-c3ce-4101-bf42-ed0849024162 +oprob1_tank = missing + +# ╔═╡ 401cb8fe-4b56-40ef-a763-741867c55685 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=1`. +""" + +# ╔═╡ 44878548-9661-421e-a9ab-b82ca92a6310 +sol1_tank = missing + +# ╔═╡ e9ae80c7-7554-42dd-8cda-8a0bcb1f0a5a +md""" +### Plotting results +""" + +# ╔═╡ 5ee777ac-71a2-449d-a678-ffe0da09287c +@bind h₀ Slider(0:0.1:2.5, default=0, show_value=true) + +# ╔═╡ a98352c7-9580-4c94-b250-1a1f24d78771 +@bind QinLmin Slider(0:10:120, default=100, show_value=true) + +# ╔═╡ 533b1c79-3408-48d0-b3ee-04ec05e89b65 +@bind R_val Slider(400:10:800, default=600, show_value=true) + +# ╔═╡ 4358d80a-8240-41b5-acfc-d4c0b1b22876 +md""" +Plot the height and the pressure. Fix the limits of the y-axis to $[0.0, 2.6]$ by specifying `ylim=(0.0, 2.6)`. Play with the sliders above to see their effect. +""" + +# ╔═╡ 8b15f793-3628-486a-8fd3-c590db3bda7e +missing + +# ╔═╡ 8da6d0e4-01aa-428a-b1e8-e951087115f6 +md""" +!!! questions + 1. On what parameters does the equilibrium value for the height depend? + 2. Can you calculate this equilibrium value analytically using the equation for the rate of change in height? How does the expression look like? + 3. To what values do you need to set `h₀` and `Qin` in order to completely drain the tank? +""" + +# ╔═╡ 474e479c-757e-498f-b40c-e7e8a3839770 +md""" +Answers: +1. missing +2. missing +3. missing +""" + +# ╔═╡ c634e6c9-78c3-4501-91ea-b5905ef8f26b +md""" +### Calculating the steady state value +""" + +# ╔═╡ 0e2f2d97-3091-41e2-ae02-2e8307fb6795 +md""" +Calculate the steady state value using `SteadyStateProblem`. Use `1.0` as a first guess for $h$. Make this calculation only when `QinLmin` is greater than $30\;L/min$. +""" + +# ╔═╡ 0308b949-5376-4d4e-9c2e-c4c0e5120991 +equil_val = missing + +# ╔═╡ 9a3c9576-a3cc-46a3-911f-906e7a019e9b +md""" +Display the steady state value. +""" + +# ╔═╡ 91066873-7530-44d5-8fac-e508066e9b1a +missing + +# ╔═╡ c3263780-eaf9-43fb-ba7d-1496cf59b0fe +md""" +## Part 2: filling up and draining +""" + +# ╔═╡ bca47fff-a618-4fb2-a059-2b98fd1632a0 +md""" +In this part the tank will be filled up to a certain height and then completely drained. In order to achieve this, we will use a continuous event. +""" + +# ╔═╡ 5559a586-59f2-470a-9bf8-efba2ce42270 +md""" +The tank is initially empty and filled up with an inlet flow $Q_{in}$ of $100$ $L/min$. Take $600$ for the value of $R$. The continuous event should state that when $h$ becomes $0.99$, the value of $Q_{in}$ should be set to $0$. +""" + +# ╔═╡ 5bd86c38-0338-4bd1-bdc7-a7c494b1f17a +md""" +### Building the ODE system +""" + +# ╔═╡ 618590fc-7fb9-4020-858b-d9b2d43fbd70 +md""" +Build the new model that includes the continuous event. Name it `sys2_tank`. +""" + +# ╔═╡ 0a7dc8af-e7d2-40fd-8c60-d266e9d6c0b7 +# @mtkbuild missing + +# ╔═╡ 919b6c52-eba1-4a20-a1be-6cd1539a8164 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ 84f7c85f-08f6-4ba0-bf80-fddf86506143 +md""" +Create the new ODE problem. Set the correct values for the initial height and the parameters. The simulation time is the same as in Part 1. +""" + +# ╔═╡ 3c9acd41-8451-4a13-8dff-f74a41f0bd3c +oprob2_tank = missing + +# ╔═╡ b9c8d9aa-2f9f-487c-87b4-048d4fbe8aa9 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=1`. Don't forget to make a `deepcopy` of the ODE problem. +""" + +# ╔═╡ c53ecb42-cd0a-43a2-b244-22eb6833ea92 +sol2_tank = missing + +# ╔═╡ b52e47a1-d642-4529-97e6-3d3837be657f +md""" +### Plotting results +""" + +# ╔═╡ 333cafcc-c6bb-433c-9f34-fdcf27d59508 +md""" +Plot the height and the pressure. +""" + +# ╔═╡ 0de3c62d-b02e-4f9d-8213-58c4199e2662 +missing + +# ╔═╡ b5196b36-9092-4c37-86a8-ebe5da4e14c1 +md""" +!!! questions + 1. What is the time needed to drain the tank after the height became `0.99`? Derive this (numerically) using the results. + 2. Can you calculate this time also analytically? + 3. Do both numerical and analytical draining times more or less correspond? + + Hints: + - You can you `findfirst(==(x), V)` to find the first index of the value `x` in the vector `V`. + - To slice a vector `V` from a certain index `i` to the `end`, do `V[i:end]`. + - You can use `findfirst(<(0), V)` to find the first index of the value less than `0` in the vector `V`. Don't use `findfirst(==(0), V)` because the numerical values will never be exactly `0` in the tail of the height vector. + +""" + +# ╔═╡ 5d78c408-b10a-4685-8b16-1ee30315aae8 +md""" +Numerical derivation: +""" + +# ╔═╡ ea6725ef-f392-4161-96fa-0da3fcffbb94 +i_max = missing + +# ╔═╡ 1c21b251-7ef4-4f07-a7f1-cf434fdaaac3 +Δi_zero = missing + +# ╔═╡ 704c37e8-ee9e-4ae9-82a1-aa95fefb14d1 +missing + +# ╔═╡ 6a807ca4-6b20-49d4-b095-c249afca6772 +md""" +Analytical calculation (optional): +""" + +# ╔═╡ 66fe0cf1-fcdf-4d4d-b7f1-bbc1b7e558f0 +missing + +# ╔═╡ b4da3154-63ef-486b-8d2a-874f93624884 +md""" +Answers: +1. missing +2. missing +3. missing +""" + +# ╔═╡ d4890a27-dca4-4207-a827-ae1968fa9fc5 +md""" +## Part 3: modifying Qin and R +""" + +# ╔═╡ 39099e15-ed02-47bb-8801-8b59ced0b0db +md""" +In this part the values of $Q_{in}$ and $R$ will be modified at distinct moments in time. In order to achieve this, we will use discrete events. The tank is initially empty and filled up with an inlet flow $Q_{in}$ of $100$ $L/min$. Take $600$ for the value of $R$. + +At the time instant $600$ $s$ the value of $Q_{in}$ will increase by $10\;\%$ (i.e., multiplied by `1.1`). At the time instant `1200` $s$ the value of `R` will be decreased by $25\;\%$ (i.e., multiplied by `0.75`). +""" + +# ╔═╡ 2b68cd82-3c81-4a4f-a27d-e5cb35a7b0f0 +md""" +### Building the ODE system +""" + +# ╔═╡ c1f4c7ab-88b2-4282-8a6f-47f9161951eb +md""" +Build the new model that includes both discrete events. Name it `sys3_tank`. +""" + +# ╔═╡ 07cc0c6d-1514-4d49-bab3-12794e199729 +# @mtkbuild missing + +# ╔═╡ e395c4c4-3761-4b74-9b1d-4e3d32366333 +md""" +### Create and solve the ODE problem +""" + +# ╔═╡ b917ecfb-bda3-453b-a66e-25d83183d5ee +md""" +Create the new ODE problem. Set the correct values for the initial height and the parameters. The simulation time is the same as in Part 1 and 2. +""" + +# ╔═╡ ffe3c2f4-dee4-46bb-9f94-d2a59b621ffc +oprob3_tank = missing + +# ╔═╡ 0a4cebcd-88a9-4193-bce4-60c619e592d4 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=1`. Don't forget to take a `deepcopy` of the ODE problem. +""" + +# ╔═╡ 27f8d05b-94d1-4855-b7ce-33d7995e10d3 +sol3_tank = missing + +# ╔═╡ 8abc6dd9-ad9e-4cc3-a4c8-cf6faa6b3b39 +md""" +### Plotting results +""" + +# ╔═╡ 45479aac-1f8d-4334-a6e5-38bad49131a1 +missing + +# ╔═╡ 9089bc00-7c8e-48a3-b5ed-ec1aa9ec11df +md""" +!!! question + Is the evolution of the height accoding to your intuition? Think about what should happen to the height when you increase $Q_{in}$ and decrease $R$. +""" + +# ╔═╡ 3b8c0bf9-9b2a-4fe5-8cf8-73653d193ca6 +md""" +Answer: missing +""" + +# ╔═╡ Cell order: +# ╟─23d4f233-1305-47a3-8b8f-89873765d7bc +# ╠═e63a0a5a-954d-11f0-1ed0-a3fcaefba117 +# ╠═1fe30acc-26dd-45e7-9264-1571c47d07c1 +# ╠═7d45ffff-e093-41c4-a0e3-50ebdba5f816 +# ╠═d6f533fd-e593-4fec-8c87-53a157eeed48 +# ╠═9c093210-cc9f-40a1-8e35-48f58a7fa491 +# ╟─d99457cc-8223-4970-b52d-45d42bf7f030 +# ╟─16d75e68-81b2-4c64-a25f-d2e1381a25d6 +# ╟─eee09dae-82eb-4e3b-98b3-daad57c42d49 +# ╟─e638ef6e-00c3-4666-86d8-5803c21abe64 +# ╟─cab795f1-f477-4d02-8428-3d26778b49ae +# ╟─5ab3e018-e59c-4b80-ab19-3d2368a21bb5 +# ╟─d850e7ac-dfdf-4ccb-aeaf-01cd5ae0d8d9 +# ╟─b37112ed-2436-42ba-a576-5bca422a0e27 +# ╟─3ae63528-8a78-4884-85f6-1c6dc3ae178e +# ╠═fac7ad9e-a4a2-4f7c-90f1-0111f9b3639c +# ╟─1df30e1f-190e-4e26-9133-152afc60119d +# ╠═b12868c1-85ef-4a51-b146-ea7be5f119f6 +# ╟─b4de5802-7cd0-496f-be3d-816511ff0281 +# ╟─29b1088f-4675-4538-bb78-26cfadc6ce28 +# ╟─b22c45f3-bfa8-4715-97bc-4b34affe99be +# ╟─e5b1b418-8fba-4c58-aa8d-8a3b0e1b4e05 +# ╠═4c14b63b-8ba3-4df3-b41b-8c5e02be8b45 +# ╟─be764668-1a10-4e79-9cc0-585eeb25caf0 +# ╠═4d24b00a-ec24-4fe9-a31d-472904f2d64e +# ╟─f3acc97b-e501-4797-9334-fd3fb7197cbe +# ╠═25973753-4374-41ae-a391-87c750289653 +# ╟─a54ebeb7-cd9d-4eac-ab2d-f516758fc045 +# ╟─a9ef1b26-e3be-45e9-8604-415dfb885a77 +# ╠═81069e6e-0785-41c2-bbcd-4b9bc25930e0 +# ╟─29e4b87b-00ee-4dfe-a0a8-2dbd18554f11 +# ╟─98cf7e7c-c52a-49d8-8d00-5d3d85582c50 +# ╠═6782c5da-c3ce-4101-bf42-ed0849024162 +# ╟─401cb8fe-4b56-40ef-a763-741867c55685 +# ╠═44878548-9661-421e-a9ab-b82ca92a6310 +# ╟─e9ae80c7-7554-42dd-8cda-8a0bcb1f0a5a +# ╠═5ee777ac-71a2-449d-a678-ffe0da09287c +# ╠═a98352c7-9580-4c94-b250-1a1f24d78771 +# ╠═533b1c79-3408-48d0-b3ee-04ec05e89b65 +# ╟─4358d80a-8240-41b5-acfc-d4c0b1b22876 +# ╠═8b15f793-3628-486a-8fd3-c590db3bda7e +# ╟─8da6d0e4-01aa-428a-b1e8-e951087115f6 +# ╠═474e479c-757e-498f-b40c-e7e8a3839770 +# ╟─c634e6c9-78c3-4501-91ea-b5905ef8f26b +# ╟─0e2f2d97-3091-41e2-ae02-2e8307fb6795 +# ╠═0308b949-5376-4d4e-9c2e-c4c0e5120991 +# ╟─9a3c9576-a3cc-46a3-911f-906e7a019e9b +# ╠═91066873-7530-44d5-8fac-e508066e9b1a +# ╟─c3263780-eaf9-43fb-ba7d-1496cf59b0fe +# ╟─bca47fff-a618-4fb2-a059-2b98fd1632a0 +# ╟─5559a586-59f2-470a-9bf8-efba2ce42270 +# ╟─5bd86c38-0338-4bd1-bdc7-a7c494b1f17a +# ╟─618590fc-7fb9-4020-858b-d9b2d43fbd70 +# ╠═0a7dc8af-e7d2-40fd-8c60-d266e9d6c0b7 +# ╟─919b6c52-eba1-4a20-a1be-6cd1539a8164 +# ╟─84f7c85f-08f6-4ba0-bf80-fddf86506143 +# ╠═3c9acd41-8451-4a13-8dff-f74a41f0bd3c +# ╟─b9c8d9aa-2f9f-487c-87b4-048d4fbe8aa9 +# ╠═c53ecb42-cd0a-43a2-b244-22eb6833ea92 +# ╟─b52e47a1-d642-4529-97e6-3d3837be657f +# ╟─333cafcc-c6bb-433c-9f34-fdcf27d59508 +# ╠═0de3c62d-b02e-4f9d-8213-58c4199e2662 +# ╟─b5196b36-9092-4c37-86a8-ebe5da4e14c1 +# ╟─5d78c408-b10a-4685-8b16-1ee30315aae8 +# ╠═ea6725ef-f392-4161-96fa-0da3fcffbb94 +# ╠═1c21b251-7ef4-4f07-a7f1-cf434fdaaac3 +# ╠═704c37e8-ee9e-4ae9-82a1-aa95fefb14d1 +# ╟─6a807ca4-6b20-49d4-b095-c249afca6772 +# ╠═66fe0cf1-fcdf-4d4d-b7f1-bbc1b7e558f0 +# ╠═b4da3154-63ef-486b-8d2a-874f93624884 +# ╟─d4890a27-dca4-4207-a827-ae1968fa9fc5 +# ╟─39099e15-ed02-47bb-8801-8b59ced0b0db +# ╟─2b68cd82-3c81-4a4f-a27d-e5cb35a7b0f0 +# ╟─c1f4c7ab-88b2-4282-8a6f-47f9161951eb +# ╠═07cc0c6d-1514-4d49-bab3-12794e199729 +# ╟─e395c4c4-3761-4b74-9b1d-4e3d32366333 +# ╟─b917ecfb-bda3-453b-a66e-25d83183d5ee +# ╠═ffe3c2f4-dee4-46bb-9f94-d2a59b621ffc +# ╟─0a4cebcd-88a9-4193-bce4-60c619e592d4 +# ╠═27f8d05b-94d1-4855-b7ce-33d7995e10d3 +# ╟─8abc6dd9-ad9e-4cc3-a4c8-cf6faa6b3b39 +# ╠═45479aac-1f8d-4334-a6e5-38bad49131a1 +# ╟─9089bc00-7c8e-48a3-b5ed-ec1aa9ec11df +# ╠═3b8c0bf9-9b2a-4fe5-8cf8-73653d193ca6 diff --git a/src/exercises/ode_model_tractor_seat_mtk.jl b/src/exercises/ode_model_tractor_seat_mtk.jl new file mode 100644 index 00000000..0e9b53a8 --- /dev/null +++ b/src/exercises/ode_model_tractor_seat_mtk.jl @@ -0,0 +1,329 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "5" +#> title = "1. ODE_model_tractor_seat" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "modeling the movement of a oscilatory tractor seat" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 893b6948-b0be-11f0-13b3-f14c2a2208bb +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 47bc4a5d-7e5a-4278-b881-951737277dcf +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 9bb68aa2-14dd-4c28-b7c1-21f05f5671e2 +using OrdinaryDiffEq, ModelingToolkit + +# ╔═╡ fa5bb459-e754-401d-84a3-0ae75fb9b914 +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 00038789-08c2-464a-9941-1109ab47d811 +md""" +# Exercise: Tractor seat +""" + +# ╔═╡ f16b68e8-02ae-49b8-a077-c0100e2d3e89 +solution(text) = Markdown.MD(Markdown.Admonition("hint", "Solution", [text])); + +# ╔═╡ c5c5d448-cb75-4d78-aa6b-2e8bc47aa008 +md""" +![](https://users.ugent.be/~gvhaelew/fig/tractor_seat_all.png) +""" + +# ╔═╡ 40b40bcf-498b-46f8-8f5d-9eb24375d229 +md""" +## Deriving the model equation +""" + +# ╔═╡ 815032ea-12ad-446c-836c-d75e2724bed4 +md""" +We want to model the movement of a tractor seat when, at time $t = 1$, a person goes to sit on the seat, suddenly increasing its mass. + +First, derive the equation relating the vertical position $y$ of a tractor seat with its mass $m$. The tractor seat consists of a spring and a shock absorber. The spring constant is denoted $k$ and the natural length of the spring is denoted $L$. The shock absorber has a damping effect which is proportional (proportionality factor $b$), but opposite, to the velocity of the seat when moving. Pay attention to the sense of forces! Assume that the seat was initially in a rest position $y_0$ and not moving ($v_0 = 0$). +""" + +# ╔═╡ 00c93c86-f862-4999-8b54-4edeb85628a0 +md""" +!!! task + Derive the model equation for the position $y$ of the seat. Use Newton's second law. +""" + +# ╔═╡ 86940e79-05d5-43e5-afb1-590475774850 +md""" +!!! hint + - Set up: + ``` math + \begin{align} + m\cfrac{d^2 y}{dt^2} &= \cdots \\ + \end{align} + ``` +""" + +# ╔═╡ 5210f34f-928a-4a14-ab61-e2a9454508dd +solution(md""" +``` math +\begin{align} + m\cfrac{d^2 y}{dt^2} &= -mg + k\left(L - y\right) - bv +\end{align} +``` +""") + +# ╔═╡ 5895728b-53cb-4717-8300-aedb1694c0cb +md""" +## Setting up the equations +""" + +# ╔═╡ 20073595-1940-406f-b30b-57a1033e9c4f +md""" +Define the variable for the position $y$. Name it `y`, and mention the dependency on the time $t$. +""" + +# ╔═╡ eb2ac37e-91e7-4253-9274-ef8e3a2c666b +# @variables missing + +# ╔═╡ 16b70e7d-e9a4-4406-a830-6e591fd868e7 +md""" +Define the parameters for this model and assign their corresponding values. + +| Parameter | Value | Unit | Meaning | +|:---------- |:---------- |:------------|:------------| +| $m$ | 23.0 | $kg$ | mass of the seat | +| $g$ | 9.81 | $m/s^2$ | gravitational constant | +| $k$ | 22570 | $N/m$ | spring constant | +| $L$ | 0.40 | $m$ | natural length of the spring | +| $b$ | 900 | $Ns/m$ | damping constant | +""" + +# ╔═╡ 2df58ef3-cec0-43a1-afcc-6fc8dd1ab4a6 +md"Use the following names: `m`, `g`, `k`, `L` and `b`." + +# ╔═╡ 0472f2b5-394f-4670-81ec-3046c9826ee0 +# @parameters missing + +# ╔═╡ e68827a7-5f6e-45f7-b7fb-d0b41472b2c8 +md""" +In order to know the initial position $y_0$ of the seat, calculate the steady state of $y$. + +Hints: +- Set both $\cfrac{d^2 y}{dt^2}$ and $v = \cfrac{d y}{dt}$ to zero in the model equation and determine an expression for the steady state of $y$. +- Once you have the expression it is preferable to calculate the actual value it a `let` ... `end` block. Everything in this block will be local scope and won't interfere with variable names defined elsewhere. +- Round to two digits after the decimal point with the function `round(..., digits=2)`. +""" + +# ╔═╡ d8ab108b-89af-4f3d-bb4d-dae83bd71aaf +# let +# m=23.0; g=9.81; k=22570; L=0.40 +# missing +# end + +# ╔═╡ e918256e-3999-4ab2-8792-7da6ba32ab19 +md""" +Set up the model equation. +""" + +# ╔═╡ 115c48a4-4dc0-4cf0-ac34-e651e773e675 +eq_position = missing + +# ╔═╡ 6049886b-95a3-4d9e-a4e7-d05f1e9c504a +md""" +## Building the ODE system +""" + +# ╔═╡ 5856753b-2753-4a83-b3ec-747a7c0b9c52 +md""" +Build the model and include the (discrete) event that when the time is $1$ second, the mass needs to be incremented with the mass of the person. Assume the person weighs $80$ $kg$. Name your model `sys_tractor_seat`. +""" + +# ╔═╡ 6bf84dfd-0f3c-47af-8b1d-fdad7e54f19b +M = 80 + +# ╔═╡ 75e757ef-ed89-40f7-a8d6-12884f4ccb98 +# @mtkbuild missing + +# ╔═╡ d4bc6ff0-7383-4ea2-a7e0-5719a9dfe794 +md""" +## Create and solve the ODE problem +""" + +# ╔═╡ 9270475c-1f63-4d3e-98e0-ba7d2417ee81 +md""" +Create the ODE problem. Use the steady state value that you calculated before for the initial position $y_0$. As mentioned before $v_0 = 0$. The symbol for $\frac{dy}{dt}$ in the vector of initial conditions is `D(y)`. Use a simulation time span of `5.0` seconds. Assign the value `b_val` to the damping constant parameter $b$. `b_val` is defined later in the notebook and bound to a slider, so you can see how it influences the results. +""" + +# ╔═╡ db295a4e-e877-46c6-9f7d-b3b9fa8d04d4 +oprob_treactor_seat = missing + +# ╔═╡ a6ca51e5-4019-4aa1-a667-66af70b77f8a +md""" +Solve the ODE problem. Make a deepcopy of the ODE problem, use `Tsit5()` and `saveat=0.01`. +""" + +# ╔═╡ 7c1edab7-722d-4014-90c0-1b834e80aec7 +sol_tractor_seat = missing + +# ╔═╡ 195752fc-f480-4996-98ae-41c0daf74942 +@bind b_val Slider(200:50:4000, default=900, show_value=true) + +# ╔═╡ dbe1a845-2cdc-45c0-9966-3187e67ef296 +md""" +## Plotting results +""" + +# ╔═╡ a4dbf8e2-044a-427a-b773-9b5b6281bffc +md""" +Plot the position $y$ of the seat over time. Use the option `idxs=[y]` and `ylim=(0.32, 0.392)`. Play with the sliders above to see their effect. +""" + +# ╔═╡ 0861aabb-60b5-442b-bb0d-a26ed10e7c00 +missing + +# ╔═╡ 0d90986f-7c35-4145-aa50-9093725b1440 +md""" +Check the final value of $y$. +""" + +# ╔═╡ d8db3d47-7a0d-4d59-8820-5dc81475b5e3 +missing + +# ╔═╡ 37c21c15-7e1e-4dc5-9cce-eaacba2be5d4 +md""" +!!! questions + 1. Can you interprete the plot? + 2. What is the approximate value of $b$ in order to have a critically damped motion? (A critically damped motion is when a system returns to its equilibrium position as quickly as possible without oscillating or overshooting). + 3. Does the steady state value depend on the value of $b$? +""" + +# ╔═╡ b7b5a8d9-5557-4fad-823c-1fb306894605 +md""" +Answers: +1. missing +2. missing +3. missing +""" + +# ╔═╡ 12e1b09d-7ec4-4efe-9a27-3373a8e22116 +md""" +Plot the velocity $v$ of the seat over time. Use `idxs=[D(y)]`. +""" + +# ╔═╡ c4e74bfe-c266-4040-81b4-f5d60737d406 +missing + +# ╔═╡ 54196cf6-8a5c-4db6-ab16-209c447316b0 +md""" +!!! question + If you use a considerable value for $b$, will the seat still be moving at the end of the time span? +""" + +# ╔═╡ 24aeaf8b-fc8a-45d2-9972-f4866aa6d17c +md""" +Answer: missing +""" + +# ╔═╡ b80c35e7-437e-4377-aee3-31294c680f30 +md""" +## Calculating the steady state values +""" + +# ╔═╡ 571afefb-1655-40e9-bfd2-d875db112fbe +md""" +Create a steady state problem and solve it. Hint: set the mass `m` to the correct value! +""" + +# ╔═╡ 5a07d780-0526-482b-a445-fd21c86a3104 +stst_val = missing + +# ╔═╡ 8e9e6d22-641c-41fc-aef6-4aabbccbf573 +md""" +Show the steady state value for `y` and `D(y)`. +""" + +# ╔═╡ 8b0c836a-5bb3-4490-8a22-b7ed2fa97aaa +missing + +# ╔═╡ 6d050df4-eae3-4aef-a730-ef343cee812d +missing + +# ╔═╡ edd73175-b75f-4538-b189-8049abfe5abb +md""" +!!! question + Do the steady state values correspond to the ones you can derive from the plots? +""" + +# ╔═╡ b2e0a8ee-fddc-4c8f-af07-92fb5ec9aa34 +md""" +Answer: missing +""" + +# ╔═╡ Cell order: +# ╟─00038789-08c2-464a-9941-1109ab47d811 +# ╠═893b6948-b0be-11f0-13b3-f14c2a2208bb +# ╠═47bc4a5d-7e5a-4278-b881-951737277dcf +# ╠═9bb68aa2-14dd-4c28-b7c1-21f05f5671e2 +# ╠═fa5bb459-e754-401d-84a3-0ae75fb9b914 +# ╠═f16b68e8-02ae-49b8-a077-c0100e2d3e89 +# ╟─c5c5d448-cb75-4d78-aa6b-2e8bc47aa008 +# ╟─40b40bcf-498b-46f8-8f5d-9eb24375d229 +# ╟─815032ea-12ad-446c-836c-d75e2724bed4 +# ╟─00c93c86-f862-4999-8b54-4edeb85628a0 +# ╟─86940e79-05d5-43e5-afb1-590475774850 +# ╟─5210f34f-928a-4a14-ab61-e2a9454508dd +# ╟─5895728b-53cb-4717-8300-aedb1694c0cb +# ╟─20073595-1940-406f-b30b-57a1033e9c4f +# ╠═eb2ac37e-91e7-4253-9274-ef8e3a2c666b +# ╟─16b70e7d-e9a4-4406-a830-6e591fd868e7 +# ╟─2df58ef3-cec0-43a1-afcc-6fc8dd1ab4a6 +# ╠═0472f2b5-394f-4670-81ec-3046c9826ee0 +# ╟─e68827a7-5f6e-45f7-b7fb-d0b41472b2c8 +# ╠═d8ab108b-89af-4f3d-bb4d-dae83bd71aaf +# ╟─e918256e-3999-4ab2-8792-7da6ba32ab19 +# ╠═115c48a4-4dc0-4cf0-ac34-e651e773e675 +# ╟─6049886b-95a3-4d9e-a4e7-d05f1e9c504a +# ╟─5856753b-2753-4a83-b3ec-747a7c0b9c52 +# ╠═6bf84dfd-0f3c-47af-8b1d-fdad7e54f19b +# ╠═75e757ef-ed89-40f7-a8d6-12884f4ccb98 +# ╟─d4bc6ff0-7383-4ea2-a7e0-5719a9dfe794 +# ╟─9270475c-1f63-4d3e-98e0-ba7d2417ee81 +# ╠═db295a4e-e877-46c6-9f7d-b3b9fa8d04d4 +# ╟─a6ca51e5-4019-4aa1-a667-66af70b77f8a +# ╠═7c1edab7-722d-4014-90c0-1b834e80aec7 +# ╠═195752fc-f480-4996-98ae-41c0daf74942 +# ╟─dbe1a845-2cdc-45c0-9966-3187e67ef296 +# ╟─a4dbf8e2-044a-427a-b773-9b5b6281bffc +# ╠═0861aabb-60b5-442b-bb0d-a26ed10e7c00 +# ╟─0d90986f-7c35-4145-aa50-9093725b1440 +# ╠═d8db3d47-7a0d-4d59-8820-5dc81475b5e3 +# ╟─37c21c15-7e1e-4dc5-9cce-eaacba2be5d4 +# ╠═b7b5a8d9-5557-4fad-823c-1fb306894605 +# ╟─12e1b09d-7ec4-4efe-9a27-3373a8e22116 +# ╠═c4e74bfe-c266-4040-81b4-f5d60737d406 +# ╟─54196cf6-8a5c-4db6-ab16-209c447316b0 +# ╠═24aeaf8b-fc8a-45d2-9972-f4866aa6d17c +# ╟─b80c35e7-437e-4377-aee3-31294c680f30 +# ╟─571afefb-1655-40e9-bfd2-d875db112fbe +# ╠═5a07d780-0526-482b-a445-fd21c86a3104 +# ╟─8e9e6d22-641c-41fc-aef6-4aabbccbf573 +# ╠═8b0c836a-5bb3-4490-8a22-b7ed2fa97aaa +# ╠═6d050df4-eae3-4aef-a730-ef343cee812d +# ╟─edd73175-b75f-4538-b189-8049abfe5abb +# ╠═b2e0a8ee-fddc-4c8f-af07-92fb5ec9aa34 diff --git a/src/exercises/ode_model_water_evap_infil.jl b/src/exercises/ode_model_water_evap_infil.jl deleted file mode 100644 index fd2bf07e..00000000 --- a/src/exercises/ode_model_water_evap_infil.jl +++ /dev/null @@ -1,233 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "9" -#> title = "EXTRA. ODE water evaporation" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "ODE model of water evaporation" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ e079823b-8b40-42a2-a63f-1645a97b33f0 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 62dc7706-f58a-11ee-2d3d-f78f7ceca914 -using Markdown - -# ╔═╡ 4071647d-3084-4c8b-9fb7-eca7255253a9 -using InteractiveUtils - -# ╔═╡ 65571bb8-e260-4a82-b0d2-198e47c56271 -using PlutoUI - -# ╔═╡ 2574879f-28d1-4d30-a1aa-a637dd1b216a -using Catalyst - -# ╔═╡ 6c4b3d09-09c2-4439-9167-63b59b078104 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 66e8a12c-74b6-4077-b90e-3d85e5a61d6e -hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text])); - -# ╔═╡ f55e0ca3-07ff-43a1-907f-9ad640227822 -md""" -# Exercise: Water evaporation and infiltration - -Consider a water reservoir, such as a lake, where the water in the reservoir is in contact with the air as well as with the groundwater. We will denote the water level in the reservoir as $W$ and the groundwater level as $G$. - -The water in the reservoir evaporates at a rate $k_1$ (i.e. the *evaporation coefficient*) and there can be infiltration into or from the groundwater at a rate $k_2$ (i.e., *infiltration coefficient*) depending on the difference in the water level in the reservoir and groundwater (cf. $(W-G)$) - -There is a natural constant inflow of water into the reservoir at a rate $I$. At time $t=0\;s$ a pumping device is switched on such that the reservoir is rapidly being emptied at an outflow rate $O$ until the level of the water reservoir drops to zero. From then on, the pump is switched off. -""" - -# ╔═╡ a551f3c5-2fc3-4236-ab4a-3d9c14afc62b -md""" -!!! question -Set-up a system of differential equations modelling the above problem. -""" - -# ╔═╡ 4eb95688-ba9d-4524-a522-3b8343f4e2be -hint( -md""" -The system of differential equations that models the water level in a reservoir ($W$) and the groundwater level ($G$) considering evaporation, infiltration, inlet flow and outlet flow can be written down as: - -$$\begin{align} -\frac{dW}{dt} & = I - O - k_1 \cdot W - k_2 \cdot (W - G) \\ -\frac{dG}{dt} & = k_2 \cdot (W - G) -\end{align}$$ -""" -) - -# ╔═╡ dc9d4427-0a9e-41ab-9074-3e4f10dbae7f -md""" -Model the aforementioned system of differential equations using a *reaction network object*. Name it `water_evap_infil`. -""" - -# ╔═╡ 3c275181-503e-4806-b0e6-1731ae881a30 -# Uncomment and complete the instruction -# water_evap_infil = @reaction_network begin -# missing -# end - -# ╔═╡ 608c61bc-c029-433e-ad8f-b13cfb40bc3d -md""" -Convert the system to a symbolic differential equation model and verify that you get the same system of differential equations as given in the problem. -""" - -# ╔═╡ 5647d122-5e8d-4ff9-a798-4076ee93b771 -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ acab2bf0-b792-4ccc-bee0-7611bedab23c -md""" -Both water levels are initially $6.75\;m$. The inflow rate is constant and is $2.7\; m/min$. The evaporation and infiltration coefficient are $0.4\;min^{-1}$ and $1.0\;min^{-1}$ respectively. The outflow rate due to the pump is $20\;m/min$ and the pump stops working when $W$ equals zero. We wish to simulate the evolution of $W$ and $G$ during $20\;min$. -""" - -# ╔═╡ 44cf2ac6-75e2-4440-94f1-8fe6887ee1e0 -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ 733bdb56-fb4f-4bc6-b50c-e3245fd59730 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 521fcca5-3c88-463b-9395-e5871b9fc5a3 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ f2267cc9-4bfd-44af-984c-cf54aa855f91 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ e12840ed-d448-4f7c-893d-cfe974f62f9a -md""" -Initialize a vector `param` with the parameter values: -""" - -# ╔═╡ 5a3d520d-fd4f-485c-a8b0-63858eca4bfc -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 666c3aa1-26ef-4d77-bb81-f8830e66eea2 -md""" -Set-up a the *condition*, name it `condition`. -""" - -# ╔═╡ ce612639-0791-4df9-bbd1-11da5ae8b247 -# condition = missing # Uncomment and complete the instruction - -# ╔═╡ b4e50fbc-6780-4559-8cab-d7f2fd533eba -md""" -Make a new *reaction system* where the discrete event is included. Name it `water_evap_infil_c`. -""" - -# ╔═╡ 82db28cb-d842-442f-a566-32c6fe3acc90 -# @named water_evap_infil_c = missing # Uncomment and complete the instruction - -# ╔═╡ 7b649517-ccab-4f30-b75d-527295cd24a2 -md""" -Complete the new *reaction system*. Name it `water_evap_infil_c_com`. -""" - -# ╔═╡ 2d72257d-cf23-4a07-b659-4b886abe5abc -# water_evap_infil_c_com = missing # Uncomment and complete the instruction - -# ╔═╡ aafb49a4-b468-49b2-838d-5ddfcc852d48 -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ a25d3652-13f5-47ef-9f16-c6698547a734 -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ 451a3c66-5bcc-4161-af81-f89af33b5862 -md""" -Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.1`. Store the solution in `osol`: -""" - -# ╔═╡ b9d2c6cb-88f0-4a88-9e61-eecc905ff3e6 -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ ab77c284-d379-47d1-bd86-88fa77749165 -md""" -Plot the results: -""" - -# ╔═╡ e29a3294-245b-445d-bb1e-12cafb2ec175 -# missing # Uncomment and complete the instruction - -# ╔═╡ 66588291-d399-4169-9383-ac6c05cdf906 -md""" -Interpret the results. Ask yourself the following questions: - -1. Can you clearly see the drop in $W$? To what value does $W$ drops? -""" - -# ╔═╡ 89ece6c0-3690-4265-8b1d-c3a3cc8b095f -md"- Answer: missing" - -# ╔═╡ 92fa2a5a-123b-434c-a97f-39d9727a5ab6 -md""" -2. Why does $G$ also drop when $W$ drops? Explain. -""" - -# ╔═╡ 71dde4bd-711c-40c3-8ee2-9f7a79d72aa2 -md"- Answer: missing" - -# ╔═╡ d04d906d-1432-4753-81b9-b03a06df9816 -md""" -3. To what values are $W$ and $G$ tending to go? Was the system with the initial values for $W$ and $G$ and no outflow in equilibrium? Explain. -""" - -# ╔═╡ 4ce3328e-3436-45ad-b899-9b901b53a8ea -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═62dc7706-f58a-11ee-2d3d-f78f7ceca914 -# ╠═4071647d-3084-4c8b-9fb7-eca7255253a9 -# ╠═e079823b-8b40-42a2-a63f-1645a97b33f0 -# ╠═65571bb8-e260-4a82-b0d2-198e47c56271 -# ╟─66e8a12c-74b6-4077-b90e-3d85e5a61d6e -# ╟─f55e0ca3-07ff-43a1-907f-9ad640227822 -# ╟─a551f3c5-2fc3-4236-ab4a-3d9c14afc62b -# ╟─4eb95688-ba9d-4524-a522-3b8343f4e2be -# ╠═2574879f-28d1-4d30-a1aa-a637dd1b216a -# ╟─dc9d4427-0a9e-41ab-9074-3e4f10dbae7f -# ╠═3c275181-503e-4806-b0e6-1731ae881a30 -# ╟─608c61bc-c029-433e-ad8f-b13cfb40bc3d -# ╠═5647d122-5e8d-4ff9-a798-4076ee93b771 -# ╠═6c4b3d09-09c2-4439-9167-63b59b078104 -# ╟─acab2bf0-b792-4ccc-bee0-7611bedab23c -# ╟─44cf2ac6-75e2-4440-94f1-8fe6887ee1e0 -# ╠═733bdb56-fb4f-4bc6-b50c-e3245fd59730 -# ╟─521fcca5-3c88-463b-9395-e5871b9fc5a3 -# ╠═f2267cc9-4bfd-44af-984c-cf54aa855f91 -# ╟─e12840ed-d448-4f7c-893d-cfe974f62f9a -# ╠═5a3d520d-fd4f-485c-a8b0-63858eca4bfc -# ╟─666c3aa1-26ef-4d77-bb81-f8830e66eea2 -# ╠═ce612639-0791-4df9-bbd1-11da5ae8b247 -# ╟─b4e50fbc-6780-4559-8cab-d7f2fd533eba -# ╠═82db28cb-d842-442f-a566-32c6fe3acc90 -# ╟─7b649517-ccab-4f30-b75d-527295cd24a2 -# ╠═2d72257d-cf23-4a07-b659-4b886abe5abc -# ╟─aafb49a4-b468-49b2-838d-5ddfcc852d48 -# ╠═a25d3652-13f5-47ef-9f16-c6698547a734 -# ╟─451a3c66-5bcc-4161-af81-f89af33b5862 -# ╠═b9d2c6cb-88f0-4a88-9e61-eecc905ff3e6 -# ╟─ab77c284-d379-47d1-bd86-88fa77749165 -# ╠═e29a3294-245b-445d-bb1e-12cafb2ec175 -# ╟─66588291-d399-4169-9383-ac6c05cdf906 -# ╟─89ece6c0-3690-4265-8b1d-c3a3cc8b095f -# ╟─92fa2a5a-123b-434c-a97f-39d9727a5ab6 -# ╟─71dde4bd-711c-40c3-8ee2-9f7a79d72aa2 -# ╟─d04d906d-1432-4753-81b9-b03a06df9816 -# ╟─4ce3328e-3436-45ad-b899-9b901b53a8ea diff --git a/src/exercises/optim_wastewater_treatment.jl b/src/exercises/optim_wastewater_treatment.jl deleted file mode 100644 index 3a1876dc..00000000 --- a/src/exercises/optim_wastewater_treatment.jl +++ /dev/null @@ -1,332 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "27" -#> title = "5. Optimisation wastewater treatment" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Optimisation wasterwater treatment" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - - -using Markdown -using InteractiveUtils - -# ╔═╡ 2b26c3b2-df08-4b24-a08d-23717248c10d -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ f08fa69c-a744-11ef-0e79-3daf5bf297ea -using Markdown - -# ╔═╡ e6c97e37-d062-4b65-96a4-bac0dab220d8 -using InteractiveUtils - -# ╔═╡ 0407d891-a46d-4deb-a21a-23833acbcb87 -using Catalyst, OrdinaryDiffEq, StatsPlots - -# ╔═╡ e48dc930-be03-47b2-b9e3-16e854782aec -using Turing, StatsBase, Optim - -# ╔═╡ 6458329f-73dd-4cb0-8da4-90678875a1f0 -using PlutoUI; TableOfContents() - -# ╔═╡ c1bc698d-41ee-45e6-b17d-29f0d53557a1 -md""" -# Exercise: Wastewater treatment - Optimisation -""" - -# ╔═╡ dfe77a8c-a8db-46d7-9a4d-3b00413d383b -md""" -Consider a wastewater treatment plant where wastewater circulates in cylindrical tanks so that microorganisms can break down the organic material present. At the top of such a tank with volume $V\;[m^3]$, wastewater enters at a flow rate $q\;[m^3/h]$. The concentration of organic material in the inflow is known and equal to $C_{in}\;[kg/m^3]$. At the bottom of the tank, wastewater and microorganisms leave the tank at the same flow rate $q\;[m^3/h]$ so that the volume of wastewater in the tank remains constant. - -The concentration of organic material in the tank is denoted as $C\;[kg/m^3]$ and the concentration of microorganisms is denoted as $X\;[kg/m^3]$. The microorganisms in the tank break down the organic material at a rate proportional to $r\cfrac{K_s}{K_s+C}\;[m^3\,h^{-1}\,kg^{-1}]$ with yield coefficient $Y$. The factor $K_s\;[kg/m^3]$ is the concentration of $C$ where the rate is half its maximum rate and $r\;[m^3\,h^{-1}\,kg^{-1}]$ is the maximum growth rate coefficient. Furthermore, the microorganisms degrade with rate coefficient $k_d$. In the middle of the tank there is a mixing system that ensures that the wastewater and microorganisms are well mixed. This means that the concentration in the outflow is equal to the concentration in the tank: $C_{out} = C$ and $X_{out} = X$. The system of differential equations describing the change in the concentrations $C(t)$ and $X(t)$ is given by: - -$$\cfrac{dC}{dt} = \cfrac{q}{V}\left(C_{in} - C\right) - r\cfrac{K_s}{K_s+C}\,C\,X$$ -$$\cfrac{dX}{dt} = -\cfrac{q}{V}X -k_d\,X + Y\,r\cfrac{K_s}{K_s+C}\,C\,X$$ - -The initial concentrations and the parameter values are summarised in the following tables: - -| $C_0$ | $X_0$ | -|:---------:|:---------:| -| $4.0$ | $0.01$ | - -| $q$ | $V$ | $r$ | $C_{in}$ | $K_s$ | $k_d$ | $Y$ | -|:--------:|:--------:|:--------:|:---------:|:---------:|:---------:|:--------:| -| $5.0$ | $50$ | $0.4$ | $3.0$ | $5.2$ | $0.10$ | $1.2$ | - -The amount of organic waste that is being broken down by the microorganisms depends on the flow rate $q$. First (Part 1), we will simulate the system with the parameters given above. Secondly (Part 2), we will optimize the value of the flow rate $q$ so that the concentration of organic waste in the tank is at most $0.28\; kg\,m^{-3}$. Take a simulation time of $72\;hours$ in both cases. -""" - -# ╔═╡ c0c83df7-a9cc-4bde-b6ec-038a423b0d90 -md""" -## Part 1 - Simulation - -In this part, we will simulate the system with the parameters given above. -""" - -# ╔═╡ bbd50cc5-032a-4219-bcee-91145935a7c4 -md""" -### Implementation of the system -""" - -# ╔═╡ a5f79c20-f62e-4df2-be79-b4f2141ced5e -md""" -Create a *reaction network object* model for the aforementioned problem. Name it `wastewater_treatment`. - -!!! tip - You can use the repressive Michaelis-Menten function `mmr(C, r, Ks)` for $r\cfrac{K_s}{K_s+C}$. -""" - -# ╔═╡ 8708de16-3532-4352-b211-c092f95c82d3 -# Uncomment and complete the instruction -# wastewater_treatment = @reaction_network begin -# @parameters missing -# @species missing -# missing -# missing -# missing -# missing -# end - -# ╔═╡ 10c73294-a32b-4aa3-80a8-10785e5eab8f -md""" -Convert the system to a symbolic differential equation model and verify your system of differential equations. -""" - -# ╔═╡ fee917dd-7ab5-4fda-b1b7-87ee61e21f19 -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 08ebcb95-8603-4579-879e-810b1494b013 -md""" -##### Setting up initial conditions, timespan and parameter values -""" - -# ╔═╡ d248f64e-ebba-4443-9c49-ff0290aa7810 -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ fe02a755-5b00-4d80-a511-fec115b42964 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 67481927-0d03-4da9-af6c-9afa409fc006 -md""" -Set the timespan: -""" - -# ╔═╡ fadd372a-a665-4b16-9b6d-e32cb7f25d7f -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 734e4d51-95a7-464e-9a23-5ad6c8715d65 -md""" -Initialize a vector `params` with the parameter values: -""" - -# ╔═╡ 15ce9889-a437-46c8-9062-74b8d234a8bd -# params = missing # Uncomment and complete the instruction - -# ╔═╡ b8a48461-3882-45f6-980c-38d650ac52c7 -md""" -### Creating an ODE problem, solve the problem and plot results -""" - -# ╔═╡ a4c57b64-6a7d-4bd4-8bb2-578923e184d2 -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ b1e18139-5277-4f06-b1f8-b0f5f11c41d8 -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ ad6d8fe6-e62f-4c67-8d63-4ee13b928ad0 -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=0.1`. Store the solution in `osol`: -""" - -# ╔═╡ e2ffba9e-aaf2-4540-84cf-8b7297ae9285 -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ 70871ee8-b0a4-4a9a-af39-5a63459b55f7 -md""" -Plot the results. Use `ylim=(0, 3)` and `lw=2` (or `linewidth=2`) as options. -""" - -# ╔═╡ 34309734-3751-47e0-a602-d113ffaae510 -# Uncomment and complete the instruction -# begin -# missing -# hline!([0.28], ls=:dash, lw=2, lc=:green, lab="C=0.28") -# end - -# ╔═╡ a0e735ad-09c2-4aa8-bc41-b294a9d56ea8 -md""" -Check out the end value of the organic waste. -""" - -# ╔═╡ f62898d5-1b8d-4350-8655-78aa3decb2a2 -# missing # Uncomment and complete the instruction - -# ╔═╡ 7eb5df9c-a475-4812-81c3-e43484c82242 -md""" -## Part 2 - Optimization - -In this part, we will optimize the value of the flow rate $q$ so that the concentration of organic waste in the tank is at most $0.28\; kg\,m^{-3}$. -""" - -# ╔═╡ 5a695734-677f-4bf6-a703-8e22382b7529 -md""" -First, declare the Turing model function. Sample the flow rate $q$ prior from an uniform distribution in the range $[0, 5]\;kg\,m^{-3}$. Suppose therein that the desired final value of the organic waste (i.e. $0.28\; kg\,m^{-3}$) is normally distributed with mean the end value obtained from the solution and standard deviation $10^{-3}\; kg\,m^{-3}$. -""" - -# ╔═╡ b6bac48a-4a3d-47e4-90ea-788ca20dadff -# Uncomment and complete the instruction -# @model function wastewater_treatment_inference() -# q ~ missing -# u0 = missing -# tspan = missing -# params = missing -# oprob = missing -# osol = missing -# C_d ~ missing -# end - -# ╔═╡ b3a40556-0c00-4f6d-8cd9-c5fca79d8bbf -md""" -Define the desired value for the organic waste with the variable name `C_val`. -""" - -# ╔═╡ 2df409ef-bd95-4ac3-a2b8-c5e17c490eba -# missing # Uncomment and complete the instruction - -# ╔═╡ 70cafd87-63f7-4674-ae49-43d422fdeae7 -md""" -Now condition the model with the desired value: -""" - -# ╔═╡ ef20f8b8-4527-4f02-b449-fa67b68bbf65 -# wastewater_treatment_cond_mod = missing - -# ╔═╡ ee1ffc12-55a1-47ef-ac5b-33148706a09b -md""" -Optimize the prior for $q$. Do this with `MLE` method and Nelder-Mead. Store the optimization results in `results_mle`. -""" - -# ╔═╡ afc035be-075b-464b-8ba2-20235082f005 -# results_mle = missing # Uncomment and complete the instruction - -# ╔═╡ 97a00511-93d4-45d6-b320-9bad1b102397 -md""" -Check out the coefficient table. -""" - -# ╔═╡ 64844b47-1578-4e47-9cc7-7f242a583067 -# missing - -# ╔═╡ 3ee8121e-3e78-4901-a32d-f04d0c6a0996 -md""" -Get the optimized value for $q$ and assign it to `q_opt`. -""" - -# ╔═╡ 98a157a1-8c20-474d-acb8-00373ee6d224 -# q_opt = missing # Uncomment and complete the instruction - -# ╔═╡ ceb146c9-a09a-458b-b7d8-3bb7d3de38e0 -md""" -Set up parameter values with the optimized parameter value. -""" - -# ╔═╡ e275df05-5c77-4c17-ad2e-503574596c31 -# params_opt = missing # Uncomment and complete the instruction - -# ╔═╡ cd515dad-44fb-4af2-b933-805ef76be9b3 -md""" -Create an ODEProblem and solve it. Use `Tsit5()` and `saveat=0.1`. -""" - -# ╔═╡ a4388f06-1223-4815-a557-9b9c3ec232bb -# oprob_opt = missing # Uncomment and complete the instruction - -# ╔═╡ ec7bf654-b275-4cfd-a819-d82bdc1be93b -# osol_opt = missing # Uncomment and complete the instruction - -# ╔═╡ 82809c26-4cab-405e-8107-a8a43e81f699 -md""" -Plot $C$ and $X$ simulated with both the initial and the optimized parameter values. Use `ylim=(0, 3)` and `lw=2` (or `linewidth=2`) as options. The dashed line indicates $C = 0.28\; kg\,m^{-3}$. -""" - -# ╔═╡ 81429279-4190-41d1-a72a-20da0ce90528 -# Uncomment and complete the instruction -# begin -# missing -# plot!(osol, ls=:dash, lw=1, lab=:none) -# hline!([0.28], ls=:dash, lw=2, lc=:green, lab="C=0.28") -# end - -# ╔═╡ 6589acfd-1d81-4c10-adea-34ca7fa1ab5d -md""" -!!! question - Does the value of $C$ respect now the limit in the concentration? Draw your conclusion. -""" - -# ╔═╡ 7c7d99b9-77b9-4c08-a74e-54eaa7d187ec -md""" -- Conclusion: missing -""" - -# ╔═╡ Cell order: -# ╠═f08fa69c-a744-11ef-0e79-3daf5bf297ea -# ╠═e6c97e37-d062-4b65-96a4-bac0dab220d8 -# ╠═2b26c3b2-df08-4b24-a08d-23717248c10d -# ╠═0407d891-a46d-4deb-a21a-23833acbcb87 -# ╠═e48dc930-be03-47b2-b9e3-16e854782aec -# ╠═6458329f-73dd-4cb0-8da4-90678875a1f0 -# ╟─c1bc698d-41ee-45e6-b17d-29f0d53557a1 -# ╟─dfe77a8c-a8db-46d7-9a4d-3b00413d383b -# ╟─c0c83df7-a9cc-4bde-b6ec-038a423b0d90 -# ╟─bbd50cc5-032a-4219-bcee-91145935a7c4 -# ╟─a5f79c20-f62e-4df2-be79-b4f2141ced5e -# ╠═8708de16-3532-4352-b211-c092f95c82d3 -# ╟─10c73294-a32b-4aa3-80a8-10785e5eab8f -# ╠═fee917dd-7ab5-4fda-b1b7-87ee61e21f19 -# ╟─08ebcb95-8603-4579-879e-810b1494b013 -# ╟─d248f64e-ebba-4443-9c49-ff0290aa7810 -# ╠═fe02a755-5b00-4d80-a511-fec115b42964 -# ╟─67481927-0d03-4da9-af6c-9afa409fc006 -# ╠═fadd372a-a665-4b16-9b6d-e32cb7f25d7f -# ╟─734e4d51-95a7-464e-9a23-5ad6c8715d65 -# ╠═15ce9889-a437-46c8-9062-74b8d234a8bd -# ╟─b8a48461-3882-45f6-980c-38d650ac52c7 -# ╟─a4c57b64-6a7d-4bd4-8bb2-578923e184d2 -# ╠═b1e18139-5277-4f06-b1f8-b0f5f11c41d8 -# ╟─ad6d8fe6-e62f-4c67-8d63-4ee13b928ad0 -# ╠═e2ffba9e-aaf2-4540-84cf-8b7297ae9285 -# ╟─70871ee8-b0a4-4a9a-af39-5a63459b55f7 -# ╠═34309734-3751-47e0-a602-d113ffaae510 -# ╟─a0e735ad-09c2-4aa8-bc41-b294a9d56ea8 -# ╠═f62898d5-1b8d-4350-8655-78aa3decb2a2 -# ╟─7eb5df9c-a475-4812-81c3-e43484c82242 -# ╟─5a695734-677f-4bf6-a703-8e22382b7529 -# ╠═b6bac48a-4a3d-47e4-90ea-788ca20dadff -# ╟─b3a40556-0c00-4f6d-8cd9-c5fca79d8bbf -# ╠═2df409ef-bd95-4ac3-a2b8-c5e17c490eba -# ╟─70cafd87-63f7-4674-ae49-43d422fdeae7 -# ╠═ef20f8b8-4527-4f02-b449-fa67b68bbf65 -# ╟─ee1ffc12-55a1-47ef-ac5b-33148706a09b -# ╠═afc035be-075b-464b-8ba2-20235082f005 -# ╟─97a00511-93d4-45d6-b320-9bad1b102397 -# ╠═64844b47-1578-4e47-9cc7-7f242a583067 -# ╟─3ee8121e-3e78-4901-a32d-f04d0c6a0996 -# ╠═98a157a1-8c20-474d-acb8-00373ee6d224 -# ╟─ceb146c9-a09a-458b-b7d8-3bb7d3de38e0 -# ╠═e275df05-5c77-4c17-ad2e-503574596c31 -# ╟─cd515dad-44fb-4af2-b933-805ef76be9b3 -# ╠═a4388f06-1223-4815-a557-9b9c3ec232bb -# ╠═ec7bf654-b275-4cfd-a819-d82bdc1be93b -# ╟─82809c26-4cab-405e-8107-a8a43e81f699 -# ╠═81429279-4190-41d1-a72a-20da0ce90528 -# ╟─6589acfd-1d81-4c10-adea-34ca7fa1ab5d -# ╠═7c7d99b9-77b9-4c08-a74e-54eaa7d187ec diff --git a/src/exercises/probabilistic_selection.jl b/src/exercises/probabilistic_selection.jl deleted file mode 100644 index 0fa79a9c..00000000 --- a/src/exercises/probabilistic_selection.jl +++ /dev/null @@ -1,670 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "36" -#> title = "7. Probability selection" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Probability selection" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" -using Markdown -using InteractiveUtils - -# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). -macro bind(def, element) - #! format: off - quote - local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end - local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) - el - end - #! format: on -end - -# ╔═╡ a18638d0-138d-11f0-0571-33060f7da7ba -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ fe265128-33a2-40d1-ab71-bc5c53979a54 -using Turing, StatsPlots - -# ╔═╡ c870d28d-7b2e-48f8-8990-b2cc0943cb09 -using Optim, StatsBase - -# ╔═╡ 2a73d23c-73fc-4845-b359-c6fe22077140 -using PlutoUI - -# ╔═╡ fb07436c-cc5d-4721-8a80-d7f7201721d7 -md"# Model selection" - -# ╔═╡ a397f48e-4228-435e-af13-c2bc71c8cb05 -TableOfContents() - -# ╔═╡ a6635715-52e3-44e7-9c00-0be751f830d6 -md"## Who's that distribution?" - -# ╔═╡ 473c003a-a34b-4e92-8a82-99450514d755 -md""" -You decide to turn your life around and invest all your money into **clams**, or more specifically, **pearl farming**. Before setting up your full-scale farm, you decide to test the pearl-producing capabilities of different species of mollusk. You cultivate 10 different species, wait a year, and collect and measure the resulting pearls. - -You want to compare the species by **fitting a distribution** to the pearl sizes. This way you can compare average size, expected deviation and the probability to get a really big pearl. **However, you don't know what distribution the pearl sizes follow.** -Since they're positive real numbers, 2 good candidates are the `Exponential` and `LogNormal` distributions. -""" - -# ╔═╡ a2dec4e7-7c16-4ae5-974e-8c01ec31507a -md""" -!!! question - For every molluks species, does the data follow an Exponential or a LogNormal distribution? -""" - -# ╔═╡ c4e09491-fd0a-43e7-8e74-04832a045a48 -md""" -![Picture of a black pearl in its shell](https://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Black_pearl_and_his_shell.jpg/1280px-Black_pearl_and_his_shell.jpg) -> Source: Brocken Inaglory (Wikipedia) -""" - -# ╔═╡ aba7efa9-a0c3-4da4-82a2-bb7efc6b9e22 -md"### Data" - -# ╔═╡ c8b56408-0710-4646-bb2b-e336e7f689c2 -# ╠═╡ disabled = true -# ╠═╡ skip_as_script = true -#=╠═╡ -begin - -function generate_point() - firstdistr = rand() < 0.5 - if firstdistr - medist = Exponential(rand(Uniform(0.1, 10))) - else - medist = LogNormal(rand(Uniform(0.1, log(10))), rand(Uniform(0.1, 1.0))) - end - n_samples = rand(Poisson(15)) - samples = rand(medist, n_samples) .|> x -> round(x, digits = 2) - return samples -end - -distr_data = [generate_point() for _ in 1:10]; - -end; - ╠═╡ =# - -# ╔═╡ 19dd461d-cc03-41c9-a3d1-fcd477d08eb0 -distr_data = [[5.23, 2.79, 5.81, 4.36, 7.46, 4.46, 0.83, 6.45, 6.2, 6.53, 6.24, 8.72, 3.15], [1.12, 1.04, 0.09, 0.06, 0.67, 0.33, 0.41, 0.87, 1.23, 4.28, 7.46, 1.21, 0.19, 0.3, 0.59, 1.74, 0.66, 5.97, 0.3, 1.43, 1.11], [0.79, 3.37], [6.84, 11.28, 9.32, 6.27, 6.73, 10.28, 13.69, 8.32, 6.95], [0.48, 8.69, 3.92], [1.53, 1.83, 1.86, 0.87, 1.53, 2.51, 2.14, 1.82, 0.28, 3.57, 0.42, 1.67, 2.39, 4.18], [6.0, 2.37, 14.05, 4.01, 8.51, 5.29, 5.24, 18.01, 2.65, 8.91, 6.37, 2.54], [0.58, 2.41, 12.87, 14.67, 3.97, 13.8, 2.54, 4.7, 17.6, 18.3, 11.16, 0.81, 18.86, 2.3], [1.07, 0.6, 2.24, 0.02, 13.28, 4.88, 0.22, 18.54, 2.81, 2.97, 9.29, 2.98, 23.94, 0.39, 29.25, 1.05, 5.52, 0.39, 4.81, 3.73, 0.49], [8.39, 10.45, 1.93, 12.18, 3.26, 5.12, 8.3, 4.09, 20.41, 0.61, 18.31]]; - -# ╔═╡ 01a530db-5e18-4540-99a9-eecd9e61e1af -md"You can choose the mollusk species here and see the data for its pearl sizes." - -# ╔═╡ 6d39fbde-81c5-4786-99e1-b587bd15f94c -md"Mollusk species" - -# ╔═╡ fce554e2-a6da-4046-ab4e-9153e27aef7a -@bind distr_index Slider(1:10, show_value = true) - -# ╔═╡ 7b1ce376-d796-4913-a488-ee921bd13855 -pearlsizes = distr_data[distr_index] - -# ╔═╡ 712756bc-0408-45a6-931b-eec07b6e052f -histogram(pearlsizes, bins = 0:ceil(maximum(pearlsizes))) - -# ╔═╡ b4de93ea-a919-41c2-bb9c-bc1beccc9ea8 -md"### Model definition" - -# ╔═╡ 5a6f8d49-3841-46ff-a826-88ba0bed868b -md""" -We need to define a model for the two candidate distributions. The likelihood was already given above. For the priors, you can assume the following: -- Exponential model - - μ ~ `Uniform(0, 10)` -- LogNormal model - - μ ~ `Uniform(0, log(10))` - - σ ~ `Uniform(0, 1)` -""" - -# ╔═╡ 88edd0b1-738b-4a86-8995-abdfcc2bcc85 -md""" -!!! note - The `LogNormal` distribution is a bit weird: `LogNormal(μ, σ)` gives the distribution of **the exponential** of a normally distributed value with mean μ and standard deviation σ: - ```math - \begin{gather} - X \sim \text{Normal}(μ, σ) \, , - \\ \Rightarrow \text{exp}(X) \sim \text{LogNormal}(μ, σ) \, . - \end{gather} - ``` - This means that **μ** is not actually the mean of a `LogNormal(μ, σ)`, but something closer to **log(μ)** (it's complicated). Hence the `log(10)` in the prior above. -""" - -# ╔═╡ 05622800-ccd9-4aa8-9a95-a1fbe34aa76c -@model function expon(num_pearls) - μ_exp ~ Uniform(0, 10) - pearls = zeros(num_pearls) - for i in 1:num_pearls - pearls[i] ~ Exponential(μ_exp) - end -end - -# ╔═╡ c5b042bb-c1d9-4b0e-a2bd-a2c367019da3 -@model function lognorm(num_pearls) - μ_lognorm ~ Uniform(0, log(10)) - σ_lognorm ~ Uniform(0, 1.0) - pearls = zeros(num_pearls) - for i in 1:num_pearls - pearls[i] ~ LogNormal(μ_lognorm, σ_lognorm) - end -end - -# ╔═╡ c72cc55c-a5f7-47f5-b5c3-49af9a5db50d -md"Instantiate the models and condition them on the available data." - -# ╔═╡ 88306e52-d975-4bd5-a8f0-7e9293c9fb82 -expmodel = expon(length(pearlsizes)) | (pearls = pearlsizes,); - -# ╔═╡ a7cf9228-7515-4d53-b689-bc711283fecc -lognormmodel = lognorm(length(pearlsizes)) | (pearls = pearlsizes,); - -# ╔═╡ 811ee6ce-bb0f-4bcf-95be-6de95ab9166b -md"### Maximum likelihood" - -# ╔═╡ 392ad62d-b7ef-4951-b729-01e41684cbcf -md""" -Determine the maximum likelihood estimation (MLE) of the parameter values given the data, using the `NelderMead()` algorithm. Plot the fitted parameters on the data for a visual comparison. -""" - -# ╔═╡ 3b16c753-a2ba-4b59-a776-b35c4dd2e927 -exp_res = optimize(expmodel, MLE(), NelderMead()) - -# ╔═╡ 1ce90419-083a-475a-8334-32d73dafb606 -exp_mean = coef(exp_res)[:μ_exp] - -# ╔═╡ 654d0ec1-92b0-4220-91a8-607978f1cf4f -lognorm_res = optimize(lognormmodel, MLE(), NelderMead()) - -# ╔═╡ 17e90886-1a41-4c19-b93a-96d8bd7a075f -lognorm_mean, lognorm_spread = coef(lognorm_res)[[:μ_lognorm, :σ_lognorm]] - -# ╔═╡ 62f000f3-6f21-4c1e-ab37-29a122b84088 -begin - histogram(pearlsizes, normalize = :pdf) - plot!(Exponential(exp_mean), linewidth = 3) -end - -# ╔═╡ 7dc215e7-83ef-430e-9f99-24cada961964 -begin - histogram(pearlsizes, normalize = :pdf) - plot!(LogNormal(lognorm_mean, lognorm_spread), linewidth = 3) -end - -# ╔═╡ abffd9fb-dd0a-4145-8b7d-8e233e498735 -md"### Bayes factor" - -# ╔═╡ 1adee574-150d-4df6-94e0-6aa20ad92389 -md""" -Compare both models using the Bayes factor $K$. Start off by calculating the model evidence $P(D \mid M)$ of the data $D$ for each model $M$, approximating the integral with a [Riemann sum](https://en.wikipedia.org/wiki/Riemann_sum): -""" - -# ╔═╡ 461c10b2-13f6-442c-ae29-a5f5116a0d01 -md""" -```math -P(D \mid M) =\int_{\theta\in\Theta} P(D \mid M, \theta) \, P(\theta) \, d \theta \approx \sum_{i} P(D \mid M, \theta_i) \, P(\theta_i) \, \Delta \theta_i -``` -""" - -# ╔═╡ feee9ca3-05c5-480b-8cb9-19a3e0c536c1 -md""" -The figure below illustrates the different probabilities involved. The red curve is the product of the two curves above, and the area underneath it is the model evidence we want to calculate. -""" - -# ╔═╡ 259e3755-eac2-4bba-8c15-a54e326f365d -prior_exp(m) = exp(logprior(expmodel, (μ_exp=m,))); - -# ╔═╡ 85a07f1b-df87-4531-ba2a-ccae822379c4 -likelihood_exp(m) = exp(loglikelihood(expmodel, (μ_exp=m,))); - -# ╔═╡ 8fce7eb3-1371-4d3b-b9cb-8730e18d2be4 -posterior_exp(m) = exp(logjoint(expmodel, (μ_exp=m,))) ; - # prior * likelihood: not yet normalized with evidence! - -# ╔═╡ 304b0c2e-7169-403d-baf3-141b0b35f560 -let - xs = 0.1:0.1:15 - ys = [posterior_exp(x) for x in xs] - - p_likelihood = plot(x -> likelihood_exp(x), xlims = (0, 15), - label = "Likelihood: P(D | M, μ)", color = :blue, width = 2 - ) - p_prior = plot(prior_exp, label = "Prior: P(μ)", color = :cyan, width = 2, xlims = (0, 15)) - p_post = plot(xs, ys, label = "Unnormalized posterior: P(D| M)", - color = :red, width = 2, line = :dash, xlims = (0, 15), xlabel = "μ_exp", - ribbon = (ys, zeros(length(xs))), - yticks = round.(0:maximum(ys)/10:maximum(ys), sigdigits = 1) - ) - plot(p_likelihood, p_prior, p_post, ylabel = "density", plottitle = "Evidence", layout = (3, 1)) - -end - -# ╔═╡ 6d39160d-5e5a-4702-bd3f-617d6e7c5513 -Δm = 0.1 - -# ╔═╡ 89547bb1-9027-47c2-a8c0-0212a833879f -begin - evidence_exp = 0.0 - for m in 0.1:Δm:10 - likelihood_per_point = [pdf(Exponential(m), pearlsize) for pearlsize in pearlsizes] - likelihood = prod(likelihood_per_point) - prior = pdf(Uniform(0, 10), m) - evidence_exp += likelihood * prior * Δm - end - println(evidence_exp) -end - -# ╔═╡ 3a0d70d3-92d6-4254-b77b-58b75947548d -Δs = 0.01 - -# ╔═╡ c3fc3e53-3f60-4444-a99c-3a0ef7ce5a94 -begin - evidence_lognorm = 0.0 - for m in 0.1:Δm:log(10) - for s in 0.1:Δs:1.0 - likelihood_per_point = [ - pdf(LogNormal(m, s), pearlsize) - for pearlsize in pearlsizes - ] - likelihood = prod(likelihood_per_point) - prior = pdf(Uniform(0, log(10)), m) * pdf(Uniform(0, 1.0), s) - evidence_lognorm += likelihood * prior * Δm * Δs - end - end - println(evidence_lognorm) -end - -# ╔═╡ 89683590-7f48-4ee8-bc5c-46d37fc0067b -md""" -Now calculate the Bayes factor as follows: -```math -K = \frac{P(M_2 \mid D)}{P(M_1 \mid D)} = \frac{P(D \mid M_2) \, P(M_2)}{P(D \mid M_1) \, P(M_1)} -``` -""" - -# ╔═╡ 8c63219d-e520-40b7-a4b3-c6e2ffc7b17e -P_M_exp = 0.5 - -# ╔═╡ 84f99a01-f3e5-4197-998d-2bdec0d53fb1 -P_M_lognorm = 1 - P_M_exp - -# ╔═╡ 37f977de-f1a9-4a83-85f1-8dadf7910025 -bayes_factor = (evidence_lognorm * P_M_lognorm) / (evidence_exp * P_M_exp) - -# ╔═╡ 4995e5a0-b56d-4aa5-b41d-e264936c3bd9 -md""" -Another comparison we can make between the models is calculating whether the first model is the correct one: -```math -\begin{align} -P(M_1 \mid D) &= \frac{P(D \mid M_1) \, P(M_1)}{P(D)} \, , -\\&= \frac{P(D \mid M_1) \, P(M_1)}{P(D \mid M_1) \, P(M_1) + P(D \mid M_2) \, P(M_2)} \, . -\end{align} -``` -""" - -# ╔═╡ ce13fb27-8b5a-4efd-b415-4d0bfd6d3a6a -P_M_exp_cond_D = evidence_exp * P_M_exp / (evidence_exp * P_M_exp + evidence_lognorm *P_M_lognorm) - -# ╔═╡ fd1813b9-d8f2-468b-bd8e-bafdd9dca750 -md""" -!!! extra - A faster way to calculate the model evidences is using Turing's `logjoint` function and [array comprehensions](https://docs.julialang.org/en/v1/manual/arrays/#man-comprehensions). -""" - -# ╔═╡ fe5e1b6d-33f2-4357-a40d-4e77845332e0 -evidence1 = sum(exp(logjoint(expmodel, (μ_exp=m,)))*Δm for m in 0.1:Δm:10) - -# ╔═╡ 306333d0-e7e0-4d61-9afb-b5afdc817699 -evidence2 = sum(exp(logjoint(lognormmodel, (μ_lognorm=m, σ_lognorm=s,)))*Δm*Δs for m in 0.1:Δm:10 for s in 0.1:Δs:1) - -# ╔═╡ 5b34c046-bc87-474d-8c20-b5732c0de219 -md"### AIC" - -# ╔═╡ cc4e9632-53ab-48c9-b179-ca2b10cbfabc -md""" -Using the likelihoods calculated above, calculate the Akaike Information Criterion (AIC) for both models: -""" - -# ╔═╡ 964706fe-e48c-4cf6-8e80-484867719bf6 -md""" -```math -\text{AIC} = 2 k - 2 \, \text{log}(L) -``` -""" - -# ╔═╡ 03a7e04d-c16f-468d-ba84-314effa6d838 -md""" -!!! tip - To get your model's best possible **AIC** value, you need the highest possible loglikelihood. By definition, this corresponds with your **MLE**. If `opt_res` is the variable returned by the `optimize` function, you can get the correspondig maximal loglikelihood using `opt_res.lp`. -""" - -# ╔═╡ 2e1fb72c-fea9-4de1-bed5-ee947306bc65 -AIC(num_params, loglikelihood) = 2*num_params - 2 * loglikelihood - -# ╔═╡ 3553ed7d-07a0-418a-8d5f-6791e62d9e10 -AIC_exp = AIC(1, exp_res.lp) - -# ╔═╡ 257f0675-c5fe-45c5-aba0-ac808efb0ead -AIC_lognorm = AIC(2, lognorm_res.lp) - -# ╔═╡ 1936f7bb-ad6b-4759-b37e-a9b73a8dabc2 -md"### BIC" - -# ╔═╡ 3867fe78-e317-4d89-9a0b-45068597c015 -md""" -Do the same for the (dissapointingly non-Bayesian) Bayesian Information Criterion (BIC): -""" - -# ╔═╡ cd826270-0b8e-4437-931a-5cb6f01a1e56 -md""" -```math -\text{BIC} = k \, \text{log}(n) - 2 \, \text{log}(L) -``` -""" - -# ╔═╡ 01648f73-8b75-4158-a7ea-00e55ece7548 -BIC(num_observations, num_params, loglikelihood) = num_params*log(num_observations) - - 2 * loglikelihood - -# ╔═╡ 1e685af1-6abe-4849-b07b-2ab1da058edc -BIC_exp = BIC(length(pearlsizes), 1, exp_res.lp) - -# ╔═╡ 23a7d39c-0c0f-43fb-88be-ddfaba988a2c -BIC_lognorm = BIC(length(pearlsizes), 2, lognorm_res.lp) - -# ╔═╡ 4cf6a2d0-e58c-4b80-abf9-525097d75f35 -md"## Overlapping cells" - -# ╔═╡ a1f2199f-29ee-4908-9ad6-bdebcf043de7 -md""" -When counting cells, overlapping cells are a common cause of errors. Here we will tackle a simplified version of the problem where we try to distinguish whether a point cloud originates from one or two circles. -""" - -# ╔═╡ 5407d952-c5a9-4145-8e4a-4b3bb65e6c19 -md""" -![Overlapping cell picture](https://media.springernature.com/full/springer-static/image/art%3A10.1007%2Fs11334-022-00478-y/MediaObjects/11334_2022_478_Fig1_HTML.png?as=webp) -> **Source:** Efficient detection and partitioning of overlapped red blood cells using image processing approach (Dhar 2022) -""" - -# ╔═╡ 54dc6f54-fb0e-4d80-b32c-c5ce8f8974c6 -md"### Data" - -# ╔═╡ bb19f97a-4778-4fc5-8739-1f03bc5416a8 -cell_data = [[0.68 -1.34 -0.53 0.5 -1.85 0.68 0.57 -1.55 0.16 -0.04 1.06 1.34 1.41 -1.67 -1.56 -0.51; -0.36 -1.73 -0.4 -1.5 -0.97 1.62 -3.71 -1.98 0.9 1.55 -1.82 -4.56 2.46 2.18 -1.23 -1.06], [-0.3 -0.29 0.99 2.58 -0.38 -2.16 -1.51 -0.36 0.9 1.27 -0.3 0.77 -0.6 -0.94; 0.73 -0.63 -1.67 0.39 2.15 0.29 -0.91 -2.4 -0.18 2.23 2.05 1.49 -0.16 0.49], [-3.23 -1.51 -2.78 1.1 2.52 0.76 -1.34 -3.79 0.39 0.76; 0.08 0.63 -0.11 2.2 1.48 2.94 -0.82 -0.87 0.38 2.21], [-1.56 0.53 1.02 -0.53 -2.08 -1.22 -0.12 1.04 -0.95 0.74; -0.18 0.04 -1.19 -0.76 -0.58 -0.69 0.88 -1.1 -0.93 1.72], [0.11 0.57 -2.06 1.59 1.45 1.11 -2.2 1.24 0.89 0.67 -0.17 1.21 -0.89 1.01 -0.01 1.9 1.26 -1.48 0.6 -0.74 1.6; -0.45 -0.56 0.53 -0.45 -2.05 2.68 -1.75 0.35 -0.67 -0.44 -0.4 -0.79 -2.12 -2.59 -1.31 -1.66 0.54 -0.2 -3.03 -0.16 -0.56], [-0.21 0.36 -0.89 -0.83 -0.36 -1.75 -2.84 0.46 1.1 3.34 -1.61 0.08; -0.38 -2.23 0.27 -1.6 -2.72 -1.87 -1.48 -0.1 -0.83 0.26 0.46 0.57], [3.15 -0.1 0.77 1.62 -0.5 0.28 0.66 -0.01 1.93 -0.15 -0.94 -0.42 1.79 0.27 -0.01 1.7 0.96 2.35 1.61; -0.05 0.28 0.06 -1.26 1.64 -0.48 0.42 1.47 1.05 0.03 -0.65 -0.74 0.26 0.89 1.43 -0.83 -1.55 -0.48 1.72], [2.16 1.24 3.64 -1.18 1.11 2.4 1.19 1.14 1.26 1.11 0.95 2.14 1.88 1.5 2.43 0.64 1.84 0.05 -0.83 1.5 4.44; -1.13 -0.33 -0.98 0.34 -3.2 0.41 0.77 0.1 1.33 0.76 -0.73 -2.07 0.64 -1.96 -0.7 -1.34 0.84 -2.28 -0.95 -0.28 0.24], [0.38 2.4 2.14 -0.65 -0.23 1.37 0.7 0.74 -0.17 2.53 -1.42 -0.03; 1.25 -2.24 0.0 1.12 -2.23 0.93 -0.86 0.89 -1.61 0.93 -1.51 1.58], [1.53 0.05 -0.39 -1.14 0.04 0.36 0.78 -3.02 -0.28 -2.49 -0.3 -0.55 -1.58 -0.24; 2.5 1.84 -0.67 -1.69 1.57 0.57 1.96 -1.94 3.22 1.47 0.57 0.45 -0.23 0.93]]; - -# ╔═╡ eb552ae5-f32d-4383-8aae-99999ce42552 -md"You can choose the cell picture and visualize the data here." - -# ╔═╡ d3f98c65-de66-4a3c-b087-c2ef34340110 -md"Picture idx" - -# ╔═╡ 0533c731-82b2-4dab-8c5e-d5913ee0f4f3 -@bind picture_idx Slider(1:length(cell_data), show_value = true) - -# ╔═╡ a4c89c22-636c-4621-ac28-b285cf2ecbef -xs, ys = eachrow(cell_data[picture_idx]); - -# ╔═╡ 272327fd-a587-4c14-80cc-d581ff2d7f27 -scatter(xs, ys, xlims = (-5, 5), ylims = (-5, 5)) - -# ╔═╡ f11ca2fe-dc50-41b5-bf9c-299f1b18a9e2 -md"### Model definition" - -# ╔═╡ 65a23129-7fc0-48aa-a9f0-c9f885b7e4c2 -md""" -The model for one cell is defined as follows: -- The points originate from one pointcloud with a centre (`xm`, `ym`). -- `xm` and `ym` both follow a standard Normal distribution. -- All x-values follow a Normal distribution around `xm` with $σ = 1$. -- All y-values follow a Normal distribution around `ym` with $σ = 1$. -""" - -# ╔═╡ c8da1bc6-4455-4577-98bb-d7dd41ff4f06 -@model function singlecell(n) - xm ~ Normal(0, 1) - ym ~ Normal(0, 1) - - xs ~ filldist(Normal(xm, 1.0), n) - ys ~ filldist(Normal(ym, 1.0), n) -end - -# ╔═╡ 523d6280-7147-4652-b593-cdd802d80b4e -md""" -The model for two cells is very similar: -- The points originate from one of two pointclouds, one with centre (`xm1`, `ym1`), the other with centre (`xm2`, `ym2`). -- `xm1`, `ym1`, `xm2` and `ym2` all follow standard Normal distributions. -- All x-values follow either a Normal distribution ($σ = 1$) around `xm1` or `xm2`, with equal chance for either. -- The same idea goes for the y-values. -""" - -# ╔═╡ 740afb3b-4bd6-4516-9a2a-7bbe5f19ccf0 -md""" -!!! hint - To model the likelihood, consider the humble `MixtureModel`. -""" - -# ╔═╡ 166f1c0c-1615-47e7-8538-f19cbdaa6923 -@model function doublecell(n) - xm1 ~ Normal(0, 1) - xm2 ~ Normal(0, 1) - ym1 ~ Normal(0, 1) - ym2 ~ Normal(0, 1) - - xsdist = MixtureModel([Normal(xm1, 1.0), Normal(xm2, 1.0)]) - ysdist = MixtureModel([Normal(ym1, 1.0), Normal(ym2, 1.0)]) - - xs ~ filldist(xsdist, n) - ys ~ filldist(ysdist, n) -end - -# ╔═╡ dab87fa9-0921-48c3-8034-bfa036dc4e0c -my_dist = filldist(Normal(0, 1), 3) - -# ╔═╡ 3f25a8b2-7f68-4bf4-b8c2-48b0c870fc89 -rand(my_dist) - -# ╔═╡ 36093548-5b29-4bd0-959a-befdd4da3de5 -md"Instantiate and condition the models." - -# ╔═╡ bb7acf55-a38a-440c-97b7-f70f5e97c19a -n = length(xs) - -# ╔═╡ a0502199-3e7f-4b50-b383-2e1bb4ff9d41 -singlemodel = singlecell(n) | (xs = xs, ys = ys,); - -# ╔═╡ 153ccedb-b437-4906-a2ef-1745b0dbf53e -doublemodel = doublecell(n) | (xs = xs, ys = ys,); - -# ╔═╡ 29e3dc3a-f2c4-45a6-80a0-01bde4d41d98 -md"### Maximum likelihood" - -# ╔═╡ 5f92804b-49b2-4fa3-a10a-38c66feb3ce8 -function plotsinglecell(xm, ym; bounds = 5) - mydist = MvNormal([xm, ym], [1.0 0.0; 0.0 1.0]) - - xs = -bounds:0.1:bounds - ys = -bounds:0.1:bounds - - f(x,y) = pdf(mydist, [x, y]) - contourf(xs, ys, f, xlims = (-bounds, bounds), ylims = (-bounds, bounds), - color = :viridis, aspect_ratio = :equal, legend = false, - title = "Single cell model" - ) -end - -# ╔═╡ 0977b0e1-092a-4934-9df3-674cdf12b367 -function plotdoublecell(xm1, xm2, ym1, ym2; bounds = 5) - mydist = MixtureModel( - [ - MvNormal([xm1, ym1], [1.0 0.0; 0.0 1.0]), - MvNormal([xm2, ym2], [1.0 0.0; 0.0 1.0]), - ] - ) - - xs = -bounds:0.1:bounds - ys = -bounds:0.1:bounds - - f(x,y) = pdf(mydist, [x, y]) - contourf(xs, ys, f, xlims = (-bounds, bounds), ylims = (-bounds, bounds), - color = :viridis, aspect_ratio = :equal, legend = false, - title = "Two cells model" - ) -end - -# ╔═╡ 15b5d7d1-cf91-4024-a7fd-b8b8f0563dff -md""" -Determine the maximum likelihood estimation (MLE) of the parameter values given the data, using the `NelderMead()` algorithm. -""" - - -# ╔═╡ 8942d992-5084-4c2c-9cbc-c442b0381922 -singleres = optimize(singlemodel, MLE(), NelderMead()) - -# ╔═╡ 99a8a4b4-bbcd-4321-ba41-4f307c04af8e -single_xm, single_ym = coef(singleres)[[:xm, :ym]]; - -# ╔═╡ 51ce7374-c7d8-4a32-815d-1c2ff9ba9970 -doubleres = optimize(doublemodel, MLE(), NelderMead()) - -# ╔═╡ 5a0860a2-8a61-4abb-acaf-3ea3cad0c286 -double_xm1, double_xm2, double_ym1, double_ym2 = coef(doubleres)[[:xm1, :xm2, :ym1, :ym2]]; - -# ╔═╡ 73d259c6-f776-4586-a24f-3ecc368e28ae -md"Visualise the results" - -# ╔═╡ 1a00a07e-db38-4761-b881-7f475881ff4f -begin - plotsinglecell(single_xm, single_ym) - scatter!(xs, ys) -end - -# ╔═╡ 629280c7-b6c8-423d-92e9-815eb76a78f0 -begin - plotdoublecell(double_xm1, double_xm2, double_ym1, double_ym2) - scatter!(xs, ys) -end - -# ╔═╡ 4e28f87c-0953-48c4-8e3a-d7e853cae816 -md"### AIC" - -# ╔═╡ ec4ff409-f28c-4640-8bdb-abe21252afaf -md"Using the MLE results from the previous section, determine the AIC of both models. You can use the implementation from previous exercise." - -# ╔═╡ fef14ffe-3399-48d8-a0cb-998826cdfda4 -AIC_single = AIC(2, singleres.lp) - -# ╔═╡ 40077482-7c98-4ca6-b441-ed3ebd893369 -AIC_double = AIC(4, doubleres.lp) - -# ╔═╡ Cell order: -# ╟─fb07436c-cc5d-4721-8a80-d7f7201721d7 -# ╠═a18638d0-138d-11f0-0571-33060f7da7ba -# ╠═fe265128-33a2-40d1-ab71-bc5c53979a54 -# ╠═c870d28d-7b2e-48f8-8990-b2cc0943cb09 -# ╠═2a73d23c-73fc-4845-b359-c6fe22077140 -# ╠═a397f48e-4228-435e-af13-c2bc71c8cb05 -# ╟─a6635715-52e3-44e7-9c00-0be751f830d6 -# ╟─473c003a-a34b-4e92-8a82-99450514d755 -# ╟─a2dec4e7-7c16-4ae5-974e-8c01ec31507a -# ╟─c4e09491-fd0a-43e7-8e74-04832a045a48 -# ╟─aba7efa9-a0c3-4da4-82a2-bb7efc6b9e22 -# ╟─c8b56408-0710-4646-bb2b-e336e7f689c2 -# ╟─19dd461d-cc03-41c9-a3d1-fcd477d08eb0 -# ╟─01a530db-5e18-4540-99a9-eecd9e61e1af -# ╟─6d39fbde-81c5-4786-99e1-b587bd15f94c -# ╟─fce554e2-a6da-4046-ab4e-9153e27aef7a -# ╠═7b1ce376-d796-4913-a488-ee921bd13855 -# ╟─712756bc-0408-45a6-931b-eec07b6e052f -# ╟─b4de93ea-a919-41c2-bb9c-bc1beccc9ea8 -# ╟─5a6f8d49-3841-46ff-a826-88ba0bed868b -# ╟─88edd0b1-738b-4a86-8995-abdfcc2bcc85 -# ╠═05622800-ccd9-4aa8-9a95-a1fbe34aa76c -# ╠═c5b042bb-c1d9-4b0e-a2bd-a2c367019da3 -# ╟─c72cc55c-a5f7-47f5-b5c3-49af9a5db50d -# ╠═88306e52-d975-4bd5-a8f0-7e9293c9fb82 -# ╠═a7cf9228-7515-4d53-b689-bc711283fecc -# ╟─811ee6ce-bb0f-4bcf-95be-6de95ab9166b -# ╟─392ad62d-b7ef-4951-b729-01e41684cbcf -# ╠═3b16c753-a2ba-4b59-a776-b35c4dd2e927 -# ╠═1ce90419-083a-475a-8334-32d73dafb606 -# ╠═654d0ec1-92b0-4220-91a8-607978f1cf4f -# ╠═17e90886-1a41-4c19-b93a-96d8bd7a075f -# ╠═62f000f3-6f21-4c1e-ab37-29a122b84088 -# ╠═7dc215e7-83ef-430e-9f99-24cada961964 -# ╟─abffd9fb-dd0a-4145-8b7d-8e233e498735 -# ╟─1adee574-150d-4df6-94e0-6aa20ad92389 -# ╟─461c10b2-13f6-442c-ae29-a5f5116a0d01 -# ╟─feee9ca3-05c5-480b-8cb9-19a3e0c536c1 -# ╟─304b0c2e-7169-403d-baf3-141b0b35f560 -# ╠═259e3755-eac2-4bba-8c15-a54e326f365d -# ╠═85a07f1b-df87-4531-ba2a-ccae822379c4 -# ╠═8fce7eb3-1371-4d3b-b9cb-8730e18d2be4 -# ╠═6d39160d-5e5a-4702-bd3f-617d6e7c5513 -# ╠═89547bb1-9027-47c2-a8c0-0212a833879f -# ╠═3a0d70d3-92d6-4254-b77b-58b75947548d -# ╠═c3fc3e53-3f60-4444-a99c-3a0ef7ce5a94 -# ╟─89683590-7f48-4ee8-bc5c-46d37fc0067b -# ╠═8c63219d-e520-40b7-a4b3-c6e2ffc7b17e -# ╠═84f99a01-f3e5-4197-998d-2bdec0d53fb1 -# ╠═37f977de-f1a9-4a83-85f1-8dadf7910025 -# ╟─4995e5a0-b56d-4aa5-b41d-e264936c3bd9 -# ╠═ce13fb27-8b5a-4efd-b415-4d0bfd6d3a6a -# ╟─fd1813b9-d8f2-468b-bd8e-bafdd9dca750 -# ╠═fe5e1b6d-33f2-4357-a40d-4e77845332e0 -# ╠═306333d0-e7e0-4d61-9afb-b5afdc817699 -# ╟─5b34c046-bc87-474d-8c20-b5732c0de219 -# ╟─cc4e9632-53ab-48c9-b179-ca2b10cbfabc -# ╟─964706fe-e48c-4cf6-8e80-484867719bf6 -# ╟─03a7e04d-c16f-468d-ba84-314effa6d838 -# ╠═2e1fb72c-fea9-4de1-bed5-ee947306bc65 -# ╠═3553ed7d-07a0-418a-8d5f-6791e62d9e10 -# ╠═257f0675-c5fe-45c5-aba0-ac808efb0ead -# ╟─1936f7bb-ad6b-4759-b37e-a9b73a8dabc2 -# ╟─3867fe78-e317-4d89-9a0b-45068597c015 -# ╟─cd826270-0b8e-4437-931a-5cb6f01a1e56 -# ╠═01648f73-8b75-4158-a7ea-00e55ece7548 -# ╠═1e685af1-6abe-4849-b07b-2ab1da058edc -# ╠═23a7d39c-0c0f-43fb-88be-ddfaba988a2c -# ╟─4cf6a2d0-e58c-4b80-abf9-525097d75f35 -# ╟─a1f2199f-29ee-4908-9ad6-bdebcf043de7 -# ╟─5407d952-c5a9-4145-8e4a-4b3bb65e6c19 -# ╟─54dc6f54-fb0e-4d80-b32c-c5ce8f8974c6 -# ╟─bb19f97a-4778-4fc5-8739-1f03bc5416a8 -# ╟─eb552ae5-f32d-4383-8aae-99999ce42552 -# ╟─d3f98c65-de66-4a3c-b087-c2ef34340110 -# ╟─0533c731-82b2-4dab-8c5e-d5913ee0f4f3 -# ╠═a4c89c22-636c-4621-ac28-b285cf2ecbef -# ╟─272327fd-a587-4c14-80cc-d581ff2d7f27 -# ╟─f11ca2fe-dc50-41b5-bf9c-299f1b18a9e2 -# ╟─65a23129-7fc0-48aa-a9f0-c9f885b7e4c2 -# ╠═c8da1bc6-4455-4577-98bb-d7dd41ff4f06 -# ╟─523d6280-7147-4652-b593-cdd802d80b4e -# ╟─740afb3b-4bd6-4516-9a2a-7bbe5f19ccf0 -# ╠═166f1c0c-1615-47e7-8538-f19cbdaa6923 -# ╠═dab87fa9-0921-48c3-8034-bfa036dc4e0c -# ╠═3f25a8b2-7f68-4bf4-b8c2-48b0c870fc89 -# ╟─36093548-5b29-4bd0-959a-befdd4da3de5 -# ╠═bb7acf55-a38a-440c-97b7-f70f5e97c19a -# ╠═a0502199-3e7f-4b50-b383-2e1bb4ff9d41 -# ╠═153ccedb-b437-4906-a2ef-1745b0dbf53e -# ╟─29e3dc3a-f2c4-45a6-80a0-01bde4d41d98 -# ╟─5f92804b-49b2-4fa3-a10a-38c66feb3ce8 -# ╟─0977b0e1-092a-4934-9df3-674cdf12b367 -# ╟─15b5d7d1-cf91-4024-a7fd-b8b8f0563dff -# ╠═8942d992-5084-4c2c-9cbc-c442b0381922 -# ╠═99a8a4b4-bbcd-4321-ba41-4f307c04af8e -# ╠═51ce7374-c7d8-4a32-815d-1c2ff9ba9970 -# ╠═5a0860a2-8a61-4abb-acaf-3ea3cad0c286 -# ╟─73d259c6-f776-4586-a24f-3ecc368e28ae -# ╠═1a00a07e-db38-4761-b881-7f475881ff4f -# ╠═629280c7-b6c8-423d-92e9-815eb76a78f0 -# ╟─4e28f87c-0953-48c4-8e3a-d7e853cae816 -# ╟─ec4ff409-f28c-4640-8bdb-abe21252afaf -# ╠═fef14ffe-3399-48d8-a0cb-998826cdfda4 -# ╠═40077482-7c98-4ca6-b441-ed3ebd893369 diff --git a/src/exercises/probmod_1-intro.jl b/src/exercises/probmod_1-intro.jl deleted file mode 100644 index 80110e02..00000000 --- a/src/exercises/probmod_1-intro.jl +++ /dev/null @@ -1,452 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "16" -#> title = "3. ProbMod intro" -#> date = "2025-03-07" -#> tags = ["exercises"] -#> description = "Introduction to the sampling practicals" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Bram Spanoghe" - -using Markdown -using InteractiveUtils - -# ╔═╡ f7cb8633-3b78-4203-92e0-37150d5dcd18 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 8307092d-368d-441d-8315-3dc312026534 -using Turing, StatsPlots - -# ╔═╡ d86c5cc5-5353-4b04-b1d2-06527c85b2db -using PlutoUI; TableOfContents() - -# ╔═╡ aeb0aef0-b2ee-11ef-3cca-7f80b487ea17 -md"# Sampling notebook #1: Intro" - -# ╔═╡ 30957e05-85b8-4106-9635-82a5c11d9825 -md""" -This notebook will guide you through the basics of sampling in Julia. -""" - -# ╔═╡ 13636a8a-8c31-4397-8a7d-5cd7f899d7a5 -md""" -To start off, load the required packages. -""" - -# ╔═╡ 9f89e350-199f-4875-947b-61df653ffc19 -md"## Problem" - -# ╔═╡ 2e544855-16b8-4794-9ba7-70a1e7209dd2 -md""" -Let's go back to the circle throw example from the theory. - -The idea is simple: a circle with radius 1 has an area of π. If you throw darts at the unit square [-1, 1] x [-1, 1] with uniform probability, the probability of a dart landing inside that circle is the area of the circle over the area of the square. - -```math -\begin{align} - -A_{circle} &= π -\\ A_{square} &= (1 - (-1))^2 = 4 -\\ P_{inside} &= \frac{A_{circle}}{A_{square}} = \frac{π}{4} - -\end{align} -``` -""" - -# ╔═╡ 88b3087c-f78a-4df4-a9dc-5696bf4052d5 -md""" -This means if we can estimate this probability, we can estimate π easily! - -```math -\begin{align} - -P_{inside} &= \frac{π}{4} -\\ π &= 4 \, P_{inside} - -\end{align} -``` -""" - -# ╔═╡ 78f872c6-013b-4ad1-966f-9e0ce3288019 -md""" -However simple the problem, this probability is not simple to calculate by hand: - -```math -P_{inside} = \int_{0}^{1} \, \int_0^{\sqrt{1 - y^2}} dx \, dy -``` -""" - -# ╔═╡ bde6033f-15a8-4716-a41e-080f5d48e9d6 -md""" -This, as one may guess, only gets worse for more complex problems. Which is why we use sampling instead! -""" - -# ╔═╡ 1724e4e4-18d2-430d-a8b0-11f5891b09a3 -md""" -!!! note - Are you a real mathhead? Try computing the integral by hand! - - Hint: there should be an inverse tangent function somewhere down the line. -""" - -# ╔═╡ ccdcfa65-a02f-4110-a664-36090c3291d8 -md"## Explanation" - -# ╔═╡ 43e8f794-fec2-4ce4-9306-2fc8a9565343 -md"### Defining the model" - -# ╔═╡ ed788556-1627-4e9a-b901-e532272a8265 -md""" -Turing models are defined as julia functions preceded by the `@model` macro. -Inside of them, you can define random variables with the "`var ~ Distribution(params)`" syntax, aside from doing the usual programming stuff. -""" - -# ╔═╡ 802e2769-613c-4ab6-b0e5-38173c220042 -md""" -Our circle problem can be defined as follows: -- Sample the `x` and `y` coordinates of the dart uniformly between -1 and 1. -- Calculate the distance to the centre of the circle [0, 0]. -- The dart is within the circle if the distance is smaller than the radius 1. -""" - -# ╔═╡ 1ee1ca1b-93dc-4c5e-a1c5-79dc8b365226 -md"This translates to the following in Turing" - -# ╔═╡ 9ea69ca8-328d-4d37-b3f5-40206353d91c -@model function distances() - x ~ Uniform(-1, 1) - y ~ Uniform(-1, 1) - dist = sqrt(x^2 + y^2) - return dist -end - -# ╔═╡ b61eb0af-5f0c-4267-8c02-ab14acb31ece -md"Calling this function will return a Turing model:" - -# ╔═╡ fbf3e659-2b59-4f97-8f88-dc783d384d75 -dist_model = distances(); - -# ╔═╡ f88af70b-0af7-48f5-91dd-39d1bb42f409 -md"### Sampling the model output" - -# ╔═╡ 03840c86-2d22-4288-a202-93f4d141ec47 -md"There's a number of things we can do with the model. The most simple is calling it, which will give the return value of the function after sampling a value for all random variables, here `x` and `y`." - -# ╔═╡ df921cec-a4a4-40de-9537-194b52782411 -dist_model() - -# ╔═╡ 7d9c1044-98f5-4876-ba7e-a2d70de92597 -md"We can use this to generate a large number of samples and make estimations about the probability" - -# ╔═╡ 62952198-602c-49a3-81d5-890588b7262a -n_samples = 1_000 - -# ╔═╡ 1012cee3-e574-43d9-b6ed-c9aa8a5ec552 -sp_dists = [dist_model() for sample_idx in 1:n_samples] - -# ╔═╡ 4084cee7-1d2d-4fc0-8226-d9d416bb4eec -histogram(sp_dists, title = "Distances of points to origin", bins = 20, legend = nothing) - -# ╔═╡ 321c50fc-36ff-438c-b98e-a14910714bca -md"Currently, we have samples of the distance to the origin. We can easily transform these to samples of being inside the circle or not, and subsequently estimate the desired probability." - -# ╔═╡ 1df2ac6a-fa56-47e2-8c10-2a5d4da3a475 -sp_inside = sp_dists .<= 1 # the circle has a radius of 1 - -# ╔═╡ 418e1de5-74ad-47d5-8500-4996a86c8e53 -md""" -!!! note - We may as well have checked whether the distance was smaller than 1 **inside of the Turing function** and returned that instead. Calling the model would have then immediately given us `sp_inside`. - - While that approach is also perfectly fine, it would have made visualising the distribution of distances more difficult. -""" - -# ╔═╡ 8c95747f-08dc-49e7-a9c7-9a5c5fef7a78 -begin - histogram(sp_dists[sp_inside], title = "Distances of points to origin", label = "Inside circle", bins = 15) - histogram!(sp_dists[.!sp_inside], label = "Not inside circle", bins = 5) -end - -# ╔═╡ 7d654226-04bd-4721-bc4b-26912fc87ba7 -prob_inside = length(sp_dists[sp_inside]) / length(sp_dists) - -# ╔═╡ f2bb0f76-836c-40eb-8fa9-f4fcf8c03671 -mean(sp_inside) # shorter alternative - -# ╔═╡ 73278ad6-00f2-4e57-8c17-4e0a47be5d57 -4*prob_inside - -# ╔═╡ 19162953-a1d6-4b3b-962e-947e36b032c5 -md""" -!!! note - How many samples do you need to get that beautiful `3.14` consistently? How about the yet even more charming `3.1415`? -""" - -# ╔═╡ c79118af-a437-4979-a857-a6c84e1f789a -md"### Sampling everything with `sample`" - -# ╔═╡ 55206a66-fda0-4299-95f0-6dcc32287f3a -md""" -An alternate way to generate a number of samples from our model is to use the `sample` function. Rather than getting samples of the function's output, this returns sampled values of _all the random variables_. -""" - -# ╔═╡ 609af082-ffcc-4e8f-99ab-825b451d6d2b -md""" -The inputs for the function `sample` are: -- the Turing model. -- the sampler. -- the desired amount of samples. -The second argument, the choice of sampler, is mostly important when doing **inference**, as we'll see in practical 4. When we want to simply sample from the model's priors without any inference, we use the `Prior()` sampler. -""" - -# ╔═╡ 8ef27c2f-31fe-4411-9d2f-a65041e07641 -dist_chain = sample(dist_model, Prior(), n_samples) - -# ╔═╡ 469a3fad-b491-4ee6-88a9-cfc47c78700a -md""" -!!! note - The `lp` column in the above output of `sample` is the **log probability** of that sample. This example uses 2 continuous distributions with an interval of size 2, so the value is always the same: - $\text{ln}(P(X = x) * P(Y = y)) = \text{ln}(1/2 * 1/2) = \text{ln}(1/4) = -1.39$ -""" - -# ╔═╡ 95d50ada-1d2b-47af-8447-c6a465e59025 -md""" -The samples are visualised below: -""" - -# ╔═╡ ee6a2c1d-5522-4870-a0e1-664f8ccd5d8f -scatter(dist_chain[:x], dist_chain[:y], aspect_ratio = :equal, label = "Dart locations") - -# ╔═╡ 563e0fa0-25b9-4b36-b213-c952199caa32 -md"The sample values of a random variable can be acquired by indexing the resulting chain with the variable's name as a `Symbol` or `String`:" - -# ╔═╡ 3994fc44-5fda-47dc-8cc6-e2550d9c237d -dist_chain[:x] # or dist_chain["x"] - -# ╔═╡ eb4b6d56-1e41-4e75-914b-8e7444afb288 -md"This can be useful for making plots, for example." - -# ╔═╡ 8c3f32ac-c4fd-416f-a798-bbfe9977e5fc -md"#### The `generated_quantities` function" - -# ╔═╡ 3bfaf571-2aa8-4b13-8c48-e8434407597b -md"What if we want the function's return value too? We could calculate it based on our random variables by hand as `sqrt.(dist_chain[:x].^2 + dist_chain[:y].^2)`, or use the `generated_quantities` function." - -# ╔═╡ 0dcc8d6f-3ab2-429d-bcf4-f022ea5d0124 -sp_dists_alt = generated_quantities(dist_model, dist_chain) - -# ╔═╡ 5a8adeb4-46cf-486f-adda-24661a79b2e9 -sp_inside_alt = sp_dists_alt .<= 1 - -# ╔═╡ 28c48f45-726e-4352-b3b2-416dbab9cb0d -scatter(dist_chain[:x], dist_chain[:y], aspect_ratio = :equal, groups = vec(sp_inside_alt), label = ["Outside of circle" "Inside of circle"]) -# Note: The `sample` method returns matrices. For plotting, vectors are often preferred, which is why we convert `sp_inside_alt` to a vector here. - -# ╔═╡ ab230de4-767f-43e8-8bdc-054234852715 -md"### For-loops for many variables" - -# ╔═╡ bf0904ce-fb72-4a39-a57b-1c99c8e9c82e -md"A common problem when defining the problem as a Turing model is many random variables being involved, often with the same distribution. Turing allows variables to be defined in a for-loop for this reason. - -Consider the circle example again but using a loop this time, which can easily be generalized to $n$ dimensions: -" - -# ╔═╡ 4c54bccf-c674-41da-be15-edac57f96ee9 -@model function distances_loop() - coords = zeros(2) # initiatilize a vector of length 2 filled with zeros - for i in 1:length(coords) - coords[i] ~ Uniform(-1, 1) - # assign every element of the vector a random variable - end - - dist = sqrt(sum(coords.^2)) -end - -# ╔═╡ c50b7bb9-57e9-4f86-a0fa-0a68488b2fa9 -distloop_model = distances_loop(); - -# ╔═╡ 330531d8-c0cf-4d0f-84b3-42acf9e30b39 -sp_loop = [distloop_model() for i in 1:n_samples] - -# ╔═╡ 9d786f74-73de-4fe3-8842-fa394641ce29 -4*mean(sp_loop .<= 1) - -# ╔═╡ 9ae88cb6-d74a-4d28-ae5f-e1cf325b6acb -md"If you want to retrieve one of the random variables using `sample`, you can simply index the output as follows:" - -# ╔═╡ eb295f23-1afd-4966-a55d-ba10b3f9b72b -loop_chain = sample(distloop_model, Prior(), n_samples); - -# ╔═╡ f62cf74f-4572-4374-84c6-79b502feacb4 -loop_chain["coords[1]"] - -# ╔═╡ 6faecd7e-fa99-4616-86c5-2561985f98a1 -md"### Working with Distributions" - -# ╔═╡ 9621ceec-c532-4ef5-883d-da67af13bfa1 -md""" -Under the hood, Turing makes use of Julia's `Distributions` package. Knowing some basic functionality of this package can be useful. -""" - -# ╔═╡ 996e876a-8e2d-44d4-bd3a-7a8d4fd8c6ea -md""" -!!! note - Turing automatically loads Distributions into the workspace, so "`using Distributions`" is not necessary when Turing has been loaded. -""" - -# ╔═╡ e8e65be5-b6b2-4fd6-bfb3-c277433b7206 -md"Considering the humble example of `X ~ Exponential(10)`, let's do some plotting, sampling and calculating." - -# ╔═╡ 398e15bb-7cc4-4f61-919b-9edf790cacd3 -plot(Exponential(10)) - -# ╔═╡ 58a6ffb1-0fef-4edb-9fbe-de5b399b013d -spX = rand(Exponential(10), n_samples) - -# ╔═╡ 6b3ac000-d31b-46ef-89bd-830c2cae8cdb -histogram(spX) - -# ╔═╡ 84b9eb6b-d18a-4394-afff-3e49293aa1d4 -pdf(Exponential(10), 0) - -# ╔═╡ b577685b-00fb-4869-b930-1ec6cd163f7d -cdf(Exponential(10), 20) - -# ╔═╡ 03ebb0a1-4fff-4d87-8d86-4d409d09363d -md"Just for fun, we can work out the circle example again without Turing." - -# ╔═╡ 48fe2927-8c79-4482-8edc-21d8e0819619 -begin - -sp_dists_noturing = zeros(n_samples) -for i in 1:n_samples - x = rand(Uniform(0, 1)) - y = rand(Uniform(0, 1)) - dist = x^2 + y^2 - sp_dists_noturing[i] = dist -end - -sp_inside_noturing = sp_dists_noturing .<= 1 -4 * mean(sp_inside_noturing) - -end - -# ╔═╡ 5acc6791-7cb6-4a31-a28d-649e761329ee -md"## The essentials" - -# ╔═╡ f3a73486-485a-4c69-b3db-2153b5a06bd8 -md""" -The most essential code for the first practical is reiterated here without long explanations to provide an easy reference for making the practical exercises. - -Side note: the code is wrapped in a `let` block so Pluto won't complain about the same variable names being used again. -""" - -# ╔═╡ 51f9b6f9-9415-4030-82a6-32f9742bf7f5 -# ╠═╡ disabled = true -#=╠═╡ -let - n_samples = 1000 - - @model function distances() - x ~ Uniform(-1, 1) - y ~ Uniform(-1, 1) - dist = sqrt(x^2 + y^2) - return dist - end - - dist_model = distances(); # instantiate model - sp_dists = [dist_model() for sample_idx in 1:n_samples]; - # make sample (= steekproef or `sp`) - sp_inside = sp_dists .<= 1; - # transform into sample of whether point is inside the circle - prob_inside = mean(sp_inside) # =(amount of points in circle)/(amount of points) - println("Pi is estimated as $(4 * prob_inside)") - - # alternative way of generating the same sample - dist_chain = sample(dist_model, Prior(), n_samples); - sp_dists2 = generated_quantities(dist_model, dist_chain); - sp_x = dist_chain[:x] # this method allows recovery of stochastic variables - sp_y = dist_chain[:y] - scatter(sp_x, sp_y, group = vec(sp_dists2 .<= 1), - legend = false, aspect_ratio = :equal) # which is nice for plotting -end - ╠═╡ =# - -# ╔═╡ Cell order: -# ╟─aeb0aef0-b2ee-11ef-3cca-7f80b487ea17 -# ╟─30957e05-85b8-4106-9635-82a5c11d9825 -# ╟─13636a8a-8c31-4397-8a7d-5cd7f899d7a5 -# ╠═f7cb8633-3b78-4203-92e0-37150d5dcd18 -# ╠═8307092d-368d-441d-8315-3dc312026534 -# ╠═d86c5cc5-5353-4b04-b1d2-06527c85b2db -# ╟─9f89e350-199f-4875-947b-61df653ffc19 -# ╟─2e544855-16b8-4794-9ba7-70a1e7209dd2 -# ╟─88b3087c-f78a-4df4-a9dc-5696bf4052d5 -# ╟─78f872c6-013b-4ad1-966f-9e0ce3288019 -# ╟─bde6033f-15a8-4716-a41e-080f5d48e9d6 -# ╟─1724e4e4-18d2-430d-a8b0-11f5891b09a3 -# ╟─ccdcfa65-a02f-4110-a664-36090c3291d8 -# ╟─43e8f794-fec2-4ce4-9306-2fc8a9565343 -# ╟─ed788556-1627-4e9a-b901-e532272a8265 -# ╟─802e2769-613c-4ab6-b0e5-38173c220042 -# ╟─1ee1ca1b-93dc-4c5e-a1c5-79dc8b365226 -# ╠═9ea69ca8-328d-4d37-b3f5-40206353d91c -# ╟─b61eb0af-5f0c-4267-8c02-ab14acb31ece -# ╠═fbf3e659-2b59-4f97-8f88-dc783d384d75 -# ╟─f88af70b-0af7-48f5-91dd-39d1bb42f409 -# ╟─03840c86-2d22-4288-a202-93f4d141ec47 -# ╠═df921cec-a4a4-40de-9537-194b52782411 -# ╟─7d9c1044-98f5-4876-ba7e-a2d70de92597 -# ╠═62952198-602c-49a3-81d5-890588b7262a -# ╠═1012cee3-e574-43d9-b6ed-c9aa8a5ec552 -# ╟─4084cee7-1d2d-4fc0-8226-d9d416bb4eec -# ╟─321c50fc-36ff-438c-b98e-a14910714bca -# ╠═1df2ac6a-fa56-47e2-8c10-2a5d4da3a475 -# ╟─418e1de5-74ad-47d5-8500-4996a86c8e53 -# ╟─8c95747f-08dc-49e7-a9c7-9a5c5fef7a78 -# ╠═7d654226-04bd-4721-bc4b-26912fc87ba7 -# ╠═f2bb0f76-836c-40eb-8fa9-f4fcf8c03671 -# ╠═73278ad6-00f2-4e57-8c17-4e0a47be5d57 -# ╟─19162953-a1d6-4b3b-962e-947e36b032c5 -# ╟─c79118af-a437-4979-a857-a6c84e1f789a -# ╟─55206a66-fda0-4299-95f0-6dcc32287f3a -# ╟─609af082-ffcc-4e8f-99ab-825b451d6d2b -# ╠═8ef27c2f-31fe-4411-9d2f-a65041e07641 -# ╟─469a3fad-b491-4ee6-88a9-cfc47c78700a -# ╟─95d50ada-1d2b-47af-8447-c6a465e59025 -# ╟─ee6a2c1d-5522-4870-a0e1-664f8ccd5d8f -# ╟─563e0fa0-25b9-4b36-b213-c952199caa32 -# ╠═3994fc44-5fda-47dc-8cc6-e2550d9c237d -# ╟─eb4b6d56-1e41-4e75-914b-8e7444afb288 -# ╟─8c3f32ac-c4fd-416f-a798-bbfe9977e5fc -# ╟─3bfaf571-2aa8-4b13-8c48-e8434407597b -# ╠═0dcc8d6f-3ab2-429d-bcf4-f022ea5d0124 -# ╠═5a8adeb4-46cf-486f-adda-24661a79b2e9 -# ╟─28c48f45-726e-4352-b3b2-416dbab9cb0d -# ╟─ab230de4-767f-43e8-8bdc-054234852715 -# ╟─bf0904ce-fb72-4a39-a57b-1c99c8e9c82e -# ╠═4c54bccf-c674-41da-be15-edac57f96ee9 -# ╠═c50b7bb9-57e9-4f86-a0fa-0a68488b2fa9 -# ╠═330531d8-c0cf-4d0f-84b3-42acf9e30b39 -# ╠═9d786f74-73de-4fe3-8842-fa394641ce29 -# ╟─9ae88cb6-d74a-4d28-ae5f-e1cf325b6acb -# ╠═eb295f23-1afd-4966-a55d-ba10b3f9b72b -# ╠═f62cf74f-4572-4374-84c6-79b502feacb4 -# ╟─6faecd7e-fa99-4616-86c5-2561985f98a1 -# ╟─9621ceec-c532-4ef5-883d-da67af13bfa1 -# ╟─996e876a-8e2d-44d4-bd3a-7a8d4fd8c6ea -# ╟─e8e65be5-b6b2-4fd6-bfb3-c277433b7206 -# ╠═398e15bb-7cc4-4f61-919b-9edf790cacd3 -# ╠═58a6ffb1-0fef-4edb-9fbe-de5b399b013d -# ╠═6b3ac000-d31b-46ef-89bd-830c2cae8cdb -# ╠═84b9eb6b-d18a-4394-afff-3e49293aa1d4 -# ╠═b577685b-00fb-4869-b930-1ec6cd163f7d -# ╟─03ebb0a1-4fff-4d87-8d86-4d409d09363d -# ╠═48fe2927-8c79-4482-8edc-21d8e0819619 -# ╟─5acc6791-7cb6-4a31-a28d-649e761329ee -# ╟─f3a73486-485a-4c69-b3db-2153b5a06bd8 -# ╠═51f9b6f9-9415-4030-82a6-32f9742bf7f5 diff --git a/src/exercises/probmod_2-basics.jl b/src/exercises/probmod_2-basics.jl deleted file mode 100644 index 07efe6d2..00000000 --- a/src/exercises/probmod_2-basics.jl +++ /dev/null @@ -1,374 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "17" -#> title = "3. ProbMod basics" -#> date = "2025-03-07" -#> tags = ["exercises"] -#> description = "Basic sampling exercises" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Bram Spanoghe" - -using Markdown -using InteractiveUtils - -# ╔═╡ 84d9771c-75b1-4d22-a1cd-b9078866a836 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 4cfd4721-e29a-4270-8d15-021bcc966eb1 -using Turing, StatsPlots - -# ╔═╡ 73c2a5db-4019-4d91-b5f1-7ba378cb8c84 -using PlutoUI; TableOfContents() - -# ╔═╡ e4cb065e-12c6-4f1c-8497-1013fa9411d6 -md"# Sampling notebook #2: Basics" - -# ╔═╡ 7026f66f-9076-4aef-ada9-198450ef5da6 -md"## 1: Double Poisson" - -# ╔═╡ bb682b51-c3ac-4e31-9b79-4c13212d84e5 -md""" -Let `X ∼ Poisson(10)` and `Y ~ Poisson(X)`. - -1. Plot the (exact) distribution of `X` and use sampling (n = 10_000) to generate a histogram of `Y`. -2. Estimate the following probabilities: - - `P(3 < Y ≤ 10)`. - - `P(Y^2 > 100)`. -3. Consider `var(X|Y=15)` and `var(Y|X=15)`. - - Estimate them numerically. - - One of the two has a simple analytical answer: which one is it, and what is its exact value? -""" - -# ╔═╡ def27d26-2205-4a66-94f3-eddbc17483bf -md"### 1: Plots" - -# ╔═╡ ff38df99-f843-414d-8e45-b46e06a65c22 -@model function doublepoisson() - X ~ missing - Y ~ missing - return Y -end - -# ╔═╡ 42c18a70-efb3-436b-83bb-b586280d4a4e -dpmodel = doublepoisson(); - -# ╔═╡ b22a18d5-f70b-42a5-a09e-3de515148a6d -spY = missing - -# ╔═╡ 34bd60df-272f-49d7-9346-fb4d125fe89b -histogram(spY) - -# ╔═╡ 13989ba4-bcf8-4fdd-8aee-ab58c8905bc9 -md"### 2: Probabilities" - -# ╔═╡ aba42086-224f-44a0-b616-f8f651afdd18 -md""" -!!! tip - When comparing a vector of values to a single number, don't forget to use `.` to execute operations element-wise in Julia! - - ✅ `spY .< 1` compares every element of `spY` to `1` - - ❌ `spY < 1` compares an entire vector with a single number → errors :( -""" - -# ╔═╡ 39e4f3eb-b7a9-4ece-846f-eb02dbd77860 -probXY1 = missing - -# ╔═╡ acca0e39-612f-43b8-9c25-74c143041978 -probXY2 = missing - -# ╔═╡ c243ca59-191d-4905-825e-6d7825a3c8a4 -md"### 3: Variances" - -# ╔═╡ 137a1727-08ab-4455-ad8e-88bbead80845 -md""" -!!! hint - To create a sample of $X$ that is conditional on some value(s) of $Y$, you can start from a sample of $X$ and select only those elements for which the corresponding sample of $Y$ has the conditioned value(s). - - In other words, you'll need to index `spX` based on `spY` (and vice versa for $\text{var}(Y ∣ X)$). -""" - -# ╔═╡ 263048e5-206c-499e-836c-bfe489ed9b74 -spX = missing; - -# ╔═╡ aedd0fe8-da3e-4463-b0cf-7c4f9a22db52 -varXcondY = missing - -# ╔═╡ 7ef53a87-e5df-4724-b896-3d1d46214c68 -varYcondX = missing - -# ╔═╡ ce9e2ce2-26e0-4f17-adf5-c922ba98239d -missing # analytical answer of (missing) - -# ╔═╡ ce7d57ed-4f31-4dcf-af3b-b37a2e2a9393 -md"## 2: Combinations" - -# ╔═╡ 087994ce-a26d-40c4-87eb-ef9f0ce7f1fb -md""" -Let `U ~ Uniform(0, 4)`, `V ∼ Normal(U, 1)` and `W ~ TriangularDist(0, 4, U)`. -1. Use sampling (n = 10_000) to make a histogram of `|V − W|`. -2. Estimate `P(V > W)` and `P(V * W >= 10)` -3. Are `V` and `W` independent? -""" - -# ╔═╡ 9e5cc347-c74f-46a3-9534-c5ad812844bf -md"### 1: Histogram" - -# ╔═╡ 47a43282-3892-4a9a-94b7-c359fa74e12b -@model function combinations() - U ~ missing - V ~ missing - W ~ missing -end - -# ╔═╡ b0502f20-17af-4ecc-be80-a26b3e42d57f -spVW = missing - -# ╔═╡ faa105b6-0700-4f4d-92fe-2bb72a4d6e44 -md"### 2: Probabilities" - -# ╔═╡ 97aec4a9-2954-4967-bc15-c0123bac2e75 -probVW1 = missing - -# ╔═╡ 30166840-d5f5-4a2a-acc2-cde76a87e95a -probVW2 = missing - -# ╔═╡ 4decd959-aeb9-47d4-a381-14bbf4dbc5ab -md"### 3: Independence" - -# ╔═╡ aa953baa-5105-49d7-82e6-94ca462624f7 -md""" -!!! hint - One way to prove dependence is showing that $E[V] \neq E[V \mid W \leq w]$ for at least one value $w$. - - If the expected value of $V$ can change based on some information about $W$, they can't be independent! -""" - -# ╔═╡ 9740ea64-cd4f-46b1-a741-02e392280601 -md""" -## 3: Dice -""" - -# ╔═╡ 187854bb-9e30-454d-9e03-cccf77aebb6b -md"You're playing a fun game of Caverns and Chimeras, and are facing off against the mighty Carl the Chimera. The fight is not going great and your next spell **needs to deal 50 or more damage** to slay the scary monster before it kills you. Spells deal **damage equal to the sum of the dice** they let you roll. - -You can choose between your 2 mightiest spells: -- **Watercube**: lets you throw **4 dice with 20 sides** each. -- **Dirtprism**: lets you throw **20 dice with 4 sides** each. -" - -# ╔═╡ 747e3c0a-357a-448a-b479-d0fcbe44a6c0 -md""" -!!! questions - 1. What is the probability that Watercube does the job? Also plot a histogram of its damage. - 1. Do the same for Dirtprism. - 1. What is the probability that watercube deals more damage than dirtprism? -""" - -# ╔═╡ eda95f45-083a-4e65-b57e-bd9890da1f9c -md"### 1: Watercube" - -# ╔═╡ 4fefa78b-746d-4e25-baee-d791eb22d930 -md""" -!!! hint - Consider the humble `DiscreteUniform` distribution. Not sure how it works? Open the **🔍 Live Docs** at the bottom right of the screen for more information -""" - -# ╔═╡ 36477423-5628-4ed3-b54d-9a050557f6b7 -@model function watercube() - roll1 ~ missing - roll2 ~ missing - roll3 ~ missing - roll4 ~ missing - - dicesum = roll1 + roll2 + roll3 + roll4 - return dicesum -end - -# ╔═╡ 02bc37e2-5cf6-404a-b3fe-b2120671adb2 -watermodel = watercube() - -# ╔═╡ b5886255-5c1d-4d84-b7ee-6c690fa526dc -p_watercube_kills = missing - -# ╔═╡ 84e06162-e3f1-4fd7-baf6-5095172413d2 -missing # histogram - -# ╔═╡ a1b933ac-5d1b-4800-a6e8-e942846b19d8 -md"### 2: Dirtprism" - -# ╔═╡ 6b009ba3-83a8-4176-86d4-dd9f70ed29ec -@model function dirtprism() - # check the "For-loops for many variables" section from the intro notebook! - - dicesum = missing # consider the `sum` function - return dicesum -end - -# ╔═╡ 83afb3c1-9e6a-4d18-b0c7-05ed0173df40 -dirtmodel = dirtprism() - -# ╔═╡ d9152416-8a7b-480c-ba9f-7ab15404b7a6 -p_dirtprism_kills = missing - -# ╔═╡ 90e058d7-b3fe-4c42-a652-3c42bf9d851a -missing # histogram - -# ╔═╡ 49790a8f-9f53-4ba7-9543-d6a879b520e0 -md"### 3: Comparison" - -# ╔═╡ 9271b6df-fa3e-4b79-8f0f-48a3b1287b42 -p_watercube_is_better = missing - -# ╔═╡ 34f3014f-f4d4-43d1-b46f-bdca73aee33f -md"## 4: Super eggs" - -# ╔═╡ 372436c4-262f-49b8-b1cf-626b043542bf -md""" -When a chicken lays an egg, there's a small chance it contains two egg yolks. This chance, as well as the number of eggs a chicken lays per year, go down as the chicken gets older. -""" - -# ╔═╡ 20111742-008a-44c3-8c27-62791cce3e1e -md""" -You can make the following assumptions -- The age $A$ of a random chicken (in years) is discrete and Uniformly distributed between 0 and 12. -- The number of eggs $N$ an $A$-year old chicken lays in a year is Poisson distributed with mean $300 - 20 \, A$. -- The probability $P$ of an $A$-year old chicken's egg having a double yolk is distributed as a `Beta(1, 800 + 100*A)`. -""" - -# ╔═╡ 6e020801-983d-4ebc-a0e9-b5dd58f66c55 -md""" -!!! questions - 1. If someone hands you a random chicken, what is the probability it will lay 2 or more double eggs in a year? - 1. Compare the distributions of double eggs for 1-year old and 3-year old chickens. -""" - -# ╔═╡ 98fcfcef-bf63-4eae-a325-ed4cef6d4fdd -md"### 1: Probability" - -# ╔═╡ a0341046-c14a-494d-a9bd-a60c207c9e76 -md""" -!!! hint - In this exercise, the output variable (the number of double-yolked eggs) is **also a random variable**! In other words, it also follows some distribution. - - When considering what distribution, consider that each of the $N$ eggs represents a "trial" with a $P$ chance of success for a double yolk. -""" - -# ╔═╡ 2b3d930f-53d9-4869-9e4a-86a1a681b9d8 -@model function eggs() - return missing -end - -# ╔═╡ 49d274f7-5810-48b1-8954-22b6a0941a47 -p_multiple_double_eggs = missing - -# ╔═╡ 8dcdc5d7-07b5-4041-b955-485b6f830b75 -md"### 2: Histograms" - -# ╔═╡ cee7c02a-62db-4181-8321-b8bea8fb9339 -missing # histogram 1 - -# ╔═╡ 6b3227c5-78aa-4031-b321-f938757d5ad8 -missing # histogram 2 - -# ╔═╡ ff06c070-50a2-43d0-9729-1c47e728ff52 -md"## 5: Birthdays" - -# ╔═╡ 6ac2238a-16fd-4a8d-b779-8627d87367ed -md""" -Sometimes, people are born on the same day of the year. -""" - -# ╔═╡ 01648616-bf50-4f66-82fc-eaae3de22a38 -md""" -!!! question - What is the probability that, in a class of 150 students, 3 or more share a birthday? -""" - -# ╔═╡ da44d18c-8be3-446e-a5c2-905af545d2c6 -md""" -!!! tip - You can solve this (among other possibilities) using either a for-loop and the `count_occurences` function given below, or the `Multinomial` distribution. -""" - -# ╔═╡ 52cf545a-d7c7-41d8-ad89-617d2f8b3eb9 -count_occurences(vec) = [count(==(element), vec) for element in unique(vec)] - -# ╔═╡ 0a6ff75d-fbc7-48a1-924b-e16d2654749c -count_occurences([5, 107, 364, 5, 5, 364]) # three 5's, one 107 and two 364's - -# ╔═╡ 2925bb92-32f8-415d-960f-b0a60a1037b8 -@model function birthdays() - missing -end - -# ╔═╡ Cell order: -# ╟─e4cb065e-12c6-4f1c-8497-1013fa9411d6 -# ╠═84d9771c-75b1-4d22-a1cd-b9078866a836 -# ╠═4cfd4721-e29a-4270-8d15-021bcc966eb1 -# ╠═73c2a5db-4019-4d91-b5f1-7ba378cb8c84 -# ╟─7026f66f-9076-4aef-ada9-198450ef5da6 -# ╟─bb682b51-c3ac-4e31-9b79-4c13212d84e5 -# ╟─def27d26-2205-4a66-94f3-eddbc17483bf -# ╠═ff38df99-f843-414d-8e45-b46e06a65c22 -# ╠═42c18a70-efb3-436b-83bb-b586280d4a4e -# ╠═b22a18d5-f70b-42a5-a09e-3de515148a6d -# ╠═34bd60df-272f-49d7-9346-fb4d125fe89b -# ╟─13989ba4-bcf8-4fdd-8aee-ab58c8905bc9 -# ╟─aba42086-224f-44a0-b616-f8f651afdd18 -# ╠═39e4f3eb-b7a9-4ece-846f-eb02dbd77860 -# ╠═acca0e39-612f-43b8-9c25-74c143041978 -# ╟─c243ca59-191d-4905-825e-6d7825a3c8a4 -# ╟─137a1727-08ab-4455-ad8e-88bbead80845 -# ╠═263048e5-206c-499e-836c-bfe489ed9b74 -# ╠═aedd0fe8-da3e-4463-b0cf-7c4f9a22db52 -# ╠═7ef53a87-e5df-4724-b896-3d1d46214c68 -# ╠═ce9e2ce2-26e0-4f17-adf5-c922ba98239d -# ╟─ce7d57ed-4f31-4dcf-af3b-b37a2e2a9393 -# ╟─087994ce-a26d-40c4-87eb-ef9f0ce7f1fb -# ╟─9e5cc347-c74f-46a3-9534-c5ad812844bf -# ╠═47a43282-3892-4a9a-94b7-c359fa74e12b -# ╠═b0502f20-17af-4ecc-be80-a26b3e42d57f -# ╟─faa105b6-0700-4f4d-92fe-2bb72a4d6e44 -# ╠═97aec4a9-2954-4967-bc15-c0123bac2e75 -# ╠═30166840-d5f5-4a2a-acc2-cde76a87e95a -# ╟─4decd959-aeb9-47d4-a381-14bbf4dbc5ab -# ╟─aa953baa-5105-49d7-82e6-94ca462624f7 -# ╟─9740ea64-cd4f-46b1-a741-02e392280601 -# ╟─187854bb-9e30-454d-9e03-cccf77aebb6b -# ╟─747e3c0a-357a-448a-b479-d0fcbe44a6c0 -# ╟─eda95f45-083a-4e65-b57e-bd9890da1f9c -# ╟─4fefa78b-746d-4e25-baee-d791eb22d930 -# ╠═36477423-5628-4ed3-b54d-9a050557f6b7 -# ╠═02bc37e2-5cf6-404a-b3fe-b2120671adb2 -# ╠═b5886255-5c1d-4d84-b7ee-6c690fa526dc -# ╠═84e06162-e3f1-4fd7-baf6-5095172413d2 -# ╟─a1b933ac-5d1b-4800-a6e8-e942846b19d8 -# ╠═6b009ba3-83a8-4176-86d4-dd9f70ed29ec -# ╠═83afb3c1-9e6a-4d18-b0c7-05ed0173df40 -# ╠═d9152416-8a7b-480c-ba9f-7ab15404b7a6 -# ╠═90e058d7-b3fe-4c42-a652-3c42bf9d851a -# ╟─49790a8f-9f53-4ba7-9543-d6a879b520e0 -# ╠═9271b6df-fa3e-4b79-8f0f-48a3b1287b42 -# ╟─34f3014f-f4d4-43d1-b46f-bdca73aee33f -# ╟─372436c4-262f-49b8-b1cf-626b043542bf -# ╟─20111742-008a-44c3-8c27-62791cce3e1e -# ╟─6e020801-983d-4ebc-a0e9-b5dd58f66c55 -# ╟─98fcfcef-bf63-4eae-a325-ed4cef6d4fdd -# ╟─a0341046-c14a-494d-a9bd-a60c207c9e76 -# ╠═2b3d930f-53d9-4869-9e4a-86a1a681b9d8 -# ╠═49d274f7-5810-48b1-8954-22b6a0941a47 -# ╟─8dcdc5d7-07b5-4041-b955-485b6f830b75 -# ╠═cee7c02a-62db-4181-8321-b8bea8fb9339 -# ╠═6b3227c5-78aa-4031-b321-f938757d5ad8 -# ╟─ff06c070-50a2-43d0-9729-1c47e728ff52 -# ╟─6ac2238a-16fd-4a8d-b779-8627d87367ed -# ╟─01648616-bf50-4f66-82fc-eaae3de22a38 -# ╟─da44d18c-8be3-446e-a5c2-905af545d2c6 -# ╠═52cf545a-d7c7-41d8-ad89-617d2f8b3eb9 -# ╠═0a6ff75d-fbc7-48a1-924b-e16d2654749c -# ╠═2925bb92-32f8-415d-960f-b0a60a1037b8 diff --git a/src/exercises/probmod_3-advanced.jl b/src/exercises/probmod_3-advanced.jl deleted file mode 100644 index ac95b401..00000000 --- a/src/exercises/probmod_3-advanced.jl +++ /dev/null @@ -1,269 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "18" -#> title = "3. ProbMod advanced" -#> date = "2025-03-07" -#> tags = ["exercises"] -#> description = "Advanced sampling exercises" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Bram Spanoghe" - -using Markdown -using InteractiveUtils - -# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). -macro bind(def, element) - #! format: off - quote - local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end - local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) - el - end - #! format: on -end - -# ╔═╡ df95b3d0-9f2d-4267-ad8c-837c7848a871 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 80bc0e86-5ad3-4d61-9600-8dc05b86599d -using Turing, StatsPlots - -# ╔═╡ fe68a4dd-038c-4f94-a4f0-48933ff2fa87 -using PlutoUI; TableOfContents() - -# ╔═╡ 52a38b60-178b-4a1d-ac32-e73fafd339f9 -md"# Sampling notebook #3: Advanced" - -# ╔═╡ 116b840c-e766-4ff6-aafa-0977fb122992 -md"## 1: Petridish peril" - -# ╔═╡ 415b5ba8-3f6d-46ea-8f89-19fa7c0e74f9 -md""" -Living the microbiology master thesis life, your mornings consist of inoculating petridishes with bacteria. Somewhere along the day, you need to split them. You want to do this **after** there's a decent amount of bacteria in the dish (>10\_000) but **before** they have overgrown the entire dish and start dying (<100\_000). This condition we call **splittable**. - -You'd like to estimate how long after inoculation you should return to your bacteria so that they're most likely to be in a splittable state. -""" - -# ╔═╡ 3691d6aa-c717-46e8-b8b3-f4aa56c9f761 -md""" -Bacteria follow **logistic growth**, and you can use the following assumptions: -- The initial population size $P_0$ has a 75% chance of originating from a small droplet and a 25% chance for a big droplet - - For small droplets, `P0` follows a `Poisson(10)` - - For big droplets, `P0` follows a `Poisson(30)` -- The growth rate $r$ follows a `LogNormal(0.0, 0.3)` -- The growth capacity $K$ of the inoculated medium follows a `Normal(1e5, 1e4)` -""" - -# ╔═╡ 749cacb9-b73d-470e-bf58-5550db5de7e0 -md""" -!!! questions - 1. Plot the prior distribution of P0. - 2. What is the probability your bacteria are in a splittable state 8 hours after inoculation? - 3. Plot 100 of the sampled logistic growth curves from 0 to 12 hours. -""" - -# ╔═╡ cec8b8e8-850e-4549-97fc-71eb35b8334b -md"### 1: Droplet Prior" - -# ╔═╡ bebe6f6b-9603-4062-8685-363ed7ff3dcb -md""" -!!! tip - A simple way of representing the distribution of P0 is through a mixture model. - Mixture models are a way of modeling something that has a chance to be from different, simple distributions. - - If you wanted to model a variable that has a 0.8 chance of being from a `Normal(0, 1)` and a 0.2 chance of being from an `Exponential(10)`, you would model it as follows in Turing: - `MixtureModel([Normal(0, 1), Exponential(10)], [0.8, 0.2])` - - For the interested reader, mixture models are explained in more detail in theory section `4.5.2`. -""" - -# ╔═╡ eb6dc4e7-e779-4bfc-b865-3defa3894181 -dropletdist = missing; - -# ╔═╡ 107533fc-b300-4b8d-bea2-a3aa6a37938d -md"### 2: Probability" - -# ╔═╡ 38c5b9cd-0baf-4a62-912e-4993614ddbf3 -md""" -!!! tip - You can `return` the logistic function estimated within the model and retrieve it using `generated_quantities` to make plotting easier later on. - - Remember: anonymous functions can be defined using `myfun = x -> ...` -""" - -# ╔═╡ bde57599-1dca-41a4-94aa-498da72c2012 -logistic(t, P0, r, K) = K / (1 + (K - P0)/P0 * exp(-r*t)) - -# ╔═╡ 976cfb96-3196-4a61-bd5f-e4f5d24ba1e9 -@model function petrigrowth() - P0 ~ dropletdist - r ~ missing - K ~ missing - - logfun = missing - return logfun -end - -# ╔═╡ 4345b3dd-0731-4dd5-a319-627b4f91306e -petri_model = missing - -# ╔═╡ 9d747eef-a883-49b3-acb7-f0d077a2b902 -chain_petri = missing - -# ╔═╡ ce8b53c3-0af1-4e9a-ad80-6fd7a2bf020b -logfuns = missing - -# ╔═╡ e1d0c5d5-2b7a-4af4-a7ac-e1ca46e665c8 -sp_petri = missing - -# ╔═╡ e3092915-a083-425e-8fa1-b7bf370abc8a -prob_splittable = missing - -# ╔═╡ e4f42f16-5cce-4fc2-aa01-8971f37c710e -md"### 3: Plot" - -# ╔═╡ 2b39e0bd-91cd-456e-9053-7b8fe5a395fb -md""" -!!! tip - Plotting a function `myfun` is as simple as entering `plot(myfun)`. The same syntax applies if `myfun` is a vector of functions. However, don't forget it was asked to plot only **100** growth curves. -""" - -# ╔═╡ d6c193d7-9fe2-413b-801f-ebc33c772ee9 -missing # plot - -# ╔═╡ c8941726-9e81-47fc-9b7e-cb3b5c0c61ca -md"# 2: Attraction" - -# ╔═╡ 431023df-3724-4325-b0ac-96dbf5e4fd20 -md""" -Following a course on electromagnetism will teach one that computing the net force between 2 arbitrary shapes can be a terrifying task. Tragedy has it then, that this is a very general problem with application from making fusion reactors to space travel. We can ease the pain by turning it into a sampling problem. - -We'll start in a humble manner and simulate **the gravitational force between 2 cubes**. Both cubes are size 1. The first cube is in [0, 1] x [0, 1] x [0, 1], and the second cube in [1.1, 2.1] x [0, 1] x [0, 1], as shown in the figure below. -""" - -# ╔═╡ 599ac984-ef1d-4c7a-8e87-9d4ddb1aa710 -begin - xe = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0] - ye = [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1] - ze = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1] - - xe2 = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0] .+ 1.1 - - plot(xlims = (-0.5, 2.5), ylims = (-1, 2), zlims = (0, 3)) - plot!(xe, ye, ze; color = :blue, linewidth = 0.5, label = "cube 1") - plot!(xe2, ye, ze; color = :orange, lw = 0.5, label = "cube 2") -end - -# ╔═╡ bb8e4cd6-6106-4d3f-9e35-0dfd8b2c45f5 -md""" -The gravitational force can be estimated by **randomly sampling a point from both cubes** and using the formula for gravitational force between those points, ignoring all constants: - -```math -F = \frac{1}{r^2} -``` -""" - -# ╔═╡ ceeeee76-e31c-4429-8ed0-e1c503433dbf -md""" -!!! questions - 1. What is the estimated net force between the two cubes? Is this the same as if you had treated the cubes as point masses? - 1. How many samples do you need to estimate this force reliably? Define a reliable estimator as one having a standard deviation of 0.1. Visualise the distribution of the estimator. -""" - -# ╔═╡ 9be28327-d87f-4d50-bbcc-91e799f14dbf -md"### 1: Net Force" - -# ╔═╡ 06d0e92f-1f07-41fd-b6ee-e94eb539627d -@model function cubeforce() - x1 ~ missing - y1 ~ missing - z1 ~ missing - - x2 ~ missing - y2 ~ missing - z2 ~ missing - - F = missing - - return F -end - -# ╔═╡ 43119060-bd35-4c4c-8831-d5c5df1d8dd5 -cubemodel = cubeforce(); - -# ╔═╡ d26ad946-2bb4-4383-860f-d601903ce1be -force_sp = missing - -# ╔═╡ 7b117453-0875-4b41-a228-866c6c0a8208 -force_average = missing - -# ╔═╡ b28cfbae-2fac-4b38-b234-53f71e381bcd -pointmass_force = missing # (doesn't require Turing, only maths) - -# ╔═╡ 304d6052-6d3d-487c-8c01-dab259276d6f -md"### 2: Variance of Estimator" - -# ╔═╡ e38e8f51-90db-4136-84e2-f06cd03d502a -@bind required_samples Slider(10:10:200, show_value = true) - -# ╔═╡ b753fa82-28ab-46d1-9085-4c65301db046 -estimator = mean([cubemodel() for _ in 1:required_samples]) - # a single estimation of the force given `required_samples` samples - -# ╔═╡ 55cf127f-5534-4926-955a-487ea9553b70 -estimator_sp = missing - # a sample of estimations given `required_samples` samples - -# ╔═╡ f6a67c1e-3677-4e51-b6dd-5d11a5146ea7 -estimator_sp_σ = missing - # standard deviation of the force estimator - -# ╔═╡ 45f0813b-c4c9-4f13-8d66-1e58293c4422 -missing # histogram - -# ╔═╡ Cell order: -# ╟─52a38b60-178b-4a1d-ac32-e73fafd339f9 -# ╠═df95b3d0-9f2d-4267-ad8c-837c7848a871 -# ╠═80bc0e86-5ad3-4d61-9600-8dc05b86599d -# ╠═fe68a4dd-038c-4f94-a4f0-48933ff2fa87 -# ╟─116b840c-e766-4ff6-aafa-0977fb122992 -# ╟─415b5ba8-3f6d-46ea-8f89-19fa7c0e74f9 -# ╟─3691d6aa-c717-46e8-b8b3-f4aa56c9f761 -# ╟─749cacb9-b73d-470e-bf58-5550db5de7e0 -# ╟─cec8b8e8-850e-4549-97fc-71eb35b8334b -# ╟─bebe6f6b-9603-4062-8685-363ed7ff3dcb -# ╠═eb6dc4e7-e779-4bfc-b865-3defa3894181 -# ╟─107533fc-b300-4b8d-bea2-a3aa6a37938d -# ╟─38c5b9cd-0baf-4a62-912e-4993614ddbf3 -# ╠═bde57599-1dca-41a4-94aa-498da72c2012 -# ╠═976cfb96-3196-4a61-bd5f-e4f5d24ba1e9 -# ╠═4345b3dd-0731-4dd5-a319-627b4f91306e -# ╠═9d747eef-a883-49b3-acb7-f0d077a2b902 -# ╠═ce8b53c3-0af1-4e9a-ad80-6fd7a2bf020b -# ╠═e1d0c5d5-2b7a-4af4-a7ac-e1ca46e665c8 -# ╠═e3092915-a083-425e-8fa1-b7bf370abc8a -# ╟─e4f42f16-5cce-4fc2-aa01-8971f37c710e -# ╟─2b39e0bd-91cd-456e-9053-7b8fe5a395fb -# ╠═d6c193d7-9fe2-413b-801f-ebc33c772ee9 -# ╟─c8941726-9e81-47fc-9b7e-cb3b5c0c61ca -# ╟─431023df-3724-4325-b0ac-96dbf5e4fd20 -# ╟─599ac984-ef1d-4c7a-8e87-9d4ddb1aa710 -# ╟─bb8e4cd6-6106-4d3f-9e35-0dfd8b2c45f5 -# ╟─ceeeee76-e31c-4429-8ed0-e1c503433dbf -# ╟─9be28327-d87f-4d50-bbcc-91e799f14dbf -# ╠═06d0e92f-1f07-41fd-b6ee-e94eb539627d -# ╠═43119060-bd35-4c4c-8831-d5c5df1d8dd5 -# ╠═d26ad946-2bb4-4383-860f-d601903ce1be -# ╠═7b117453-0875-4b41-a228-866c6c0a8208 -# ╠═b28cfbae-2fac-4b38-b234-53f71e381bcd -# ╟─304d6052-6d3d-487c-8c01-dab259276d6f -# ╠═e38e8f51-90db-4136-84e2-f06cd03d502a -# ╠═b753fa82-28ab-46d1-9085-4c65301db046 -# ╠═55cf127f-5534-4926-955a-487ea9553b70 -# ╠═f6a67c1e-3677-4e51-b6dd-5d11a5146ea7 -# ╠═45f0813b-c4c9-4f13-8d66-1e58293c4422 diff --git a/src/exercises/probmod_4-review.jl b/src/exercises/probmod_4-review.jl deleted file mode 100644 index f1c846f2..00000000 --- a/src/exercises/probmod_4-review.jl +++ /dev/null @@ -1,76 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "19" -#> title = "3. ProbMod review" -#> date = "2025-03-07" -#> tags = ["exercises"] -#> description = "Review sampling exercise" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Bram Spanoghe" - -using Markdown -using InteractiveUtils - -# ╔═╡ 432217b3-d4c0-4472-9350-28649d7ad958 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ aa2737db-f89f-4e08-9abe-0a86e8880c19 -using Turing, StatsPlots - -# ╔═╡ 5be87389-1a97-481e-bec6-1d781f016077 -md"# Review exercise: Buffon's needles" - -# ╔═╡ a6435b94-f1af-4609-abfc-93d88730d023 -md""" -A wise man once said: ["there is no greater joy than estimating π"](https://en.wikipedia.org/wiki/Approximations_of_%CF%80). Next to throwing darts at the unit square, another method to accomplish this is using [Buffon's needle problem](https://en.wikipedia.org/wiki/Buffon%27s_needle_problem). - -The experiment is as follows: consider a floor with parallel lines all a distance of 1 away from eachother. Now drop a needle of length 1 (and width ~0) on the floor with a **random position and angle**. What is the probability $P_{cross}$ that the needle will cross one of the lines? -""" - -# ╔═╡ df1758da-50fa-4476-a132-b952620110c6 -md"The following image illustrates the problem (imagine $l$ = $t$ = 1) for two needles, where `a` crosses a line and `b` does not." - -# ╔═╡ c2ba2183-957f-4b94-a22e-0c2f3dd957ad -md""" -![Buffon's needles](https://upload.wikimedia.org/wikipedia/commons/thumb/5/58/Buffon_needle.svg/1920px-Buffon_needle.svg.png) -""" - -# ╔═╡ d71cf8dc-99e5-48e0-9abe-2242a6ccc30b -md""" -Using sampling magic, it's not difficult to make an estimate of this probability, $\hat{P}_{cross}$. Solving the problem analytically shows that the exact value is: -```math -P_{cross} = \frac{2}{\pi} -``` - -Therefore, our estimator for π is: -```math -\hat{π} = \frac{2}{\hat{P}_{cross}} -``` -""" - -# ╔═╡ 99747189-d201-4583-ac9f-6875b0b606f2 -md""" -!!! question - Estimate π using the Buffon's needle approximation. -""" - -# ╔═╡ 6656cc86-db29-42ef-b612-c09252edfd49 -md""" -!!! hint - Assuming the lines are vertical, you only need to consider the **x-coordinates** of both ends of the needle. -""" - -# ╔═╡ Cell order: -# ╠═432217b3-d4c0-4472-9350-28649d7ad958 -# ╠═aa2737db-f89f-4e08-9abe-0a86e8880c19 -# ╟─5be87389-1a97-481e-bec6-1d781f016077 -# ╟─a6435b94-f1af-4609-abfc-93d88730d023 -# ╟─df1758da-50fa-4476-a132-b952620110c6 -# ╟─c2ba2183-957f-4b94-a22e-0c2f3dd957ad -# ╟─d71cf8dc-99e5-48e0-9abe-2242a6ccc30b -# ╟─99747189-d201-4583-ac9f-6875b0b606f2 -# ╟─6656cc86-db29-42ef-b612-c09252edfd49 diff --git a/src/exercises/sde_model_aging.jl b/src/exercises/sde_model_aging.jl deleted file mode 100644 index 9ce24bb8..00000000 --- a/src/exercises/sde_model_aging.jl +++ /dev/null @@ -1,343 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "12" -#> title = "2. SDE aging" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "SDE aging" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 85b6ee78-2f7b-4f83-908b-c48fade56fcb -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 0c457482-95fe-11ef-0873-65164704c7a6 -using Markdown - -# ╔═╡ 7a0d5c7f-fb37-4b38-9736-32e81c8014c4 -using InteractiveUtils - -# ╔═╡ 68588061-fedd-4f19-9e89-39b1bd791e33 -using Catalyst - -# ╔═╡ 1d0a8fb2-dab9-44ae-a230-f3e92cf3cd6a -using OrdinaryDiffEq, StochasticDiffEq, StatsPlots - -# ╔═╡ 22e0eda2-a922-48bb-9b6f-ec0c6dfeb524 -using Distributions - -# ╔═╡ a176adcc-bde0-4a25-aa80-33379e05e63f -md""" -# Exercise: Aging and saturated repair -""" - -# ╔═╡ 818ba2af-83dd-4ddb-93e8-d0549ddd806b -md""" -Aging is ultimately correlated with damaged cells. These damaged cells are called **senescent cells**. Senescent cells are cells that eventually stop multiplying but don't die off when they should. They instead remain and secrete factors that cause **chronic inflammation** and **reduce regeneration, leading to disease and decline**. Let $X$ denote the number of senescent cells - or the **damage** - in a human body. Research shows that they are **produced** at a **rate proportional to age**. Fortunately, in living organisms, these senescent cells are removed by so-called **natural-killer cells**. However, like many biological processen, this biological process of removing senescent cells is **saturated**. - -Hence, the model that we could adopt in order to predict the number of senescent cells (or damaged cells) $X$ in a human body, has two features: - -1. **production of damage that rises linearly with age**, and -2. the **saturating removal of damage**. - -A possible model is the following differential equation: - -$$\cfrac{dX}{dt} = \mu t - \beta \cfrac{X}{X + \kappa}$$ - -Lets denote $X$ in trillions [$tn$]. The term $\mu t$ stands for the procution of senescent cells, and the term $- \beta \cfrac{X}{X + \kappa}$ for the removal of senescent cells. The time $t$ is in years [$y$]. The coefficient $\eta$ [$tn/y^2$] is a proportionality factor for the production, $\beta$ [$tn/y$] is the removal rate coefficient and $\cfrac{X}{X + \kappa}$ [$-$] is the corresponding saturation factor, with $\kappa$ [$tn$] the amount of $X$ at which they inhibit half of their own removal rate. - -If this model was all there was, then all individuals would age at the same rate and die at the same age. The model does not explain why genetically identical organisms could differ in the number of senenscent cells. Therefore, we will introduce noise in the model by treating it as a **Stochastic Differential Equation** (SDE) model, where noise will be added to both, production and removal processes. -""" - -# ╔═╡ d917e75d-eefb-415f-ba45-2f1d7c62d63c -md""" -#### Implementation of the system -""" - -# ╔═╡ 7bda135b-2e0a-425f-b30c-e5a09dbb8a4a -md""" -Implement the above ODE into a *reaction network object*, and name it `senescent_cells_rn`. - -Take a default initial value $X(t=0)=0.0$ for the *species* $X$, and default values of $\mu=0.00558$, $\beta=0.4464$, $\kappa=1.116$ for the *parameters* in the model. In addition to the parameters, take $\eta=0.1$ as the *default noise scaling* factor, and, furthermore, set the noise scaling to $0.5$ for the process exhibiting the saturating removal of damage. -""" - -# ╔═╡ 38edfb50-7d6f-4fcc-b328-95ecb26d6de1 -# Uncomment and complete the instruction -# senescent_cells_rn = @reaction_network begin -# @species missing -# @parameters missing -# @default_noise_scaling missing -# missing -# missing -# end - -# ╔═╡ 51edb519-2086-45a5-a666-b5a58c51b5e8 -md""" -Convert this *reaction model* into a symbolic differential equation model and verify that you get the correct differential equation as mentioned above. -""" - -# ╔═╡ b3dd11e4-1209-46f3-a47a-07b54c5e9d77 -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 0524da26-35cb-4846-b7ef-f5842ae5db74 -md""" -#### Setting initial condition, time span and parameters. -""" - -# ╔═╡ 98c1f94e-aea5-40c5-bee7-8df8794fedb3 -md""" -Initialize a vector `u0` with the default initial condition, set the timespan for the simulation (we will simulate from $0\;y$ to $120\;y$), and initialize a vector `param` with the default parameter values. In that way, later, you can change the initial condition and the parameter values if you want to try other values. -""" - -# ╔═╡ 80db134e-bc57-41b0-8e1b-2ac6bff0c806 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ ab8bef21-8c4f-4962-b9cb-e87ac1db049a -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ ab302108-e9a7-4941-a762-c0dc109c9b1d -# parms = missing # Uncomment and complete the instruction - -# ╔═╡ 3934ba8f-49c9-4f0c-8373-c8010f397d66 -md""" -#### Simulating the system as an SDE problem -""" - -# ╔═╡ 45fceec8-a641-43ed-9a17-72b795e09297 -md""" -Create the SDE problem. -""" - -# ╔═╡ 7d164cdf-fd63-4c85-b168-3279d6d658eb -# sprob = missing # Uncomment and complete the instruction - -# ╔═╡ 9f9a616a-4353-4f9a-8da5-effe4d214ed0 -md""" -Solve the SDE problem using `EM()`as solver and time step `dt=0.1`. -""" - -# ╔═╡ e4b9393d-d33c-4e16-814e-347b35435a82 -# ssol = missing # Uncomment and complete the instruction - -# ╔═╡ 91881bc3-a1f0-48f7-82e8-7d7acea2f4de -md""" -Plot the solutions. Use the option `ylim=(0, 6)` in order to limit the range of $X$. -""" - -# ╔═╡ 7128ae66-c576-435b-8e33-4221f469ba1c -# missing # Uncomment and complete the instruction - -# ╔═╡ 1bce56da-0c35-41df-adbd-b5dee116be11 -md""" -Execute the cell, where the SDE problem is being solved, a few times and watch the (stochastic) changes in the solutions. -""" - -# ╔═╡ 7ca7c177-40b3-42c0-a57d-2f7eb874abdf -md""" -#### Simulating the system as an EnsembleProblem. -""" - -# ╔═╡ 33debdb3-2008-4055-a778-600bf7409361 -md""" -In order to see to have an idea of the extend of the stochastic effect on the solutions, we can make a so-called *EnsembleProblem*. This allows us to plot many possible solutions in one plot. -""" - -# ╔═╡ 85c7adcd-5a69-4900-849e-6222568bf68f -md""" -Create an `EnsembleProblem` based on `sprob`. -""" - -# ╔═╡ 1f25730d-d93e-462a-ac02-f83381633849 -# esprob = missing # Uncomment and complete the instruction - -# ╔═╡ 7d531f11-1a64-4b43-aa4f-04d282a615bd -md""" -Solve the ensemble problem. Use `EM()` as solver, take a time step `dt=0.1`, use the options `save_everystep=true`, and `trajectories=100`. -""" - -# ╔═╡ 5aaf1395-9b7c-451f-b1a3-44e5ba3c4d6a -# essol = missing # Uncomment and complete the instruction - -# ╔═╡ c048528f-58a2-415d-a338-fc87302367b8 -md""" -Plot the solutions. Use the option `ylim=(0, 6)` in order to limit the range of $X$. -""" - -# ╔═╡ 147adea6-e7ed-4f7b-b753-127e41500109 -# missing # Uncomment and complete the instruction - -# ╔═╡ 1db274cd-3b01-4f45-8aa0-ffe6878a1dd1 -md""" -#### Distribution of ages at 5 trillion senescent cells -""" - -# ╔═╡ c024dd3a-8fa8-4dbe-9669-0f338416a6af -md""" -Set up a histogram that shows the distribution of ages once the 5 trillion senescent cells are present in the body. -""" - -# ╔═╡ 3fe09628-cbfb-4e56-85e8-b97285b32e87 -md""" -!!! hints -- The number of senescent cells of the `i`-th trajoctory can be accessed with: `essol.u[i][:X]`. -- The index of the first element in the `i`-th trajectory that is greater than 5 can be found with: `findfirst(>(5), essol.u[i][:X])`. -- An index is a valid index when it if not `nothing`. -- The time at index position `j` can be accessed with `essol.u[i].t[j]` -- Appending an element, e.g., `x` to an array `times` can be done as follow: `append!(times, x)` -""" - -# ╔═╡ 0f901112-e504-4b7d-b7ec-3a80153903ca -# Uncomment and complete the instruction -# begin -# times = [] # make empty vector -# for missing # for loop from 1 to 100, default step is 1 -# # find index of first element that is greater than 5 -# missing -# if missing # if index is a valid index -# missing # append time to vector times -# end -# end -# end - -# ╔═╡ 65edc40f-430f-4d1a-9062-17bf8e1d7d59 -md""" -Make a histogram with the array `times`. Use `bins=range(0, 120, length=121)`. -""" - -# ╔═╡ 17a51f9e-0adf-4802-8d13-c43eb7801bc7 -# missing # Uncomment and complete the instruction - -# ╔═╡ dad744a4-0fd2-406d-a600-429ad7619efd -md""" -Check the mean. -""" - -# ╔═╡ a41d2ab4-ffad-4b27-9811-ab57c51526cb -# missing # Uncomment and complete the instruction - -# ╔═╡ 98ca03e1-9c4e-40db-9dd7-68f1898c1ef7 -md""" -Check the standard deviation. -""" - -# ╔═╡ 7ac54e2a-9c8e-4743-a1a0-38490b04f759 -# missing # Uncomment and complete the instruction - -# ╔═╡ 0f9118f5-93f4-42f8-a0cf-754ce9ce9dbe -md""" -Check the minimum value. -""" - -# ╔═╡ a07565f7-31d9-4c50-8345-5777adb8a77a -# missing # Uncomment and complete the instruction - -# ╔═╡ 6f4253ce-e686-4061-ae13-9b35065464d9 -md""" -Check the maximum value. -""" - -# ╔═╡ 70628044-aaa5-46d6-a2df-deabbd8f2df8 -# missing # Uncomment and complete the instruction - -# ╔═╡ b10a73a1-63ae-4a15-9a83-394f6ed4e36e -md""" -Interpret the results. Ask yourself the following question: - -1. Suppose that $5$ trillion senescent cells is about the maximum a human body can bear. What is the (approximate) corresponding range of ages? -""" - -# ╔═╡ a4f5a40e-c4ec-45ef-b44b-b64b71717c25 -md"- Answer: missing" - -# ╔═╡ 88c5c0f5-5bbf-4c54-9a9a-bf6369a2e895 -md""" -2. What is the effect of halving the damage rate $\mu$? -""" - -# ╔═╡ 369b91ee-68c0-406d-8832-363faec6feec -md"- Answer: missing" - -# ╔═╡ 5ded917b-9524-4492-9b05-5a1ac1507dc2 -md""" -3. What is the effect of doubling the damage removal rate $\beta$? -""" - -# ╔═╡ 2560cf75-95ff-4760-a25c-baa812b40fc6 -md"- Answer: missing" - -# ╔═╡ 75b08cc8-20b2-4423-bea2-bf218c9f8202 -md""" -4. What is the effect of halving the noise? -""" - -# ╔═╡ 6123aaa2-2afb-46a3-bcab-1cf3d8335666 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═0c457482-95fe-11ef-0873-65164704c7a6 -# ╠═7a0d5c7f-fb37-4b38-9736-32e81c8014c4 -# ╠═85b6ee78-2f7b-4f83-908b-c48fade56fcb -# ╠═68588061-fedd-4f19-9e89-39b1bd791e33 -# ╠═1d0a8fb2-dab9-44ae-a230-f3e92cf3cd6a -# ╠═22e0eda2-a922-48bb-9b6f-ec0c6dfeb524 -# ╟─a176adcc-bde0-4a25-aa80-33379e05e63f -# ╟─818ba2af-83dd-4ddb-93e8-d0549ddd806b -# ╟─d917e75d-eefb-415f-ba45-2f1d7c62d63c -# ╟─7bda135b-2e0a-425f-b30c-e5a09dbb8a4a -# ╠═38edfb50-7d6f-4fcc-b328-95ecb26d6de1 -# ╟─51edb519-2086-45a5-a666-b5a58c51b5e8 -# ╠═b3dd11e4-1209-46f3-a47a-07b54c5e9d77 -# ╟─0524da26-35cb-4846-b7ef-f5842ae5db74 -# ╟─98c1f94e-aea5-40c5-bee7-8df8794fedb3 -# ╠═80db134e-bc57-41b0-8e1b-2ac6bff0c806 -# ╠═ab8bef21-8c4f-4962-b9cb-e87ac1db049a -# ╠═ab302108-e9a7-4941-a762-c0dc109c9b1d -# ╟─3934ba8f-49c9-4f0c-8373-c8010f397d66 -# ╟─45fceec8-a641-43ed-9a17-72b795e09297 -# ╠═7d164cdf-fd63-4c85-b168-3279d6d658eb -# ╟─9f9a616a-4353-4f9a-8da5-effe4d214ed0 -# ╠═e4b9393d-d33c-4e16-814e-347b35435a82 -# ╟─91881bc3-a1f0-48f7-82e8-7d7acea2f4de -# ╠═7128ae66-c576-435b-8e33-4221f469ba1c -# ╟─1bce56da-0c35-41df-adbd-b5dee116be11 -# ╟─7ca7c177-40b3-42c0-a57d-2f7eb874abdf -# ╟─33debdb3-2008-4055-a778-600bf7409361 -# ╟─85c7adcd-5a69-4900-849e-6222568bf68f -# ╠═1f25730d-d93e-462a-ac02-f83381633849 -# ╟─7d531f11-1a64-4b43-aa4f-04d282a615bd -# ╠═5aaf1395-9b7c-451f-b1a3-44e5ba3c4d6a -# ╟─c048528f-58a2-415d-a338-fc87302367b8 -# ╠═147adea6-e7ed-4f7b-b753-127e41500109 -# ╟─1db274cd-3b01-4f45-8aa0-ffe6878a1dd1 -# ╟─c024dd3a-8fa8-4dbe-9669-0f338416a6af -# ╟─3fe09628-cbfb-4e56-85e8-b97285b32e87 -# ╠═0f901112-e504-4b7d-b7ec-3a80153903ca -# ╟─65edc40f-430f-4d1a-9062-17bf8e1d7d59 -# ╠═17a51f9e-0adf-4802-8d13-c43eb7801bc7 -# ╟─dad744a4-0fd2-406d-a600-429ad7619efd -# ╠═a41d2ab4-ffad-4b27-9811-ab57c51526cb -# ╟─98ca03e1-9c4e-40db-9dd7-68f1898c1ef7 -# ╠═7ac54e2a-9c8e-4743-a1a0-38490b04f759 -# ╟─0f9118f5-93f4-42f8-a0cf-754ce9ce9dbe -# ╠═a07565f7-31d9-4c50-8345-5777adb8a77a -# ╟─6f4253ce-e686-4061-ae13-9b35065464d9 -# ╠═70628044-aaa5-46d6-a2df-deabbd8f2df8 -# ╟─b10a73a1-63ae-4a15-9a83-394f6ed4e36e -# ╟─a4f5a40e-c4ec-45ef-b44b-b64b71717c25 -# ╟─88c5c0f5-5bbf-4c54-9a9a-bf6369a2e895 -# ╟─369b91ee-68c0-406d-8832-363faec6feec -# ╟─5ded917b-9524-4492-9b05-5a1ac1507dc2 -# ╟─2560cf75-95ff-4760-a25c-baa812b40fc6 -# ╟─75b08cc8-20b2-4423-bea2-bf218c9f8202 -# ╟─6123aaa2-2afb-46a3-bcab-1cf3d8335666 diff --git a/src/exercises/sde_model_catalyst_intro.jl b/src/exercises/sde_model_catalyst_intro.jl deleted file mode 100644 index 5841e7c0..00000000 --- a/src/exercises/sde_model_catalyst_intro.jl +++ /dev/null @@ -1,382 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "11" -#> title = "2. SDE catalyst intro" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "SDE catalyst intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 2fb64158-6293-4a0b-b252-223307b472d9 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 71118b72-1db2-11ef-1f5b-a163b0b7c390 -using Markdown - -# ╔═╡ dbf68cf4-7a18-4bf2-90a3-e36216a41a70 -using InteractiveUtils - -# ╔═╡ acac551e-77b1-4bbb-b7a1-99205e2a4f8e -using PlutoUI; TableOfContents() - -# ╔═╡ b0b9313a-cc6b-44bd-90c9-5797f5d65d4c -using Catalyst - -# ╔═╡ 99229de2-1e9b-470f-b532-ed1afb91c971 -using OrdinaryDiffEq, StochasticDiffEq, StatsPlots - -# ╔═╡ 5308b9d5-8068-48d7-b7a2-9e6ae043321b -md""" -# Solving SDE problems with Catalyst -""" - -# ╔═╡ da37d5d2-4615-4be7-a34f-566f02ca2cc6 -md""" -**Stochastic Differential Equations** (SDEs) are mathematical equations used to model -systems influenced by random noise. They extend **Ordinary Differential Equations** (ODEs) by incorporating terms that represent **stochastic processes**, typically in the form of a Wiener process or Brownian motion. SDEs are widely used in various fields, such as physics, biology, finance, and engineering, to describe the evolution of systems under uncertainty or with inherent randomness. -""" - -# ╔═╡ 06e9a270-a116-4304-82a8-8733fd313b9c -md""" -We will illustrate the concepts of SDE problems using the infection model that was used to introduce the Catalyst package and how to solve it as an ODE problem.\ -In a previous notebook elaborated on the infection model in detail, hence, we will here limit ourselves to summarizing the variables, parameters and *reactions*. -""" - -# ╔═╡ 2b83ca01-0e45-4831-96ae-7a22f706a7c7 -md""" -Below we summarize the **variables** (**species**): -""" - -# ╔═╡ 7f3ffadd-5ae0-45d5-9894-49426f8c3af8 -md""" -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``S`` | *persons* | number of susceptible persons | -| ``I`` | *persons* | number of infected persons | -| ``D`` | *persons* | number of deceased persons | -| ``R`` | *persons* | number of resistant persons | -""" - -# ╔═╡ 7e72d0f8-50bb-460a-9820-91c38474e1a2 -md""" -Below we summarize the **parameters**: -""" - -# ╔═╡ 0ad755ec-9746-4716-9be3-aa92480c52c9 -md""" -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``\alpha`` | ``\frac{persons}{contact}`` | chances of getting infected after contact | -| ``\beta`` | ``\frac{contact}{persons^2\,day}`` | contact rate | -| ``r`` | ``\frac{1}{day}`` | rate of leaving infection period | -| ``m`` | ``\frac{person}{person}`` | fraction of persons deceasing | -| ``1-m`` | ``\frac{person}{person}`` | fraction of persons becoming resistant | -""" - -# ╔═╡ d46a18d4-f189-4be4-9bcb-edc6b05119cf -md""" -Hence, the infection rate is ``\alpha \beta``. This means that a susceptible person meets an infected person: ``S+I``, this will result in ``2I`` at a rate ``\alpha \beta``. Futhermore, an infected person ``I`` will either become a deceased person ``D`` at a rate ``m r`` or become a resistant person ``R`` at rate ``(1-m) r`` -""" - -# ╔═╡ 1e8b7f98-bede-4c43-9d4d-7fd94b1e73d3 -md""" -Our infection model has three reaction events: - -- Infection, where a susceptible persons meets an infected persons and also becomes infected. -- Deceasing, where an infected person die. -- Recovery, where an infected person recovers and becomes resistant. -""" - -# ╔═╡ 274a11b8-09fc-46dd-9f27-ead38a41080d -md""" -Each reaction is also associated with a specific rate: - -- ``\alpha \beta``, the infection rate. -- ``m r``, the death rate. -- ``(1-m) r``, the recovery rate. -""" - -# ╔═╡ ef20da6f-c5af-4a94-bf6c-1ad68292992d -md""" -Hence, the following *infection reactions* are: - -$$S + I \xrightarrow[]{\alpha \beta} 2I$$ -$$I \xrightarrow[]{mr} D$$ -$$I \xrightarrow[]{(1-m)r} R$$ -""" - -# ╔═╡ dcdadb6b-8577-4a59-80a5-ebad28b7d6b8 -md""" -We are going to implement this system of *reactions* using Catalyst. -""" - -# ╔═╡ 67ceffa7-2f2d-4719-b277-1eecf7c3ea57 -md""" -We first load the Catalyst package, which is required for the code in this introduction to run: -""" - -# ╔═╡ 5161d6a6-df87-4d50-9c74-5b7c780d4eb4 -md""" -## Implementation of the system - -The following code creates a so called *reaction network object*, that we have named `infection_sde_model`, that implements the aforementioned *reactions*.\ -""" - -# ╔═╡ d1e14521-3365-43a5-b730-9b51ca359e12 -infection_sde_model = @reaction_network begin - @parameters η=40 - @default_noise_scaling η - α * β, S + I --> 2I, [noise_scaling = 60.0] - r * m, I --> D - r * (1 - m), I --> R -end - -# ╔═╡ f2b28b56-1369-4972-b771-7684397ad2ce -md""" -Note that we have now introducted a **new parameter** (cf. `@parameters η` and `@default_noise_scaling η`). This parameter represent a default **noise scaling** parameter applying to all reactions (default value is 1). You can overwrite this default value for specific reactions by specifying `[noise_scaling = ...]` on the same line.\ -They are in principle not necessary to solve the problem as a SDE problem but they can in many cases be very useful (see later).\ -When solving SDE problems, some random noise will be introduced upon the reaction rates of all reactions. -""" - -# ╔═╡ 2a1afc01-5707-4368-ab69-cf2ffb8aa956 -md""" -Similarity as before, you can get a list of the *reaction* **species** with the function `species`, and a list of the **parameters** with the function `parameters`. -""" - -# ╔═╡ 2921c702-0ec3-4154-966c-7ba9c31095eb -parameters(infection_sde_model) - -# ╔═╡ 619b6dc1-7486-4561-ab48-8daee7299734 -md""" -Note that the parameter $\eta$ also is present in the list. -""" - -# ╔═╡ 59c69709-f058-4312-9158-1de975ed20cd -md""" -This *reaction model* can of course also be converted to a symbolic differential equation model: -""" - -# ╔═╡ 182667f0-e9cb-40b5-8b38-51a061c2c6de -osys = convert(ODESystem, infection_sde_model) - -# ╔═╡ 56dce5a0-5917-4052-ae52-39683a5f8d31 -md""" -## Simulating the system as an SDE problem - -We first need to load the Differential and Plot package, which is required for simulating the system and plotting the results. -""" - -# ╔═╡ a4fd70ed-7767-471e-b1ff-58fd36a8f077 -md""" -Assume, as before, that there are $10\,000\,000$ people in the country, and that initially $1\,000$ person are infected. Hence, $I_0 = 1\,000$, $S_0 = 10\,000\,000-I_0 = 9\,999\,000$, $D_0 = 0$ and $R_0 = 0$.\ -Furthermore, we take the following values for the parameters: $\alpha = 0.08\;person/contact$, $\beta = 10^{-6}\;contact/(person^2\,day)$, $r = 0.2\;day^{-1}$ and $m=0.4$.\ -Finally, we want to run our simulation from day $0$ till day $90$. -""" - -# ╔═╡ b88e39ef-21a4-4a94-8145-3799348c80bd -md""" -### Setting initial conditions -""" - -# ╔═╡ e2ff6711-8e2b-43f7-9917-a567227a306c -u0 = [:S => 9_999_000.0, :I => 1_000.0, :D => 0.0, :R => 0.0] - -# ╔═╡ c0fad359-8a7f-4690-a405-e75b8625b868 -md""" -### Setting the timespan -""" - -# ╔═╡ 1d0b68e1-b363-46e8-8445-9fc0765733eb -tspan = (0.0, 90.0) - -# ╔═╡ b0a5e259-37d9-4480-971e-7365250706bb -md""" -### Setting parameter values - -In the parameter list, you could also mention another default value for the default noise scaling parameter. -""" - -# ╔═╡ 38e28866-8c09-4675-a9c1-d9ba4012070b -params = [:α => 0.08, :β => 1.0e-6, :r => 0.2, :m => 0.4, :η => 50] - -# ╔═╡ 5ef6414d-943c-4a1e-a528-af0fb61d4f9a -md""" -### Creating a SDEProblem - -Create the SDE problem. -""" - -# ╔═╡ 1d5c6ff5-6a63-4491-981e-9c1ab5d37d60 -sprob = SDEProblem(infection_sde_model, u0, tspan, params) - -# ╔═╡ 0da84898-6170-4d71-acaf-23d97bfd7be9 -md""" -### Solving the SDEProblem - -There are many solving methods available for solving SDE problems. You can find a [list of methods here](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/#Full-List-of-Methods). We will simply use the first one in this list, namely `EM()`, with the time step option `dt=0.1` that will introduce some randomness at every time step. -""" - -# ╔═╡ f6ef847c-f9ea-464b-be95-e2bf5502a200 -ssol = solve(sprob, EM(), dt=0.1) - -# ╔═╡ 4fe4f1f8-b6a4-4e4a-b966-58bcdc2b2435 -md""" -Finally, we can plot the solution through the plot function. -""" - -# ╔═╡ b0253973-1a98-499d-a4b9-5db402a878bf -plot(ssol) - -# ╔═╡ 96547ac1-d353-4a90-a6dd-38124d59294c -md""" -You might notice that is you run the above instruction `ssol = solve(sprob, EM(), dt=0.1)` subsequent times, you will each time get different solutions (plots) due to the randomness introduced by treating the problem as SDE problem. -""" - -# ╔═╡ 4ed7affa-249b-4a7e-be37-ae7f42c7f36b -md""" -## Simulating the system as an EnsembleProblem. - -In order to see to have an idea of the extend of the stochastic effect on the solutions, we can create a so-called *EnsembleProblem*. This allows us to plot many possible solutions in one plot. - -In order to create an *EnsembleProblem*, you need to create an *SDEProblem* first. Since we already have our *SDEProblem* called `sprob`, we can readily create an *EnsembleProblem* from this. All you need to do is call the function `EnsembleProblem` with `sprob` as argument. -""" - -# ╔═╡ 8da9bc41-83d5-4404-a195-0fc0430b231b -md""" -### Creating a EnsembleProblem - -Create the ensemble problem. -""" - -# ╔═╡ 9adb42be-ed67-4c14-9dbe-b1377c8d7b9a -esprob = EnsembleProblem(sprob) - -# ╔═╡ cc62cf4a-6fab-4ead-82e1-46faef12bf9b -md""" -### Solving the EnsembleProblem - -Solving the ensemble problem can be done with our, yet familiar, function `solve` as we did when solving the SDE problem, but now we need to provide a few more arguments. The first additional argument and value that we will provide is `save_everystep=true`, this will ensure that every simulation will be saved. The second argument indicates how many trajectories (simulations) you want to make. If you want an ensemble of 100 simulations, you can put `trajectories=100`. Hence, the function call would look like this: - -`essol_try = solve(esprob, EM(), dt=0.1, save_everystep=true, trajectories=100)` - -You can try this by uncommenting the instruction below and run the cell. -""" - -# ╔═╡ f0d159eb-2e57-43f3-8c49-5173cc7465f3 -# essol_try = solve(esprob, EM(), dt=0.1, save_everystep=true, trajectories=100) - -# ╔═╡ 5b91a7c2-64a3-4987-a9c6-6b1432d9b226 -md""" -You will have noticed the detection of instabilities and the abortions. This is because of the stochastic effects that can cause calculations to become unstable. In order to cope with that, we will make sure that at every step the states $S$, $I$, $R$ and $D$ always remain within their boundaries. Here this is in the interval $[0, 10000000]$. To realize this we can create a so-called `DisceteCallback` function using the functions below, namely, `condition` and `affect!`. Both put in a `DisceteCallback` function they basically will make sure that at each (integration) step, the states (cf. `integrator.u[i]`) will not go below $0$ or above $10000000$. -""" - -# ╔═╡ 3aebcf0b-717b-49bd-ae6b-ca7fffd9b75b -function condition(u, t, integrator) - true -end - -# ╔═╡ 05d2136d-7b20-4bd7-8081-a3d4889afd52 -function affect!(integrator) - for i = 1:4 - if integrator.u[i] > 10000000 - integrator.u[i] = 10000000 - end - if integrator.u[i] < 0 - integrator.u[i] = 0 - end - end -end - -# ╔═╡ 3f8937da-730c-49a8-b0ee-41ba84e7e0e9 -md""" -Combining them in a `DiscreteCallback` function: -""" - -# ╔═╡ a819d032-f9c4-475d-b749-d3da4c93aa1f -cb = DiscreteCallback(condition, affect!, save_positions=(false,true)) - -# ╔═╡ a66adae7-c5c9-4453-82ca-c7555083f695 -md""" -The option `save_positions=(false,true)` serves to save only the states *after* the `affect!` function was called, and not the states before. -""" - -# ╔═╡ 094d7363-7918-4bd5-ae2b-52f283468317 -md""" -Now we can solve the ensemble problem while including the callback function. -""" - -# ╔═╡ 3f57d613-0042-4df9-9892-edaa90c0f52e -essol = solve(esprob, EM(), dt=0.1, callback=cb, save_everystep=true, trajectories=100) - -# ╔═╡ 72770915-e1f5-41c5-84d2-9e6cbc0c24ff -plot(essol) - -# ╔═╡ Cell order: -# ╠═71118b72-1db2-11ef-1f5b-a163b0b7c390 -# ╠═dbf68cf4-7a18-4bf2-90a3-e36216a41a70 -# ╠═2fb64158-6293-4a0b-b252-223307b472d9 -# ╠═acac551e-77b1-4bbb-b7a1-99205e2a4f8e -# ╟─5308b9d5-8068-48d7-b7a2-9e6ae043321b -# ╟─da37d5d2-4615-4be7-a34f-566f02ca2cc6 -# ╟─06e9a270-a116-4304-82a8-8733fd313b9c -# ╟─2b83ca01-0e45-4831-96ae-7a22f706a7c7 -# ╟─7f3ffadd-5ae0-45d5-9894-49426f8c3af8 -# ╟─7e72d0f8-50bb-460a-9820-91c38474e1a2 -# ╟─0ad755ec-9746-4716-9be3-aa92480c52c9 -# ╟─d46a18d4-f189-4be4-9bcb-edc6b05119cf -# ╟─1e8b7f98-bede-4c43-9d4d-7fd94b1e73d3 -# ╟─274a11b8-09fc-46dd-9f27-ead38a41080d -# ╟─ef20da6f-c5af-4a94-bf6c-1ad68292992d -# ╟─dcdadb6b-8577-4a59-80a5-ebad28b7d6b8 -# ╟─67ceffa7-2f2d-4719-b277-1eecf7c3ea57 -# ╠═b0b9313a-cc6b-44bd-90c9-5797f5d65d4c -# ╟─5161d6a6-df87-4d50-9c74-5b7c780d4eb4 -# ╠═d1e14521-3365-43a5-b730-9b51ca359e12 -# ╟─f2b28b56-1369-4972-b771-7684397ad2ce -# ╟─2a1afc01-5707-4368-ab69-cf2ffb8aa956 -# ╠═2921c702-0ec3-4154-966c-7ba9c31095eb -# ╟─619b6dc1-7486-4561-ab48-8daee7299734 -# ╟─59c69709-f058-4312-9158-1de975ed20cd -# ╠═182667f0-e9cb-40b5-8b38-51a061c2c6de -# ╟─56dce5a0-5917-4052-ae52-39683a5f8d31 -# ╠═99229de2-1e9b-470f-b532-ed1afb91c971 -# ╟─a4fd70ed-7767-471e-b1ff-58fd36a8f077 -# ╟─b88e39ef-21a4-4a94-8145-3799348c80bd -# ╠═e2ff6711-8e2b-43f7-9917-a567227a306c -# ╟─c0fad359-8a7f-4690-a405-e75b8625b868 -# ╠═1d0b68e1-b363-46e8-8445-9fc0765733eb -# ╟─b0a5e259-37d9-4480-971e-7365250706bb -# ╠═38e28866-8c09-4675-a9c1-d9ba4012070b -# ╟─5ef6414d-943c-4a1e-a528-af0fb61d4f9a -# ╠═1d5c6ff5-6a63-4491-981e-9c1ab5d37d60 -# ╟─0da84898-6170-4d71-acaf-23d97bfd7be9 -# ╠═f6ef847c-f9ea-464b-be95-e2bf5502a200 -# ╟─4fe4f1f8-b6a4-4e4a-b966-58bcdc2b2435 -# ╠═b0253973-1a98-499d-a4b9-5db402a878bf -# ╟─96547ac1-d353-4a90-a6dd-38124d59294c -# ╟─4ed7affa-249b-4a7e-be37-ae7f42c7f36b -# ╟─8da9bc41-83d5-4404-a195-0fc0430b231b -# ╠═9adb42be-ed67-4c14-9dbe-b1377c8d7b9a -# ╟─cc62cf4a-6fab-4ead-82e1-46faef12bf9b -# ╠═f0d159eb-2e57-43f3-8c49-5173cc7465f3 -# ╟─5b91a7c2-64a3-4987-a9c6-6b1432d9b226 -# ╠═3aebcf0b-717b-49bd-ae6b-ca7fffd9b75b -# ╠═05d2136d-7b20-4bd7-8081-a3d4889afd52 -# ╟─3f8937da-730c-49a8-b0ee-41ba84e7e0e9 -# ╠═a819d032-f9c4-475d-b749-d3da4c93aa1f -# ╟─a66adae7-c5c9-4453-82ca-c7555083f695 -# ╟─094d7363-7918-4bd5-ae2b-52f283468317 -# ╠═3f57d613-0042-4df9-9892-edaa90c0f52e -# ╠═72770915-e1f5-41c5-84d2-9e6cbc0c24ff diff --git a/src/exercises/sde_model_fermenter_secondorder.jl b/src/exercises/sde_model_fermenter_secondorder.jl deleted file mode 100644 index fb7556ba..00000000 --- a/src/exercises/sde_model_fermenter_secondorder.jl +++ /dev/null @@ -1,174 +0,0 @@ -### A Pluto.jl notebook ### -# v0.19.46 - -#> [frontmatter] -#> order = "13" -#> title = "2. SDE fermentor 2nd order" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "SDE fermentor 2nd order" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 8152f632-af15-4164-a8ff-07c33a9a49b3 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ eb142900-1d94-11ef-12ed-6951b45f1817 -using Markdown - -# ╔═╡ 7139a11b-64db-46b9-a41c-dca83a9eab26 -using InteractiveUtils - -# ╔═╡ 1ca1d1db-1fdd-4a76-b350-126add7e013c -using Catalyst - -# ╔═╡ e04f782d-67da-4e21-a3bf-d2ddff4bba0b -using OrdinaryDiffEq, StochasticDiffEq, StatsPlots - -# ╔═╡ a28e7ddf-76e9-4628-888c-e1d838da75ce -md""" -# Exercise: Fermenter - 2nd order kinetics - SDE -""" - -# ╔═╡ 959d6307-a30d-4ae7-970d-b2c7584c2c8f -md""" -In a fermenter reactor biomass grows on substrate. The reactor is fed with a inlet flow rate $Q_{in}$ [$L/h$], which consist of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. Inside the reactor, biomass, with a concentration of $X$ [$g/L$], is produced through second-order kinetics: - -$$\begin{eqnarray*} -%S \xrightarrow[\quad\quad]{\beta} Y \, X -S \xrightarrow[\quad\quad]{r} Y \, X \quad\quad\quad\quad r = k \, S\,X -\end{eqnarray*}$$ - -with $k$ [$L\,gS^{-1}h^{-1}$] the reaction rate constant, and $Y$ [$gX/gS$] the yield coefficient which is defined here by the amount of produced biomass by consumption of one unit of substrate. Futhermore, the reactor is drained with an outlet flow $Q$ [$L/h$], which consist of the current concentrations of substrate $S$ [$g/L$] and biomass $X$ [$g/L$] inside the reactor. The volume $V$ [$L$] of the reactor content is kept constant by setting $Q_{in} = Q$. -""" - -# ╔═╡ 98858b9c-d4f9-451f-a7b9-fcaa012ee28e -md""" -Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of substrate $S$ and biomass $X$ with time as a Stochastic Differential Equation (SDE) problem with noise scaling. Name it `fermenter_sde_secondorder`. -""" - -# ╔═╡ d2e2680c-01c6-449a-bb5f-7472bc1de243 -md""" -Assign the following noise scaling values: -- `η = 0.10` for the main *reaction* (default value for `η`) -- noise scaling of `0.05` for the *reaction* describing the inlet $S_{in}$ -- noise scaling of `0.0` for the remaining *reactions* -""" - -# ╔═╡ 6b627d84-b6a5-444d-8163-40a4cab181bd -# Uncomment and complete the instruction -# fermenter_sde_secondorder = @reaction_network begin -# @parameters missing -# missing -# missing -# missing -# end - -# ╔═╡ 47ca3573-691c-4127-85b9-d5b5a1a23fbb -md""" -Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. -""" - -# ╔═╡ e1f11068-e489-4c54-a30d-981f5cb19b47 -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ a65c84c8-d2ec-44e4-9a07-cbd59f190c57 -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ ef3f7a41-61ab-4449-8ffc-784cf1e5cbe6 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 9d113e7f-8499-4b2a-a884-d34ce3da0b82 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 839a624b-ca62-4c57-9511-207b626ce864 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ a4d28c40-e315-4bb9-87a5-2b45dd633e5f -# params = missing # Uncomment and complete the instruction - -# ╔═╡ aeddc31e-9de2-4792-a2d8-59a14dfc8173 -# sprob = missing # Uncomment and complete the instruction - -# ╔═╡ 3a981326-2031-4c63-ad31-c44ddd7a88d5 -md""" -Solve the SDE problem. Use `EM()` with `dt=0.1`. Store the solution in `ssol`: -""" - -# ╔═╡ 6a69c369-4743-48c1-aed9-4f0ccb095707 -# ssol = missing # Uncomment and complete the instruction - -# ╔═╡ 6b6d2229-913b-41a2-8101-00e9fef0945a -md""" -Plot the results with the option `ylim=(0.0, 2.0)`: -""" - -# ╔═╡ 593a0e0a-c4d8-4b12-b38b-15b47705f8a7 -# missing - -# ╔═╡ 08746e97-d794-4261-9ba6-9002cf17e4c1 -md""" -Create an `EnsembleProblem` in order to visualize a multiple solutions. Store it in `esprob`. -""" - -# ╔═╡ 3d07836a-60b1-4584-a89a-3d8bbc72b8cd -# esprob = missing # Uncomment and complete the instruction - -# ╔═╡ 98c4ee2b-20ef-43a1-b420-7644d568810b -md""" -Solve the `EnsembleProblem` using the same solver (and time step) as before, for $100$ trajectories. Store the solution in `essol`. -""" - -# ╔═╡ 683fe575-887b-4bd1-8960-10c04f68354d -# essol = missing # Uncomment and complete the instruction - -# ╔═╡ 26746cab-d3e7-4a01-bbbd-9fcb49ef652f -md""" -Plot the results. Use as option again `ylim=(0.0,2.0)` and also `linealpha=0.5` to modify the line boldness. -""" - -# ╔═╡ 4fcc1d0a-9d30-4056-b8a3-3d802edc42e5 -# missing - -# ╔═╡ Cell order: -# ╠═eb142900-1d94-11ef-12ed-6951b45f1817 -# ╠═7139a11b-64db-46b9-a41c-dca83a9eab26 -# ╠═8152f632-af15-4164-a8ff-07c33a9a49b3 -# ╠═1ca1d1db-1fdd-4a76-b350-126add7e013c -# ╠═e04f782d-67da-4e21-a3bf-d2ddff4bba0b -# ╟─a28e7ddf-76e9-4628-888c-e1d838da75ce -# ╟─959d6307-a30d-4ae7-970d-b2c7584c2c8f -# ╟─98858b9c-d4f9-451f-a7b9-fcaa012ee28e -# ╟─d2e2680c-01c6-449a-bb5f-7472bc1de243 -# ╠═6b627d84-b6a5-444d-8163-40a4cab181bd -# ╟─47ca3573-691c-4127-85b9-d5b5a1a23fbb -# ╠═e1f11068-e489-4c54-a30d-981f5cb19b47 -# ╟─a65c84c8-d2ec-44e4-9a07-cbd59f190c57 -# ╠═ef3f7a41-61ab-4449-8ffc-784cf1e5cbe6 -# ╟─9d113e7f-8499-4b2a-a884-d34ce3da0b82 -# ╠═839a624b-ca62-4c57-9511-207b626ce864 -# ╠═a4d28c40-e315-4bb9-87a5-2b45dd633e5f -# ╠═aeddc31e-9de2-4792-a2d8-59a14dfc8173 -# ╟─3a981326-2031-4c63-ad31-c44ddd7a88d5 -# ╠═6a69c369-4743-48c1-aed9-4f0ccb095707 -# ╟─6b6d2229-913b-41a2-8101-00e9fef0945a -# ╠═593a0e0a-c4d8-4b12-b38b-15b47705f8a7 -# ╟─08746e97-d794-4261-9ba6-9002cf17e4c1 -# ╠═3d07836a-60b1-4584-a89a-3d8bbc72b8cd -# ╟─98c4ee2b-20ef-43a1-b420-7644d568810b -# ╠═683fe575-887b-4bd1-8960-10c04f68354d -# ╟─26746cab-d3e7-4a01-bbbd-9fcb49ef652f -# ╠═4fcc1d0a-9d30-4056-b8a3-3d802edc42e5 diff --git a/src/exercises/sens_bitrophic_model.jl b/src/exercises/sens_bitrophic_model.jl deleted file mode 100755 index 70dcd656..00000000 --- a/src/exercises/sens_bitrophic_model.jl +++ /dev/null @@ -1,412 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "30" -#> title = "6. Sensitivity bitrophic model" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Sensitivity bitrophic model" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ a3c1055e-b44f-4412-9ca0-f8ec5f972494 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ fd357fe0-0920-11ef-02e4-25a84575c6a2 -using Markdown - -# ╔═╡ 0ddc1082-bf8d-4357-a056-2fa861824b47 -using InteractiveUtils - -# ╔═╡ cce8fcde-069f-49b9-aadd-6abccbcfbace -using Catalyst - -# ╔═╡ b48e4b46-3a07-4ff0-b617-6bc1125f6a1e -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ ddc035a2-3b03-4a54-807c-05a271384ca7 -using ForwardDiff - -# ╔═╡ ceec287b-5622-48d2-9fe1-26b3e84e3f45 -md""" -### Exercise: Bitrophic model - Sensitivity analysis -""" - -# ╔═╡ 2a732f39-0499-4c90-be62-a1e2278e0c8b -md""" -The dynamic relationship between a field crop and a voracious insect population within an ecosystem can be represented by a bitrophic model. Such model typically consists of two variables: the abundance of the field crop, often representing a primary producer such as a plant species, and the population size of the voracious insect, which acts as a consumer feeding on the crop. The differential equations below describe how changes in the crop population affect the growth and behavior of the insect population, and vice versa, under the influence of an insecticide. - -$$\begin{eqnarray*} -\frac{dC}{dt} &= \theta C \left(1-\frac{C}{k}\right)-fCA \\ -\frac{dA}{dt} &= \phi f CA -(1 + p)\, \mu A -\end{eqnarray*}$$ - -Understanding this bitrophic interaction is crucial for predicting the impact of insect predation on crop yields and devising effective strategies for pest management in agriculture and ecological conservation efforts. - -In these equations, $C$ and $A$ are both expressed in $kg/ha$, $\theta=0.2\;d^{-1}$, $k=4000\;kg/ha$, $f=0.001\;ha/(kg\,d)$, the efficiency ratio $\phi=0.2$, and the mortality ratio $\mu=0.1\;d^{-1}$. The crop can be treated with an insecticide which increases the insect's death coefficient by a factor of $p=3$. The factor $p$ depends on the applied insecticide concentration and can therefore be controlled externally. - -At the beginning of a season, $100\;kg$ of the crop and $0.5\;kg$ of insects per $ha$ are present. -""" - -# ╔═╡ 87508f87-42d7-44e7-a0fb-415e6d7190c2 -md""" -Set up a *reaction network* model by analysing the terms in the above differential equations and simulate the evolution of $C$ and $A$ for $200$ days. Next, perform a sensitivity analysis of $C$ and $A$ wrt. the parameters $\theta$, $\phi$ and $p$. -""" - -# ╔═╡ eeb52e5e-f57b-4746-bbfc-7f043fc50e96 -md""" -Set up a *reaction network* model and name it `bitrophic_model`.\ -Hints: -- $C$ is growing (i.e., $C \rightarrow 2C$) at a rate $\theta \left(1-\frac{C}{k}\right)$. -- The insects $A$ eat crops $C$ (i.e., $C + A$) at a rate $f$ resulting in an increase of a factor of $\phi$ more insects (i.e., $(1+\phi)A$). -- The insects $A$ are dying (i.e., $A \rightarrow 0$) at a rate $(1 + p)\, \mu$. -""" - -# ╔═╡ 0b902d89-f0c2-4621-998e-7013931315ae -# bitrophic_model = @reaction_network begin -# missing # Uncomment and complete the instruction -# missing # Uncomment and complete the instruction -# missing # Uncomment and complete the instruction -# end - -# ╔═╡ 92027dcf-9765-4fad-a0a4-f19967ce6bce -md""" -Check out the species and the parameters. -""" - -# ╔═╡ 15dbf491-c5ae-4682-a21d-1a815a957993 -# missing # Uncomment and complete the instruction - -# ╔═╡ 051f1b87-34ec-4e3f-8d4e-c4e42430be8f -# missing # Uncomment and complete the instruction - -# ╔═╡ f11bae81-a855-48ec-90a7-16b4ea1fc30b -md""" -Convert the system to a symbolic differential equations model, name it `osys` and verify, by analyzing the differential equations, that your model is correctly implemented. -""" - -# ╔═╡ 1a19c02c-5d0f-4271-b0e7-7da873030d7e -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 55eba88f-d4cf-4ccf-b061-51feb3a4290c -md""" -Initialize a vector `u0` with the initial conditions, define the timespan in `tspan` and initialize a vector `param` with the parameter values: -""" - -# ╔═╡ 7769c7a5-5f19-4e1a-8721-41ee03c2024e -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 00453a3c-88d1-4cc4-a8f2-d4fa2eed0b66 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 4e1acbdd-65ce-4f5e-b557-633af8a11aa9 -md""" -For clarity, we will use the variables `θ`, `ϕ` and `p` to store the parameter values that are used for the calculation of the sensitivity functions. -""" - -# ╔═╡ 9fda8191-e28d-4d10-b330-c386711a2a73 -θ = 0.2 - -# ╔═╡ ae172c81-539c-4482-9cd2-ab74fd83c26a -ϕ = 0.2 - -# ╔═╡ 28417aea-9c05-4ba0-8b85-b8cccd808238 -p = 3 - -# ╔═╡ 157eae84-9c8a-4d23-9ada-9d53ba520b0a -# params = missing # Uncomment and complete the instruction - -# ╔═╡ eddc01fa-3ab0-4fd0-963f-5704f48778a7 -md""" -Create the ODE problem and store it in `oprob`. Next, solve the ODE problem using `Tsit5()` and `saveat=0.5`, and store the solution in `osol`. Finally plot the results. -""" - -# ╔═╡ 1c185585-7c7a-4073-85f5-8300d3fe19e4 -# oprob = missing; # Uncomment and complete the instruction - -# ╔═╡ 2f1b4426-7235-4caf-84ed-ec6e04eaa34a -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ 63b33638-b967-43a7-b6ff-f6912f9a12e3 -# missing # Uncomment and complete the instruction - -# ╔═╡ fc1ae98d-0126-4eb3-86e6-20342acf0340 -md""" -Interpret your results. Try to answer the following question(s): -""" - -# ╔═╡ 81c6198f-5f69-4d78-b67c-b6e283a6c3b1 -md""" -!!! question - 1. What happens to $C$ and $A$ during the first 30 days? -""" - -# ╔═╡ 9c42da1d-f80a-4ad5-bc4f-caec9a1dadb6 -md"- Answer: missing" - -# ╔═╡ cd1ea360-4fa0-47ee-95ea-5a34dbd0aff9 -md""" -!!! question - 2. Why does $C$ starts to decline around day 40? -""" - -# ╔═╡ 047cc4df-1c6c-4371-a6e9-fe83a402b342 -md"- Answer: missing" - -# ╔═╡ b75a8e32-39c2-41dd-95ff-5cb6de30199b -md""" -!!! question - 3. What happens to $C$ and $A$ from day 50 on, do they finally reach steady state values? -""" - -# ╔═╡ c9caecc4-8d14-4885-b087-54a9226be597 -md"- Answer: missing" - -# ╔═╡ aa07ac11-1d6e-41ea-885d-1a6ae38bd58d -md""" -Write a solution function with as argument a vector of the parameters (that you want the sensitivity on), and that returns the outputs. -""" - -# ╔═╡ 2a858811-9ffd-4dc7-84ba-f7a59421753f -# Uncomment and complete the instruction -# function bitrophic_model_sim(params) - # θ, ϕ, p = missing - # u0 = missing - # tspan = missing - # params = missing - # oprob = missing - # osol = missing - # return missing -# end - -# ╔═╡ e911dc09-c56a-42ed-9a09-9c4bba1aa5f6 -md""" -Make two functions based on the solution function that each returns a single output, hence, one function that returns the output $C$, and another function that returns the output $A$. -""" - -# ╔═╡ 2e43110a-d948-4e6f-9ed9-a35101ba223a -# bitrophic_model_sim_C(params) = missing # Uncomment and complete the instruction - -# ╔═╡ 298604da-3207-40db-8535-f628d17a0f39 -# bitrophic_model_sim_A(params) = missing # Uncomment and complete the instruction - -# ╔═╡ 76b256dc-bdec-4fb8-95c7-b32df03ba8b1 -md""" -Make the time vector. -""" - -# ╔═╡ 3d1f2c90-2e4e-4fa1-af41-9d44d00e5090 -# t_vals = missing # Uncomment and complete the instruction - -# ╔═╡ c2ca4b68-39a6-45f7-a2a1-0f5c4903ed07 -md""" -Compute the two outputs $C$ and $A$ for the given parameter values. -""" - -# ╔═╡ 7bc637be-8238-43f4-b190-b46833fc9d34 -# C_sim = missing # Uncomment and complete the instruction - -# ╔═╡ 127d8761-97d6-4fcb-b633-98ee30ebc617 -# A_sim = missing # Uncomment and complete the instruction - -# ╔═╡ cda34ccc-e018-4e2c-a4d3-8ed3d0c1624b -md""" -Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouputs $C$ and $A$. Hence, you need to call `ForwardDiff.jacobian` twice. -""" - -# ╔═╡ 55f00963-bfb0-420f-ae6f-d20f93f01b1e -# sens_C = missing # Uncomment and complete the instruction - -# ╔═╡ 4d2c77f2-1a5f-437c-adb6-232155f2cafb -# sens_A = missing # Uncomment and complete the instruction - -# ╔═╡ c261b580-2cc7-4aba-af15-1778f9293278 -md""" -Extract the (absolute) sensitivities of the outputs on the different parameters. -""" - -# ╔═╡ 730de2d2-104b-4f4a-96da-4f96b0445f95 -# Uncomment and complete the instruction -# begin -# sens_C_on_θ = missing -# sens_C_on_ϕ = missing -# sens_C_on_p = missing -# end; - -# ╔═╡ 2d8df796-c362-49d9-b938-26f1917e800b -# Uncomment and complete the instruction -# begin -# sens_A_on_θ = missing -# sens_A_on_ϕ = missing -# sens_A_on_p = missing -# end; - -# ╔═╡ 6b3ee543-e3e2-4fe6-925d-1f201243d014 -md""" -Compute the normalized sensitivities. -""" - -# ╔═╡ 89830894-4481-480b-954d-b24971590eaf -# Uncomment and complete the instruction -# begin -# sens_C_on_θ_rel = missing -# sens_C_on_ϕ_rel = missing -# sens_C_on_p_rel = missing -# end; - -# ╔═╡ c2b5c2c4-3aa6-466d-b106-d0d7aef43bdf -# Uncomment and complete the instruction -# begin -# sens_A_on_θ_rel = missing -# sens_A_on_ϕ_rel = missing -# sens_A_on_p_rel = missing -# end; - -# ╔═╡ 3814640e-ed0b-48a1-b4ac-6b7f04d899f3 -md""" -Plot the sensitivity functions of $C$ and $A$ on $\theta$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`). -""" - -# ╔═╡ bbe8980e-441d-4ac6-abc5-88ff77de9f24 -# missing # Uncomment and complete the instruction - -# ╔═╡ c6eb2f89-2737-4eb0-8fec-dd87ea962051 -md""" -Interpret your results. Try to answer the following question(s): -""" - -# ╔═╡ a8257414-2d6a-4913-9f05-6369c62189e8 -md""" -!!! question - 1. In steady state, does $\theta$ have any influence on $C$? Explain why this could be. -""" - - -# ╔═╡ 68264b8a-a89f-465b-bedb-1b85e960a25e -md"- Answer: missing" - -# ╔═╡ f84e6cdd-be44-464e-865b-24feca38df0f -md""" -!!! question - 2. In steady state, why does $\theta$ have a positive effect on $A$? Explain why this could be. -""" - -# ╔═╡ a7d513eb-66b3-4167-b799-74a37718a315 -md"- Answer: missing" - -# ╔═╡ 882f2f6b-eb58-4c8f-877e-76e460900c0c -md""" -Plot the sensitivity functions of $C$ and $A$ on $\phi$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`). -""" - -# ╔═╡ f76e5c58-12fc-4b2d-8718-e15144fbef98 -# missing # Uncomment and complete the instruction - -# ╔═╡ 0bd84cf5-c0a2-4bb2-b5f4-f16da6eaab04 -md""" -Plot the sensitivity functions of $C$ and $A$ on $p$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`). -""" - -# ╔═╡ cb02ebf8-aee5-47a2-9496-6f63fc123147 -# missing # Uncomment and complete the instruction - -# ╔═╡ 75309ee6-17b6-4ebb-b21c-a5e4a2a97510 -md""" -Interpret your results. Try to answer the following question(s): -""" - -# ╔═╡ 08eaa553-7b74-4f02-9328-b23add528c06 -md""" -!!! question - 1. In steady state, does $\phi$ have a positive or negative effect on $C$? Explain why this could be. -""" - -# ╔═╡ a299b51b-1b7b-4420-9f91-bfe1d8c43158 -md"- Answer: missing" - -# ╔═╡ 58afee9f-1157-45b1-99d9-f796b474975a -md""" -!!! question - 2. In steady state, does $p$ have a positive or negative effect on $C$? Explain why this could be. -""" - -# ╔═╡ 24d198c3-9fd2-4ea3-8ab1-c7f027af78be -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═fd357fe0-0920-11ef-02e4-25a84575c6a2 -# ╠═0ddc1082-bf8d-4357-a056-2fa861824b47 -# ╠═a3c1055e-b44f-4412-9ca0-f8ec5f972494 -# ╠═cce8fcde-069f-49b9-aadd-6abccbcfbace -# ╠═b48e4b46-3a07-4ff0-b617-6bc1125f6a1e -# ╠═ddc035a2-3b03-4a54-807c-05a271384ca7 -# ╟─ceec287b-5622-48d2-9fe1-26b3e84e3f45 -# ╟─2a732f39-0499-4c90-be62-a1e2278e0c8b -# ╟─87508f87-42d7-44e7-a0fb-415e6d7190c2 -# ╟─eeb52e5e-f57b-4746-bbfc-7f043fc50e96 -# ╠═0b902d89-f0c2-4621-998e-7013931315ae -# ╟─92027dcf-9765-4fad-a0a4-f19967ce6bce -# ╠═15dbf491-c5ae-4682-a21d-1a815a957993 -# ╠═051f1b87-34ec-4e3f-8d4e-c4e42430be8f -# ╟─f11bae81-a855-48ec-90a7-16b4ea1fc30b -# ╠═1a19c02c-5d0f-4271-b0e7-7da873030d7e -# ╟─55eba88f-d4cf-4ccf-b061-51feb3a4290c -# ╠═7769c7a5-5f19-4e1a-8721-41ee03c2024e -# ╠═00453a3c-88d1-4cc4-a8f2-d4fa2eed0b66 -# ╟─4e1acbdd-65ce-4f5e-b557-633af8a11aa9 -# ╠═9fda8191-e28d-4d10-b330-c386711a2a73 -# ╠═ae172c81-539c-4482-9cd2-ab74fd83c26a -# ╠═28417aea-9c05-4ba0-8b85-b8cccd808238 -# ╠═157eae84-9c8a-4d23-9ada-9d53ba520b0a -# ╟─eddc01fa-3ab0-4fd0-963f-5704f48778a7 -# ╠═1c185585-7c7a-4073-85f5-8300d3fe19e4 -# ╠═2f1b4426-7235-4caf-84ed-ec6e04eaa34a -# ╠═63b33638-b967-43a7-b6ff-f6912f9a12e3 -# ╟─fc1ae98d-0126-4eb3-86e6-20342acf0340 -# ╟─81c6198f-5f69-4d78-b67c-b6e283a6c3b1 -# ╠═9c42da1d-f80a-4ad5-bc4f-caec9a1dadb6 -# ╟─cd1ea360-4fa0-47ee-95ea-5a34dbd0aff9 -# ╠═047cc4df-1c6c-4371-a6e9-fe83a402b342 -# ╟─b75a8e32-39c2-41dd-95ff-5cb6de30199b -# ╠═c9caecc4-8d14-4885-b087-54a9226be597 -# ╟─aa07ac11-1d6e-41ea-885d-1a6ae38bd58d -# ╠═2a858811-9ffd-4dc7-84ba-f7a59421753f -# ╟─e911dc09-c56a-42ed-9a09-9c4bba1aa5f6 -# ╠═2e43110a-d948-4e6f-9ed9-a35101ba223a -# ╠═298604da-3207-40db-8535-f628d17a0f39 -# ╟─76b256dc-bdec-4fb8-95c7-b32df03ba8b1 -# ╠═3d1f2c90-2e4e-4fa1-af41-9d44d00e5090 -# ╟─c2ca4b68-39a6-45f7-a2a1-0f5c4903ed07 -# ╠═7bc637be-8238-43f4-b190-b46833fc9d34 -# ╠═127d8761-97d6-4fcb-b633-98ee30ebc617 -# ╟─cda34ccc-e018-4e2c-a4d3-8ed3d0c1624b -# ╠═55f00963-bfb0-420f-ae6f-d20f93f01b1e -# ╠═4d2c77f2-1a5f-437c-adb6-232155f2cafb -# ╟─c261b580-2cc7-4aba-af15-1778f9293278 -# ╠═730de2d2-104b-4f4a-96da-4f96b0445f95 -# ╠═2d8df796-c362-49d9-b938-26f1917e800b -# ╟─6b3ee543-e3e2-4fe6-925d-1f201243d014 -# ╠═89830894-4481-480b-954d-b24971590eaf -# ╠═c2b5c2c4-3aa6-466d-b106-d0d7aef43bdf -# ╟─3814640e-ed0b-48a1-b4ac-6b7f04d899f3 -# ╠═bbe8980e-441d-4ac6-abc5-88ff77de9f24 -# ╟─c6eb2f89-2737-4eb0-8fec-dd87ea962051 -# ╟─a8257414-2d6a-4913-9f05-6369c62189e8 -# ╠═68264b8a-a89f-465b-bedb-1b85e960a25e -# ╟─f84e6cdd-be44-464e-865b-24feca38df0f -# ╠═a7d513eb-66b3-4167-b799-74a37718a315 -# ╟─882f2f6b-eb58-4c8f-877e-76e460900c0c -# ╠═f76e5c58-12fc-4b2d-8718-e15144fbef98 -# ╟─0bd84cf5-c0a2-4bb2-b5f4-f16da6eaab04 -# ╠═cb02ebf8-aee5-47a2-9496-6f63fc123147 -# ╟─75309ee6-17b6-4ebb-b21c-a5e4a2a97510 -# ╟─08eaa553-7b74-4f02-9328-b23add528c06 -# ╠═a299b51b-1b7b-4420-9f91-bfe1d8c43158 -# ╟─58afee9f-1157-45b1-99d9-f796b474975a -# ╠═24d198c3-9fd2-4ea3-8ab1-c7f027af78be diff --git a/src/exercises/sens_fermenter_monod.jl b/src/exercises/sens_fermenter_monod.jl deleted file mode 100755 index 45ed8e27..00000000 --- a/src/exercises/sens_fermenter_monod.jl +++ /dev/null @@ -1,432 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "29" -#> title = "6. Sensitivity fermenter monod" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Sensitivity fermenter monod" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - - -using Markdown -using InteractiveUtils - -# ╔═╡ 3ef93246-657d-4e77-9bf0-8380c64bfcfd -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 55cdebd2-0881-11ef-2722-91de1447877a -using Markdown - -# ╔═╡ 03ae0690-06a0-4276-9f00-d07b206fe124 -using InteractiveUtils - -# ╔═╡ a355b0ba-baaf-49f4-a5dc-965364a884f0 -using Catalyst - -# ╔═╡ 00fd6d49-f561-42e9-9413-d33af92f83dc -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 7ae714c4-d25d-4f9f-ab3d-cc067db9c156 -using ForwardDiff - -# ╔═╡ 31d294d1-3a1f-41db-abff-54f2a67c7ed9 -md""" -### Exercise: Fermenter - Monod kinetics - Sensitivity analysis -""" - -# ╔═╡ 5ffe7dcb-620d-4f22-95fe-2f77cda6fbe7 -md""" -In one of the previous practicals we were introduced to a fermenter in which biomass $X$ [$g/L$] grows by breaking down substrate $S$ [$g/L$]. The reactor is fed with an inlet flow rate $Q_{in}$ [$L/h$], which consists of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. This process was modelled using Monod kinetics, resulting in the model below: - -$$\begin{eqnarray*} -S + X \xrightarrow[\quad\quad]{k} (1 + Y) \, X \quad\quad\quad\quad \textrm{with} \quad k = \cfrac{\mu_{max}}{S + K_s} -\end{eqnarray*}$$ -""" - -# ╔═╡ 6ec6da23-853b-4129-94cf-67b5cadb1f95 -md""" -The *reaction network object* model for this problem could be defined as: -""" - -# ╔═╡ 935ca610-7a7a-4692-8908-fc26abb880b4 -fermenter_monod = @reaction_network begin - μmax/(S+Ks), S + X --> (1 + Y)*X - # Alternatives: - # mm(S, μmax, Ks)*X, S => Y*X - # mm(S, μmax, Ks)*X, S + X => (1 + Y)*X - Q/V, (S, X) --> 0 - Q/V*Sin, 0 --> S -end - -# ╔═╡ 79e6056a-881c-442f-8989-5bc284d3d777 -md""" -which resulted in the following differential equations: -""" - -# ╔═╡ fa93e2c3-8b43-418e-ba24-406645b2e397 -md""" -$$\begin{eqnarray*} -\cfrac{dS}{dt} &=& \cfrac{Q}{V} \left(S_{in} - S \right) - \mu_{max}\cfrac{S}{S + K_s} X\\ -\cfrac{dX}{dt} &=& -\cfrac{Q}{V} X + Y \mu_{max}\cfrac{S}{S + K_s} X -\end{eqnarray*}$$ -""" - -# ╔═╡ 06730f54-7293-43f2-b772-84eec3e5528a -md""" -Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. - -In case you want to use the `mm` function, keep in mind that `mm(S, μmax, Ks)` stands for $\mu_{max} \, \cfrac{S}{S + K_s}$. -""" - -# ╔═╡ 7f8b7a2e-bc65-4b51-ad59-bd7ac98604dd -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 55f1d688-0c53-481b-9965-5e92ca87ad83 -md""" -The parameter values are $\mu_{max} = 0.40$, $K_s = 0.015$, $Y = 0.67$, $Q = 2.0$, $V = 40.0$ and $S_{in} = 0.022\;g/L$. Suppose that at $t=0$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concetration of $0.0005\;g/L$.\ -Compute the following in a timespan of $[0, 100]\,h$: - -- The sensitivities of $S$ and $X$ wrt. $\mu_{max}$, $K_s$ and $S_{in}$. - -Plot the following: -- A figure with the sensitivity functions of $S$ and $X$ wrt. $S_{in}$. -- A figure with the sensitivity functions of $S$ wrt. $\mu_{max}$, $K_s$ and $S_{in}$. -- A figure with the sensitivity functions of $X$ wrt. $\mu_{max}$, $K_s$ and $S_{in}$. - -Interpret your results. -""" - -# ╔═╡ 6c4e3c09-4b84-4f5c-8739-2ac18e6f2af6 -md""" -Initialize a vector `u0` with the initial conditions, set the timespan and initialize a vector `param` with the parameter values: -""" - -# ╔═╡ 2ee277e5-ce4a-4ade-be0e-9bba7a4dc08c -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 3fdc6b17-cdeb-4dc5-8886-9d3a62caac8d -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 0139da85-02e3-4021-9b39-84af7e68d428 -md""" -For the sake of clarity, we will use the variables `μmax`, `Ks` and `Sin` to store the parameter values that are used for the calculation of the sensitivity functions. -""" - -# ╔═╡ 0f995929-4d2b-4a7a-8da1-04e4d501385f -# μmax = missing # Uncomment and complete the instruction - -# ╔═╡ 262e8346-df6d-49bf-9186-92f5afb421e0 -# Ks = missing # Uncomment and complete the instruction - -# ╔═╡ baa777d2-abb8-45f8-87aa-b3b17c8dc07c -# Sin = missing # Uncomment and complete the instruction - -# ╔═╡ 79b0eb65-5a0f-40b3-aa97-4088421c562e -# params = missing # Uncomment and complete the instruction - -# ╔═╡ f0f4fa14-6f99-4f21-a743-be61e08444a7 -md""" -Create the ODE problem and store it in `oprob`. Next, solve the ODE problem using `Tsit5()` and `saveat=0.5`, and store the solution in `osol`. Finally plot the results. -""" - -# ╔═╡ 8b2f23f6-80b2-4e63-942e-e5cd17d8ba72 -# oprob = missing; # Uncomment and complete the instruction - -# ╔═╡ 89a31c32-88a4-479f-a688-ffcb75ee8e91 -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ 51a9b7e6-8ad9-477d-9596-ffd614df2c79 -# missing # Uncomment and complete the instruction - -# ╔═╡ 693844d0-3858-4861-bae0-b47e78809f17 -md""" -Write a solution function with as argument a vector of the parameters (with the values for which we want to calculate the sensitivity), and that returns the outputs. -""" - -# ╔═╡ 9622f7ca-f71a-4ad9-a309-d7d10a1c3e3b -# Uncomment and complete the instruction -# function fermenter_monod_sim(params) - # μmax, Ks, Sin = missing - # u0 = missing - # tspan = missing - # params = missing - # oprob = missing; - # osol = missing - # return missing -# end - -# ╔═╡ 4a5971b1-f4d0-43b6-805f-e17f5052ae92 -md""" -Make two functions based on the solution function, where each returns a single output; hence, one function that returns the output $S$, and another function that returns the output $X$. -""" - -# ╔═╡ f40c6402-3c28-4a7d-b629-83507a9f29bd -# fermenter_monod_sim_S(params) = missing # Uncomment and complete the instruction - -# ╔═╡ 3ae5bd00-2e06-4789-aab3-d897824d5e29 -# fermenter_monod_sim_X(params) = missing # Uncomment and complete the instruction - -# ╔═╡ 4bd2bcca-9c42-4333-b062-2aaa9f7be3fe -md""" -Make the time vector. -""" - -# ╔═╡ fbd98975-aa32-46ae-8db0-0e65cdf48309 -# t_vals = missing # Uncomment and complete the instruction - -# ╔═╡ 93791eb3-1eaa-4146-90b5-c4811fb3485b -md""" -Compute the two outputs $S$ and $X$ for the given parameter values. -""" - -# ╔═╡ dc0557d6-81b9-4759-8ed7-3129f60c6dc3 -# S_sim = missing # Uncomment and complete the instruction - -# ╔═╡ 95bc683c-f6e6-4b42-b90b-b5a863edd4d5 -# X_sim = missing # Uncomment and complete the instruction - -# ╔═╡ fa970c0e-fb3b-486f-bbc1-345d44f8f0da -md""" -Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouputs $S$ and $X$. Hence, you need to call `ForwardDiff.jacobian` twice. -""" - -# ╔═╡ 64354302-f4cc-4592-9302-5db0f5bccb2e -# sens_S = missing # Uncomment and complete the instruction - -# ╔═╡ 49a94b9a-a543-495e-b4f1-c8579e59304d -# sens_X = missing # Uncomment and complete the instruction - -# ╔═╡ 9cace6c1-e678-4dd7-8705-92a55eb32fa9 -md""" -Extract the (absolute) sensitivities of the outputs on the different parameters. -""" - -# ╔═╡ a6dc2b60-6a0a-4140-892e-02cde8dc79d3 -# Uncomment and complete the instruction -# begin -# sens_S_on_μmax = missing -# sens_S_on_Ks = missing -# sens_S_on_Sin = missing -# end; - -# ╔═╡ f806c243-9032-46b7-add3-4714344691c7 -# Uncomment and complete the instruction -# begin -# sens_X_on_μmax = missing -# sens_X_on_Ks = missing -# sens_X_on_Sin = missing -# end; - -# ╔═╡ 5bf3a62d-d2aa-4653-8ee8-e90caa9504e8 -md""" -Compute the normalized sensitivities. -""" - -# ╔═╡ 76846731-929c-408f-a3de-970581c497e9 -# Uncomment and complete the instruction -# begin -# sens_S_on_μmax_rel = missing -# sens_S_on_Ks_rel = missing -# sens_S_on_Sin_rel = missing -# end - -# ╔═╡ b6c57444-547c-4e82-8526-6a30566e07c5 -# Uncomment and complete the instruction -# begin -# sens_X_on_μmax_rel = missing -# sens_X_on_Ks_rel = missing -# sens_X_on_Sin_rel = missing -# end; - -# ╔═╡ 5388c2a7-5a11-4da8-be09-46045cde8a4e -md""" -Plot the sensitivity functions of $S$ and $X$ wrt. $S_{in}$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`), and set the line width to 2 (`linewidth=...`). -""" - -# ╔═╡ db840c76-a6c6-49fb-a0bb-d9149f947bc0 -# missing # Uncomment and complete the instruction - -# ╔═╡ 02314e9f-6186-4c97-9b28-03a4a1a15668 -maximum(sens_X_on_Sin_rel) - -# ╔═╡ d41375ef-6958-4705-a417-4c6a491232ee -md""" -Interpret your results. Try to answer the following question(s): -""" - -# ╔═╡ f6be14f3-e0d0-41dc-bc7b-54174b1ea5ea -md""" -!!! question - Which output variable, $S$ or $X$, is most sensitive to $S_{in}$ in steady state? -""" - -# ╔═╡ b2583ee2-412b-4a0a-a4d0-fe476e0510e5 -md"- Answer: missing" - -# ╔═╡ dbbf694b-942a-4757-a255-f67b208ba03b -md""" -!!! question - Why is the sensitivity function of $S$ wrt. $S_{in}$ at first positive but then becomes zero? -""" - -# ╔═╡ f10ac0f2-6b14-4805-b649-56c63f5b523a -md"- Answer: missing" - -# ╔═╡ be89600a-4927-4afc-9813-d8a70adb2852 -md""" -Plot the sensitivity functions of $S$ wrt. $\mu_{max}$, $K_s$ and $S_{in}$. Provide a suitable title (`title="..."`), labels (`label=["..." "..." "..."]`) and an x-label (`xlabel="..."`), and set the line width to 2 (`linewidth=...`). -""" - -# ╔═╡ c0223da4-9959-48d0-b607-633b2e82986c -# missing # Uncomment and complete the instruction - -# ╔═╡ 260ede0e-2584-4fe9-ae92-69990ca8f854 -md""" -Interpret your results. Try to answer the following question(s): -""" - -# ╔═╡ ff86a29f-9308-473b-aa1c-dfd4af8179c7 -md""" -!!! question - Which parameter, $\mu_{max}$, $K_s$ or $S_{in}$, affects the output $S$ the most in steady state? Why is this? -""" - -# ╔═╡ 555228b2-8075-49ac-a7ef-f51aa51dd95d -md"- Answer: missing" - -# ╔═╡ 6704e43b-30bc-4168-93db-fb853c9761fa -md""" -!!! question - Why is the sensitivity function of $S$ wrt. $\mu_{max}$ negative? -""" - -# ╔═╡ 4d2094d8-98b3-4ac9-9612-942b5cd18ce0 -md"- Answer: missing" - -# ╔═╡ dc8ef7cb-ae97-401c-9a25-12439e4363c6 -md""" -!!! question - Why is the sensitivity function of $S$ wrt. $K_s$ positive? Does this correspond to the meaning of the half-saturation constant $K_s$ or substrate concentration that allows to achieve half the maximum growth rate? *In other words: do higher values of $K_s$ support growth at low substrate concentrations?* -""" - -# ╔═╡ 66712fb2-50ea-41be-bcca-305319d158e9 -md"- Answer: missing" - -# ╔═╡ 16a84fdb-8ce2-45b9-bfb7-7f4e1284a1d7 -md""" -Plot the sensitivity functions of $X$ on $\mu_{max}$, $K_s$ and $S_{in}$. Provide a suitable title (`title="..."`), labels (`label=["..." "..." "..."]`) and an x-label (`xlabel="..."`), and set the line width to 2 (`linewidth=...`). -""" - -# ╔═╡ 53134149-0bf7-41c1-9b35-e5037744211f -# missing - -# ╔═╡ 0355bbf6-853d-4bd8-b32f-98fdbd2b761c -md""" -Interpret your results. Try to answer the following question(s): -""" - -# ╔═╡ 355ca6a7-466b-4969-ab48-28e2257f9810 -md""" -!!! question - Which parameter, $\mu_{max}$, $K_s$ or $S_{in}$, affects the output $X$ the most in steady state? -""" - -# ╔═╡ 4e08baf1-eeb6-49be-9751-204dd9ae1103 -md"- Answer: missing" - -# ╔═╡ 182e0c2b-9aa4-4514-96e8-b6a9f53b371b -md""" -!!! question - Why is the sensitivity function of $X$ wrt. $\mu_{max}$ positive? How does this compare to substrate $S$? -""" - -# ╔═╡ 05ec320e-ac87-45af-904e-5d69639e1f27 -md"- Answer: missing" - -# ╔═╡ 22e98c77-0e9f-444a-a2f0-b940940996b7 -md""" -!!! question - Why is the sensitivity function of $X$ wrt. $K_s$ negative? Compare it to that of substrate $S$. -""" - -# ╔═╡ 91795413-c9a2-43cb-a7b0-d9e055e1cf94 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═55cdebd2-0881-11ef-2722-91de1447877a -# ╠═03ae0690-06a0-4276-9f00-d07b206fe124 -# ╠═3ef93246-657d-4e77-9bf0-8380c64bfcfd -# ╠═a355b0ba-baaf-49f4-a5dc-965364a884f0 -# ╠═00fd6d49-f561-42e9-9413-d33af92f83dc -# ╠═7ae714c4-d25d-4f9f-ab3d-cc067db9c156 -# ╟─31d294d1-3a1f-41db-abff-54f2a67c7ed9 -# ╟─5ffe7dcb-620d-4f22-95fe-2f77cda6fbe7 -# ╟─6ec6da23-853b-4129-94cf-67b5cadb1f95 -# ╠═935ca610-7a7a-4692-8908-fc26abb880b4 -# ╟─79e6056a-881c-442f-8989-5bc284d3d777 -# ╟─fa93e2c3-8b43-418e-ba24-406645b2e397 -# ╟─06730f54-7293-43f2-b772-84eec3e5528a -# ╠═7f8b7a2e-bc65-4b51-ad59-bd7ac98604dd -# ╟─55f1d688-0c53-481b-9965-5e92ca87ad83 -# ╟─6c4e3c09-4b84-4f5c-8739-2ac18e6f2af6 -# ╠═2ee277e5-ce4a-4ade-be0e-9bba7a4dc08c -# ╠═3fdc6b17-cdeb-4dc5-8886-9d3a62caac8d -# ╟─0139da85-02e3-4021-9b39-84af7e68d428 -# ╠═0f995929-4d2b-4a7a-8da1-04e4d501385f -# ╠═262e8346-df6d-49bf-9186-92f5afb421e0 -# ╠═baa777d2-abb8-45f8-87aa-b3b17c8dc07c -# ╠═79b0eb65-5a0f-40b3-aa97-4088421c562e -# ╟─f0f4fa14-6f99-4f21-a743-be61e08444a7 -# ╠═8b2f23f6-80b2-4e63-942e-e5cd17d8ba72 -# ╠═89a31c32-88a4-479f-a688-ffcb75ee8e91 -# ╠═51a9b7e6-8ad9-477d-9596-ffd614df2c79 -# ╟─693844d0-3858-4861-bae0-b47e78809f17 -# ╠═9622f7ca-f71a-4ad9-a309-d7d10a1c3e3b -# ╟─4a5971b1-f4d0-43b6-805f-e17f5052ae92 -# ╠═f40c6402-3c28-4a7d-b629-83507a9f29bd -# ╠═3ae5bd00-2e06-4789-aab3-d897824d5e29 -# ╟─4bd2bcca-9c42-4333-b062-2aaa9f7be3fe -# ╠═fbd98975-aa32-46ae-8db0-0e65cdf48309 -# ╟─93791eb3-1eaa-4146-90b5-c4811fb3485b -# ╠═dc0557d6-81b9-4759-8ed7-3129f60c6dc3 -# ╠═95bc683c-f6e6-4b42-b90b-b5a863edd4d5 -# ╟─fa970c0e-fb3b-486f-bbc1-345d44f8f0da -# ╠═64354302-f4cc-4592-9302-5db0f5bccb2e -# ╠═49a94b9a-a543-495e-b4f1-c8579e59304d -# ╟─9cace6c1-e678-4dd7-8705-92a55eb32fa9 -# ╠═a6dc2b60-6a0a-4140-892e-02cde8dc79d3 -# ╠═f806c243-9032-46b7-add3-4714344691c7 -# ╟─5bf3a62d-d2aa-4653-8ee8-e90caa9504e8 -# ╠═76846731-929c-408f-a3de-970581c497e9 -# ╠═b6c57444-547c-4e82-8526-6a30566e07c5 -# ╟─5388c2a7-5a11-4da8-be09-46045cde8a4e -# ╠═db840c76-a6c6-49fb-a0bb-d9149f947bc0 -# ╠═02314e9f-6186-4c97-9b28-03a4a1a15668 -# ╟─d41375ef-6958-4705-a417-4c6a491232ee -# ╟─f6be14f3-e0d0-41dc-bc7b-54174b1ea5ea -# ╠═b2583ee2-412b-4a0a-a4d0-fe476e0510e5 -# ╟─dbbf694b-942a-4757-a255-f67b208ba03b -# ╠═f10ac0f2-6b14-4805-b649-56c63f5b523a -# ╟─be89600a-4927-4afc-9813-d8a70adb2852 -# ╠═c0223da4-9959-48d0-b607-633b2e82986c -# ╟─260ede0e-2584-4fe9-ae92-69990ca8f854 -# ╟─ff86a29f-9308-473b-aa1c-dfd4af8179c7 -# ╠═555228b2-8075-49ac-a7ef-f51aa51dd95d -# ╟─6704e43b-30bc-4168-93db-fb853c9761fa -# ╠═4d2094d8-98b3-4ac9-9612-942b5cd18ce0 -# ╟─dc8ef7cb-ae97-401c-9a25-12439e4363c6 -# ╠═66712fb2-50ea-41be-bcca-305319d158e9 -# ╟─16a84fdb-8ce2-45b9-bfb7-7f4e1284a1d7 -# ╠═53134149-0bf7-41c1-9b35-e5037744211f -# ╟─0355bbf6-853d-4bd8-b32f-98fdbd2b761c -# ╟─355ca6a7-466b-4969-ab48-28e2257f9810 -# ╠═4e08baf1-eeb6-49be-9751-204dd9ae1103 -# ╟─182e0c2b-9aa4-4514-96e8-b6a9f53b371b -# ╠═05ec320e-ac87-45af-904e-5d69639e1f27 -# ╟─22e98c77-0e9f-444a-a2f0-b940940996b7 -# ╠═91795413-c9a2-43cb-a7b0-d9e055e1cf94 diff --git a/src/exercises/sens_insuline.jl b/src/exercises/sens_insuline.jl deleted file mode 100755 index 67556bce..00000000 --- a/src/exercises/sens_insuline.jl +++ /dev/null @@ -1,531 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "31" -#> title = "6. Sensitivity insuline" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Sensitivity insuline" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). -macro bind(def, element) - #! format: off - quote - local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end - local el = $(esc(element)) - global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) - el - end - #! format: on -end - -# ╔═╡ 7bc363b0-9415-4954-807f-81a308bde531 -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 52b28a4c-b0bb-11ef-2841-17ecfb596676 -using StatsPlots, PlutoUI, OrdinaryDiffEq, ForwardDiff, Catalyst - -# ╔═╡ bfcc4b4e-073e-401e-851c-d01ef828028a -md""" -### Exercise: The minimal glucose model and dynamic compensation - sensitivity - -The Minimal Model of Glucose Regulation is a mathematical model used to describe how the body regulates glucose (sugar) levels in the blood. It was developed by Richard Bergman and Claudio Cobelli in the late 1970s and has become a cornerstone in diabetes research. - -We will use this exercise to study insulin sensitivity. - -The basic model considers only the concentration of glucose $G(t)$ in $mmol/L$ and the concentration of insulin $I(t)$ in $mmol/L$: - -- Glucose is added to a system with a zeroth-order rate of $m$ (later $m(t)$ if we model a non-fixed input). -- Glucose is removed from the blood with a rate of $sGI$, where $s$ is the insulin sensitivity. -- Insulin decays according to first-order kinetics with a rate parameter $\gamma$ -- $\beta$-cells produce insulin as a response to higher glucose concentrations. This is according to a saturated process, so it is well approximated using a Hill function ($n=2$). The rate of insulin production is given by $qBf(G)$, with $B$ the amount of $\beta$-cells, $q$ the maximal rate of insulin production/unit of cells and $f(G)$ the Hill function. -""" - -# ╔═╡ a5789d9d-a690-4c6b-8b8d-fbfe63f659d0 -md""" -The Hill function is defined as: - -$$hill(X, v, K, n) = \cfrac{v\,X^n}{X^n + K^n}$$ - -In order to have an idea of how it looks like, lets define it as `f_insuline(G)` for $v=1$, $K=5$ and $n=2$: -""" - -# ╔═╡ e93bc1a5-e0b1-425c-b069-d5ce80756e60 -f_insulin(G) = hill(G, 1, 5, 2) - -# ╔═╡ be33a8a3-a3f5-448d-9390-16f7c4ca099d -md""" -The following plot gives a fairly realistic response of insulin production as a function of glucose concentration in the blood. -""" - -# ╔═╡ 2ac43184-a393-4d39-9c3b-c299c3371b09 -plot(f_insulin, 0, 30, xlab="G (mmol/L)", ylab="f(G)", title="Insulin production rate") - -# ╔═╡ 75a5b5d9-f8d7-4728-83dc-03dde502dcfb -md"Below is a reaction network implementing this model. All parameters are set to 1.0 for didactic purposes." - -# ╔═╡ f62c2b4a-a9e8-472b-b07b-44072da08ee8 -glucose_insuline_circuit = @reaction_network begin - @parameters q=1.0 s=1.0 γ=1.0 m=1.0 B=1.0 Ks=1.0 - m, 0 --> G - s * I, G --> 0 - B * hill(G, q, Ks, 2), 0 --> I - γ, I --> 0 -end - -# ╔═╡ 882e106f-d3a5-4ccb-ae65-cfb5e4e5de57 -md""" -Convert the system to a symbolic differential equation model and inspect the differential equations. You do not need to make a new variable, just call `convert` with the right arguments. -""" - -# ╔═╡ 99ccbd1b-b9e4-4afc-8d9a-8c1055bcce0f -# missing # Uncomment and complete the instruction - -# ╔═╡ 3c1465d2-0886-4b74-baee-55c7dad41f9a -md""" -Simulate the system over a time interval of $0.0$ to $10.0$ hours with $m=1.0$ for various initial glucose concentrations (e.g., between $0.1$ and $5.0$) by means of the variable `G0` bound to the slider just here below. Use an initial insuline concentration of $0.0$. -""" - -# ╔═╡ 5bac69d1-0bd4-41bd-b15d-5ca6292a31e4 -@bind G0 Slider(0.1:0.1:5.0, default=1.0, show_value=true) - -# ╔═╡ 6ccd5ecc-5579-4ce5-9425-d1bbae0bc5ba -G0 - -# ╔═╡ afc40964-0a97-43a6-ab27-5fe96ecd319b -# Putting a semi-colon (;) after an instruction will hide its return value. -# oprob1 = missing; # Uncomment and complete the instruction - -# ╔═╡ f050b32e-1a14-4d5e-9fb3-5dbc4dfbf79c -# sol1 = missing; # Uncomment and complete the instruction - -# ╔═╡ cfbbfe48-a2c3-4691-9fac-b0a18f99222c -md""" -Plot the results. Use thereby `ylim=(0.0, 5.5)`. -""" - -# ╔═╡ 7e35b56b-44a0-46df-a812-f5fb437b672e -# missing # Uncomment and complete the instruction - -# ╔═╡ 68bdcedb-42b7-4fa8-8252-c2fbaa6e0187 - - -# ╔═╡ 8c7784df-1f6d-41e3-8548-7311710e599e -md""" -!!! question "Question" - What are the steady state concentrations for the two species? Does this depend on initial glucose levels (given enough time)? -""" - -# ╔═╡ 0133ab41-2c8c-4147-9594-1ad238833a9e -md"- Answer: missing" - -# ╔═╡ 479e0f11-e18e-4523-8d24-b2026acdeaef -md""" -Check out the final glucose and insuline concentrations (at the end time). -""" - -# ╔═╡ 824677b4-6533-453f-909f-bf04fe4bf5d9 -# missing # Uncomment and complete the instruction - -# ╔═╡ cef7f30d-43ef-4f2c-8bf3-35c140d237ce -md""" -Create a vector named `u1_guess` with the previous final values. -""" - -# ╔═╡ 9d3ba8bd-e9c3-4bb6-957d-c7f8211a2cda -# u1_guess = missing # Uncomment and complete the instruction - -# ╔═╡ 3f5e2195-da93-40fb-88ec-9e85a7bb6c24 -md""" -Calculate the steady-state values of glucose and insuline. -""" - -# ╔═╡ 3dae761f-5fb6-4f1c-a440-d2c21a6edb17 -# eq = missing # Uncomment and complete the instruction - -# ╔═╡ d48a6bfc-b533-41ca-a0de-df0af5738536 -# Geq = missing - -# ╔═╡ 087015da-d85d-4ebb-861a-b89e93a4055c -# Ieq = missing - -# ╔═╡ 314aa77c-5906-4772-94c5-f0b7cf648de0 -md""" -Check ou the steady state values for glucose and insulin. -""" - -# ╔═╡ 3b56f88c-0ca8-4f33-8516-11e639cb6a1a -# (missing, missing) # Uncomment and complete the instruction - -# ╔═╡ aab02fef-6d84-4f53-aff5-6f94bf2bd460 -md""" -Now simulate the system but rather than with $m$ being a constant glucose input, we give in a pulse of glucose (i.e., drinking a soda) with a peak at $t=5$h. **Note that our parameter now depends on the time!** -""" - -# ╔═╡ 8a5ce760-55bb-4610-9226-32c3d8376748 -glucose_pulse(t) = .5 + exp(-(t-5)^2) - -# ╔═╡ a2fb8c21-c594-44f3-92df-99e6d3afeba1 -plot(glucose_pulse, 0, 10, label="G [mmol/L]", xlabel="t") - -# ╔═╡ d95198d9-fb96-4389-bf66-f039a673d1a2 -md""" -For purpose of solving the ODE problem we will need to define $t$ as a (default) time variable with the command below. -""" - -# ╔═╡ 8bb8feaa-63e7-4777-ab79-92d98f84142b -t = default_t(); - -# ╔═╡ d7d91377-2b6b-4daa-aeb6-d0b20c20cd34 -md""" -Redo the ODE problem but now with `:m=>glucose_pulse(t)` as parameter. Use an initial value of $0.0$ for both glucose and insuline concentrations. -""" - -# ╔═╡ 6890e3ec-0875-423e-89c1-988411c869e3 -# oprob2 = missing; # Uncomment and complete the instruction - -# ╔═╡ cfcd040b-f762-4cf4-8b9c-1d77ac2837cd -md""" -Solve the new ODE problem using `Tsit5()` and `saveat=0.01`. -""" - -# ╔═╡ fb439f9d-ee88-43cd-b91b-44ed8013b27d -# sol2 = missing; # Uncomment and complete the instruction - -# ╔═╡ 83595165-3d71-43b3-93c0-4ba880e0b86e -md""" -Plot the results. -""" - -# ╔═╡ 095e359a-2ec4-4637-8477-93510bef23ed -# missing # Uncomment and complete the instruction - -# ╔═╡ 31aaced7-1fe4-4117-80f3-0fa70035822a -md""" -Up to now, we set $s$, the insulin sensitivity to $1$. This parameter represents how sensitive the body is to insuline in taking up glucose. Aging and obesity increase glucose resistance ($1/s$), resulting in diabetes! Explore the effect of this parameter on your plots below. -""" - -# ╔═╡ 53addc28-7c87-4c79-ad8f-4a41a3ca5f19 -md""" -We make a slider so that s can get values between 0.1 and 5.0 in step of 0.1. -""" - -# ╔═╡ 69e21e9b-7d0e-44e3-8b33-ee7102dadadf -@bind s Slider(0.1:0.1:5.0, default=1, show_value=true) - -# ╔═╡ b4073e81-6db8-4be6-a2d1-afa01640dbcf -s - -# ╔═╡ 4d58a832-9157-42fe-b578-c3fc29f01ea8 -md""" -Below we have made a function that computes/shows the steady state glucose concentration after $100$ hours. Initial values for $G$ and $I$ are $0.0$ and $m$ was set to $0.5$. -""" - -# ╔═╡ 7cf11e6f-7ef4-478f-8cc7-eae93374cc58 -function glucose_steady_state(s) - oprob = ODEProblem(glucose_insuline_circuit, [:G=>0.0, :I=>0.0], (0., 100.), [:m=>0.5, :s=>s]) - sol = solve(oprob, Tsit5(), saveat=0.01) - return sol[:G][end] # final (steady state) glucose concentration is returned -end - -# ╔═╡ 581b82c2-0957-4d20-a664-6ff80b5c40d9 -md""" -Calling the function results in the final (steady state) glucose concentration for a specific valu of $s$ set by the slider above. -""" - -# ╔═╡ 5eda8c4a-2baa-4987-b408-dbe7383abe95 -glucose_steady_state(s) - -# ╔═╡ 1c22a730-776e-4508-bdee-d8861c8949e4 -md""" -Below is a plot of the steady state value of $G$ as a function of $s$. -""" - -# ╔═╡ 34788b1d-1530-4b88-8690-03fe9af52a8b -plot(glucose_steady_state, 0.1, 5, xlabel="s", ylabel="Gss", label="Gss", ylim=(0, 5)) - -# ╔═╡ f616da2f-b51e-4328-bd3c-47e6d7377cbb -md""" -Use automatic differentiation with `ForwardDiff.derivative(..., ...)` to compute the absolute and relative sensitivity index. Is this system sensitive to the insuline sensitivity $s$? -""" - -# ╔═╡ 9be6f5b3-d280-4222-ad64-4b4a7b682310 -md""" -Calculate the absolute sensitivity. -""" - -# ╔═╡ 97636a73-4732-4377-b760-c36cef13904b -# Uncomment and complete the instruction -# sens_G(s) = ForwardDiff.derivative(..., ...) - -# ╔═╡ e0318ae7-d27d-4d73-bf44-632d813e8aa5 -md""" -Display the absolute sensitivity for the current $s$ value (cf. slider). -""" - -# ╔═╡ a67d70ee-c8b5-4f22-926c-f48c5dcf9815 -# missing # Uncomment and complete the instruction - -# ╔═╡ 407437a0-36b1-452f-910c-f244aeeedc4c -md""" -Calculate the normalized (total relative) sensitivity. -""" - -# ╔═╡ 1bb5834a-6a4e-40ec-82c1-f2c505ecfeb8 -# sens_G_rel(s) = missing # Uncomment and complete the instruction - -# ╔═╡ 66105045-a8f6-4513-a255-a9e1b0c78d37 -md""" -Display the normalized (total relative) sensitivity for the current $s$ value (cf. slider). -""" - -# ╔═╡ a3156073-07bd-4f13-be5e-d19483fbad28 -# missing # Uncomment and complete the instruction - -# ╔═╡ 0ae57436-0b8c-41ce-9d9d-8c48ed59b820 -md""" -We see that the final glucose concentration is highly dependent on $s$! This seems to be a flaw in the model, as we can imagine that the physiological parameters can greatly differ from person to person (for example, a person can have a large pancreas). The final glucose concentration should not depend on the insuline sensitivity $s$. - -A mechanism that stabilizes this is called *dynamic compensation*. Simply put, we have assumed here that the amount of beta cells ($B$) is fixed. However, in practice, these cells are capable of dividing, growing, and thus producing more insulin. Their growth rate depends on the concentration of glucose, creating an additional feedback loop that stabilizes the physiological circuit. - -```julia -μ(G), B --> 2B # dynamic compensation -``` - -""" - -# ╔═╡ 92d689a4-9f2b-4a0f-81a5-6194af06940c -md""" -Growth rate function depending on the glucose concentration. -""" - -# ╔═╡ 87d9c826-56af-409a-a884-d784cfa16a64 -μ(G) = 0.3atan(0.5(G-1)) - -# ╔═╡ 2f784fff-90fc-4273-8b9a-db4593849652 -md""" -The growth rate of the $\beta$-cells follows a sigmoid shape, being negative when $G$ is smaller than a threshold and positive if $G$ exceeds this threshold. This curve is plotted below. -""" - -# ╔═╡ 4d95e77b-fab0-454b-8319-1fe83e12f9e9 -plot(μ, xlim=(0, 30), xlab="G", label="μ(G)", title="Glucose-dependent growth rate") - -# ╔═╡ ea46f5f9-47a8-4641-a7e3-08a1a29cd04f -md""" -Add dynamic compensation to the model and show that this greatly reduces the sentitivty w.r.t. $s$. -""" - -# ╔═╡ 870e9229-374a-40b8-97f7-1a84d1a857e9 -md""" -Robust version of a *reaction network object* with dynamic compensation. -```julia -glucose_insuline_circuit_robust = @reaction_network begin - @parameters q=1 s=1 γ=1 m=1 Ks=1.0 - @species B(t)=1 - m, 0 --> G - s * I, G --> 0 - B * hill(G, q, Ks, 2), 0 --> I - γ, I --> 0 - μ(G), B --> 2B -end -``` -""" - -# ╔═╡ a8814cfd-8fd0-4628-b1ae-80211386a402 -md""" -Create the aforementioned robust version of a *reaction network object*. -""" - -# ╔═╡ 246a06ba-ef48-47ba-aef0-51f07ceb96e4 -# Uncomment and complete the instruction -# glucose_insuline_circuit_robust = @reaction_network begin -# @parameters missing -# @species missing -# missing -# ... -# end - -# ╔═╡ 69965b61-043b-44a0-998b-f5f34deefdd2 -md""" -Convert the system to a symbolic differential equation model and inspect the differential equations. You do not need to make a new variable, just call `convert` with the right arguments. -""" - -# ╔═╡ 2f992357-4608-40e6-9954-8306f31e9768 -# missing # Uncomment and complete the instruction - -# ╔═╡ f87451c3-1707-4f13-a373-d8949ce4ca08 -md""" -Simulate the system over a time interval of $0$ to $10$ hours with default parameter values, and initial glucose and insuline concentrations of $5.0$ and $0.0$, repectively. Use `Tsit5()` and `saveat=0.01` to solve. -""" - -# ╔═╡ 93457418-a032-48b6-9c63-94ce49645c68 -# oprob_robust = missing; # Uncomment and complete the instruction - -# ╔═╡ cac1644a-7604-4716-85c8-63b657ff95b2 -# sol_robust = missing; # Uncomment and complete the instruction - -# ╔═╡ 06645013-b63b-49b4-822b-f08059f3a394 -md""" -Plot the results. Use thereby `ylim=(0.0, 5.5)`. -""" - -# ╔═╡ de7919d3-04a8-4b4f-ad76-2f835275fc6b -# missing # Uncomment and complete the instruction - -# ╔═╡ 318e83fc-340b-4fd1-9e72-dbfdb891ff5f -md""" -Implement a function that computes/shows the steady state glucose concentration after $100$ hours. Set initial values for $G$ and $I$ to $0.0$ and set $m$ to $0.5$. - -- Tip: copy the *body* of the former function `glucose_steady_state` and adapt. -""" - -# ╔═╡ bcf597ef-c57f-402d-8209-511a7fc16437 -# Uncomment and complete the instruction -# function glucose_steady_state_robust(s) -# oprob = missing -# sol = missing -# return missing -# end - -# ╔═╡ 5c2d0d0c-7bce-4696-9807-e793b6f8033e -md""" -Create a new slider object with a range between $0.1$ and $5.0$ and step size $0.1$, and bind it to the new variable `s_robust`. - -- Tip: copy the previous slider and adapt. -""" - -# ╔═╡ 8ef6d8a4-be72-49dc-be44-0e4eb06336be -# missing # Uncomment and complete the instruction - -# ╔═╡ fd56b95e-59a3-46e7-8216-e50181c22a58 -md""" -Call the function `glucose_steady_state_robust` with `s_robust` as argument and observe the new steady state glucose concentration for different insulin sensitivity values. -""" - -# ╔═╡ daf82017-c427-480c-94d2-938e675004f0 -# missing # Uncomment and complete the instruction - -# ╔═╡ b95899e9-3af9-4fc5-820b-f72164beb5db -md""" -Plot of the new steady state value of $G$ as a function of $s$ (in the range $[0.1, 5.0]$). You might need to use `ylim=(0.98, 1.02)`. -""" - -# ╔═╡ 4adeac81-8724-4a2c-a004-17be8f92f95e -# missing # Uncomment and complete the instruction - -# ╔═╡ 041f6717-2709-47cb-ad54-8ef6b1a61fcc -md""" -Use automatic differentiation with `ForwardDiff.derivative(..., ...)` to compute the normalized (total relative) sensitivity index. Is this new system sensitive to the insuline sensitivity $s$? - -- Answer: missing -""" - -# ╔═╡ 2180719e-ad6b-4b63-a835-3c30b811d35a -# sens_G_rel_robust(s) = ... # Uncomment and complete the instruction - -# ╔═╡ d4af98b9-40dc-486b-bbc0-9538a193cf19 -md""" -Display the new normalized (total relative) sensitivity for the current $s$ value (cf. slider). -""" - -# ╔═╡ 6ca12c8b-a535-43ca-9d6a-86e324eac51e -sens_G_rel_robust(s_robust) - -# ╔═╡ Cell order: -# ╠═52b28a4c-b0bb-11ef-2841-17ecfb596676 -# ╠═7bc363b0-9415-4954-807f-81a308bde531 -# ╟─bfcc4b4e-073e-401e-851c-d01ef828028a -# ╟─a5789d9d-a690-4c6b-8b8d-fbfe63f659d0 -# ╠═e93bc1a5-e0b1-425c-b069-d5ce80756e60 -# ╟─be33a8a3-a3f5-448d-9390-16f7c4ca099d -# ╠═2ac43184-a393-4d39-9c3b-c299c3371b09 -# ╟─75a5b5d9-f8d7-4728-83dc-03dde502dcfb -# ╠═f62c2b4a-a9e8-472b-b07b-44072da08ee8 -# ╟─882e106f-d3a5-4ccb-ae65-cfb5e4e5de57 -# ╠═99ccbd1b-b9e4-4afc-8d9a-8c1055bcce0f -# ╟─3c1465d2-0886-4b74-baee-55c7dad41f9a -# ╠═5bac69d1-0bd4-41bd-b15d-5ca6292a31e4 -# ╠═6ccd5ecc-5579-4ce5-9425-d1bbae0bc5ba -# ╠═afc40964-0a97-43a6-ab27-5fe96ecd319b -# ╠═f050b32e-1a14-4d5e-9fb3-5dbc4dfbf79c -# ╟─cfbbfe48-a2c3-4691-9fac-b0a18f99222c -# ╠═7e35b56b-44a0-46df-a812-f5fb437b672e -# ╠═68bdcedb-42b7-4fa8-8252-c2fbaa6e0187 -# ╟─8c7784df-1f6d-41e3-8548-7311710e599e -# ╠═0133ab41-2c8c-4147-9594-1ad238833a9e -# ╟─479e0f11-e18e-4523-8d24-b2026acdeaef -# ╠═824677b4-6533-453f-909f-bf04fe4bf5d9 -# ╟─cef7f30d-43ef-4f2c-8bf3-35c140d237ce -# ╠═9d3ba8bd-e9c3-4bb6-957d-c7f8211a2cda -# ╟─3f5e2195-da93-40fb-88ec-9e85a7bb6c24 -# ╠═3dae761f-5fb6-4f1c-a440-d2c21a6edb17 -# ╠═d48a6bfc-b533-41ca-a0de-df0af5738536 -# ╠═087015da-d85d-4ebb-861a-b89e93a4055c -# ╟─314aa77c-5906-4772-94c5-f0b7cf648de0 -# ╠═3b56f88c-0ca8-4f33-8516-11e639cb6a1a -# ╟─aab02fef-6d84-4f53-aff5-6f94bf2bd460 -# ╠═8a5ce760-55bb-4610-9226-32c3d8376748 -# ╠═a2fb8c21-c594-44f3-92df-99e6d3afeba1 -# ╟─d95198d9-fb96-4389-bf66-f039a673d1a2 -# ╠═8bb8feaa-63e7-4777-ab79-92d98f84142b -# ╟─d7d91377-2b6b-4daa-aeb6-d0b20c20cd34 -# ╠═6890e3ec-0875-423e-89c1-988411c869e3 -# ╟─cfcd040b-f762-4cf4-8b9c-1d77ac2837cd -# ╠═fb439f9d-ee88-43cd-b91b-44ed8013b27d -# ╟─83595165-3d71-43b3-93c0-4ba880e0b86e -# ╠═095e359a-2ec4-4637-8477-93510bef23ed -# ╟─31aaced7-1fe4-4117-80f3-0fa70035822a -# ╟─53addc28-7c87-4c79-ad8f-4a41a3ca5f19 -# ╠═69e21e9b-7d0e-44e3-8b33-ee7102dadadf -# ╠═b4073e81-6db8-4be6-a2d1-afa01640dbcf -# ╟─4d58a832-9157-42fe-b578-c3fc29f01ea8 -# ╠═7cf11e6f-7ef4-478f-8cc7-eae93374cc58 -# ╟─581b82c2-0957-4d20-a664-6ff80b5c40d9 -# ╠═5eda8c4a-2baa-4987-b408-dbe7383abe95 -# ╟─1c22a730-776e-4508-bdee-d8861c8949e4 -# ╠═34788b1d-1530-4b88-8690-03fe9af52a8b -# ╟─f616da2f-b51e-4328-bd3c-47e6d7377cbb -# ╟─9be6f5b3-d280-4222-ad64-4b4a7b682310 -# ╠═97636a73-4732-4377-b760-c36cef13904b -# ╟─e0318ae7-d27d-4d73-bf44-632d813e8aa5 -# ╠═a67d70ee-c8b5-4f22-926c-f48c5dcf9815 -# ╟─407437a0-36b1-452f-910c-f244aeeedc4c -# ╠═1bb5834a-6a4e-40ec-82c1-f2c505ecfeb8 -# ╟─66105045-a8f6-4513-a255-a9e1b0c78d37 -# ╠═a3156073-07bd-4f13-be5e-d19483fbad28 -# ╟─0ae57436-0b8c-41ce-9d9d-8c48ed59b820 -# ╟─92d689a4-9f2b-4a0f-81a5-6194af06940c -# ╠═87d9c826-56af-409a-a884-d784cfa16a64 -# ╟─2f784fff-90fc-4273-8b9a-db4593849652 -# ╠═4d95e77b-fab0-454b-8319-1fe83e12f9e9 -# ╟─ea46f5f9-47a8-4641-a7e3-08a1a29cd04f -# ╟─870e9229-374a-40b8-97f7-1a84d1a857e9 -# ╟─a8814cfd-8fd0-4628-b1ae-80211386a402 -# ╠═246a06ba-ef48-47ba-aef0-51f07ceb96e4 -# ╟─69965b61-043b-44a0-998b-f5f34deefdd2 -# ╠═2f992357-4608-40e6-9954-8306f31e9768 -# ╟─f87451c3-1707-4f13-a373-d8949ce4ca08 -# ╠═93457418-a032-48b6-9c63-94ce49645c68 -# ╠═cac1644a-7604-4716-85c8-63b657ff95b2 -# ╟─06645013-b63b-49b4-822b-f08059f3a394 -# ╠═de7919d3-04a8-4b4f-ad76-2f835275fc6b -# ╟─318e83fc-340b-4fd1-9e72-dbfdb891ff5f -# ╠═bcf597ef-c57f-402d-8209-511a7fc16437 -# ╟─5c2d0d0c-7bce-4696-9807-e793b6f8033e -# ╠═8ef6d8a4-be72-49dc-be44-0e4eb06336be -# ╟─fd56b95e-59a3-46e7-8216-e50181c22a58 -# ╠═daf82017-c427-480c-94d2-938e675004f0 -# ╟─b95899e9-3af9-4fc5-820b-f72164beb5db -# ╠═4adeac81-8724-4a2c-a004-17be8f92f95e -# ╟─041f6717-2709-47cb-ad54-8ef6b1a61fcc -# ╠═2180719e-ad6b-4b63-a835-3c30b811d35a -# ╟─d4af98b9-40dc-486b-bbc0-9538a193cf19 -# ╠═6ca12c8b-a535-43ca-9d6a-86e324eac51e diff --git a/src/exercises/sens_intro.jl b/src/exercises/sens_intro.jl deleted file mode 100755 index 83382742..00000000 --- a/src/exercises/sens_intro.jl +++ /dev/null @@ -1,811 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "28" -#> title = "6. Sensitivity intro" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Sensitivity intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ e1e7bc8e-7264-4cbc-98d2-aa73679fa2df -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 5f4fea06-0632-11ef-102e-21f5606d2056 -using Markdown - -# ╔═╡ 489b5399-fe4c-481a-834f-0101bbe28cea -using InteractiveUtils - -# ╔═╡ ab4a1911-461e-4878-9258-931fc2f1ea06 -using PlutoUI; TableOfContents() - -# ╔═╡ 22489bd4-ab64-4bf9-ad03-5372ea273935 -using Catalyst - -# ╔═╡ cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 65885bbe-7b73-4efd-a8e7-984a34b80548 -using ForwardDiff - -# ╔═╡ 427b509f-d08e-4d93-99ba-a79f9c244b28 -md""" -# Introduction to sensitivity analysis -""" - -# ╔═╡ 49e7085c-3691-4182-9630-68dc9371ad18 -md""" -## Goal of this practicum -""" - -# ╔═╡ c41894dd-0f6c-483f-b19f-dbf8148f776f -md""" -Sensitivity functions indicate how sensitive the model output is to a change in parameter values. When a model output is very sensitive to a certain parameter, a small change in the value of this parameter will have a large influence on the value of the model output. Sensitivity functions thus provide important information about the model and are implicitly used to estimate parameters and explicitly in the context of optimal experimental design. -""" - -# ╔═╡ 7b58f2f9-cb93-4a20-bf90-62a9006b57d6 -md""" -The sensitivity function that measures how sensitive output $y_i$ is to changes in parameter $\theta_j$ is given by the partial derivative - -$$S_{ij} = \cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j}\tag{1}$$ -""" - -# ╔═╡ abd70f04-1bf8-454f-869a-0d8082d68453 -md""" -Expression $(1)$ is sometimes referred to as the *absolute sensitivity*. Try to understand why the above expression $(1)$ does indeed give us the information we were promised in the first paragraph. - -!!! question - How will we be able to see from the value determined by the above expression $(1)$ whether or not output $\hat{y}_i$ is sensitive to a change in $\theta_j$? What is then the meaning of a negative sensitivity? -""" - -# ╔═╡ e76aedec-69f2-4301-b015-6960e4503c42 -md"Answer: missing" - -# ╔═╡ a6a97279-0b64-45c3-8312-c22b1a8425d0 -md""" -Sometimes expression $(1)$ can be evaluated analytically. Usually, however, we will have to approximate the partial derivative numerically. Expression $(1)$ can be made more specific: - -$$\cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j} \approx \cfrac{\hat{y}_i(\theta_j+\Delta\theta_j)-\hat{y}_i(\theta_j)}{\Delta\theta_j}$$ -""" - -# ╔═╡ 25d3ef62-382c-455c-92bb-fadfc650c5a6 -md""" -Thus, to calculate the sensitivity function numerically, the model is evaluated for the parameter values $\theta_j$ and $\theta_j+\Delta\theta_j$ and the difference between these evaluations is taken. -""" - -# ╔═╡ 2a544bef-d7fe-4300-bc59-69e6f7429304 -md""" -Since quantity $(1)$ is dependent on the units, a *normalized* variant is often used: - -$$s_{ij} = \cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j} \cdot \cfrac{\theta_j}{\hat{y}_i} \tag{2}$$ - -The interpretation of $(2)$ is how much the output changes per cent if the parameter is increased by one per cent. It assumes positive (nonzero) model outputs and parameters, which is often the case for biochemical models. - -Using the normalized variant allows you to compare all possible sensitivity functions with each other. -""" - -# ╔═╡ 7107a3a9-15ef-488e-8709-52a6444d0e1c -md""" -We now calculate and interpret sensitivity functions for some given models. To illustrate these concepts, we first consider three simple models describing the growth of grass. -""" - -# ╔═╡ 04e95855-7c4a-4d2c-b836-c5dde291adad -md""" -## Grass growth models -""" - -# ╔═╡ 4673bfdf-8f4a-42bd-a026-21a66d800f2b -md""" -In this notebook, three different models will be used, each modelling the yield of grass in a grassland: - -- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ -- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ -- Gompertz growth model: $\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$ - -with output $W$ the grass yield, and $W_f$, $\mu$ and $D$ parameters. The table below shows some typical values for the parameters: - -| | $\mu$ | $W_f$ | $D$ | -|:----------- |:----------:|:-----------:|:------------:| -| Logistic | 0.07 | 10.0 | | -| Exponential | 0.02 | 10.0 | | -| Gompertz | 0.09 | | 0.04 | - -We will use an initial condition of $W_0 = 2.0$ for each and a simulation time of $100$ days. -""" - -# ╔═╡ 8d55bc42-1f8f-4bc1-b9fa-e8152fc125ce -md""" -We will illustrate how to compute the local normalized sensitivity functions for the logistic model. The same will be left as exercises below for the exponential and Gompertz models. - -**Important:** -- We will use consequently `_log`, `_exp` and `_gom` appended to relevant variables names in order to indicate their model origin **and** to prevent cell-disabling that occurs when using the same variables names in these Notebooks. -""" - -# ╔═╡ 676119ce-f4fe-41f9-8121-b2c21f0dd28c -md""" -### Modelling logistic growth -$$\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$$\ -$W_0$ = 2.0, $\mu$ = 0.07 and $W_f$ = 10.0\ -We will start by modelling our system and simulating using the aforementioned parameters values, initial condition and timespan in a way that we are familiar with. -""" - -# ╔═╡ 5492210b-6ee6-4baf-b37e-d21358cdeb60 -md""" -Implementation of the system: -""" - -# ╔═╡ 18eece66-46fa-4458-aaed-f4c8fa002c20 -growth_log = @reaction_network begin - @species W(t)=2.0 # default initial condition - @parameters μ=0.07 Wf=10.0 # default parameter values - μ*(1-W/Wf), W --> 2W -end - -# ╔═╡ 8bb0b24b-6cce-49cd-a625-5f375b92d9b7 -md""" -Convert the *reaction model* to check that we work with the correct differential equation: -""" - -# ╔═╡ ba181db8-d176-4b83-9168-d5939ffe9661 -osys_log = convert(ODESystem, growth_log) - -# ╔═╡ cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 -md""" -Setting initial conditions, timespan and parameter values: -""" - -# ╔═╡ cbb2ca49-b019-495b-9310-83fcc00cad26 -u0_log = [:W => 2.0] - -# ╔═╡ 0b3d35bb-c5b0-44b7-94b3-06fa571d339e -tspan = (0.0, 100.0) # this will be the same for the three models - -# ╔═╡ be565a3c-31b6-4df1-b73b-08f308a8c09b -md""" -For the sake of clarity, we will use the variables `μ_log` and `Wf_log` to store the parameter values. -""" - -# ╔═╡ f62806d1-77e1-470b-9711-33a924c788cc -μ_log = 0.07 - -# ╔═╡ 546ed163-26a7-4235-982f-7568ed609488 -Wf_log = 10.0 - -# ╔═╡ 0da53fa2-5a42-46e6-8bd8-45d6aa903d46 -params_log = [:μ => μ_log, :Wf => Wf_log] - -# ╔═╡ b1298f40-4696-49d0-ac94-896e0cdbc996 -md""" -Creating and solving the ODEProblem and plotting results: -""" - -# ╔═╡ 270647d2-1371-4272-8bc1-3a6ad77bc716 -oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log); -# Also possible here if initial conditions and parameter values are defined in the catalyst model: -# oprob_log = ODEProblem(growth_mod_log, [], tspan, []) - -# ╔═╡ ac235d86-1d93-4944-aa89-1b4fd38f0e6e -osol_log = solve(oprob_log, Tsit5(), saveat=0.5) - -# ╔═╡ 3e13efa1-9bc6-456f-8e62-ecd3165e2a65 -plot(osol_log) - -# ╔═╡ 79fe5411-fb78-490e-a9de-1868d1261aa6 -md""" -### Local Sensitivity Analysis (LSA) -""" - -# ╔═╡ c57938c8-566a-4502-a2e1-69dc77291500 -md""" -In order to compute the local sensitivity functions, we will need to load the `ForwardDiff` package: -""" - -# ╔═╡ 4aa73da9-a394-4ca3-a839-d076eb3c3d7f -md""" -We need to write a solution function with as argument a vector of the parameters (those values for which we want to calculate the sensitivity), and that returns the solution (time vector and outputs). -""" - -# ╔═╡ bc201b61-0f58-49d2-a20b-4f18c42fcc96 -function growth_sim_log(params) - μ, Wf = params - u0_log = [:W => 2.0] - tspan = (0.0, 100.0) - oprob_log = ODEProblem(growth_log, u0_log, tspan, [:μ=>μ, :Wf=>Wf]); - osol_log = solve(oprob_log, Tsit5(), saveat=0.5) - return osol_log -end - -# ╔═╡ 11f15a0e-2958-4075-928c-5a24bcc00c69 -md""" -Next, we will need to make a function that returns a single output based on the solution function. -""" - -# ╔═╡ d0f3197f-3094-4f6e-b33c-e5b74e0947f6 -growth_sim_W_log(params) = growth_sim_log(params)[:W] - -# ╔═╡ ba4eaa90-b97a-4b71-b3e1-968340c6def5 -md""" -Now make a time vector that is the same as the time vector from the solution. -""" - -# ╔═╡ 71f897a6-1e91-412b-ad58-c5f1e7cd1adb -t_vals_log = 0:0.5:100.0 -# Alternatives: -# t_vals_log = tspan[1]:0.5:tspan[2] - -# ╔═╡ f9f30a2b-04bd-4563-bd3d-8ec7129ca2e7 -t_log = growth_sim_log([μ_log, Wf_log])[:t] - -# ╔═╡ b3b97b57-a4f7-4cb6-80c8-6ad893ade75d -md""" -Compute the single output with the given parameter values. This will give us exactly the same output that we simulated before in a familiar way. -""" - -# ╔═╡ 99864b21-3db0-4d60-a84e-8e96db4de4ae -W_log = growth_sim_W_log([μ_log, Wf_log]) - -# ╔═╡ 344eb3a7-8614-4349-a536-62d9acef6bda -md""" -Use now the function `ForwardDiff.jacobian` to compute the sensitivities. This function takes two arguments: the solution function and a vector with the parameter values. -""" - -# ╔═╡ 670ccfa4-24fa-4167-a46a-2a48dc19538b -sens_W_log = ForwardDiff.jacobian(growth_sim_W_log, [μ_log, Wf_log]) - -# ╔═╡ c262d3ed-9f62-45ad-a7ee-ec78ab16f35c -md""" -To get the (absolute) sensitivities of $W$ wrt. $\mu$, and of $W$ on $W_f$, you need to use indexing with `sens_W_log`: -- `sens_W_log[:,1]` gives the (absolute) sensitivity of $W$ wrt. $\mu$. -- `sens_W_log[:,2]` gives the (absolute) sensitivity of $W$ wrt. $W_f$. -""" - -# ╔═╡ 6600d28e-2522-4069-a5e0-643be43f6117 -sens_W_on_μ_log = sens_W_log[:,1] # sensitivity of W on μ - -# ╔═╡ d9e3a4ac-c138-4b93-a19c-737166a0f0ea -sens_W_on_Wf_log = sens_W_log[:,2] # sensitivity of W on Wf - -# ╔═╡ f07d8205-e172-45fa-b955-792bd95f3023 -md""" -We now calculate the normalized sensitivities. For that we need to multiply by the parameter value and divide by the ouput. Beware that all element wise operations need a dot in front of the operator, e.g. as in `.*` and `./`. -""" - -# ╔═╡ 1c142797-ada6-4d77-a22f-b981ffd38956 -sens_W_on_μ_rel_log = sens_W_on_μ_log .* μ_log ./ W_log - -# ╔═╡ cb397e0b-56f9-420f-a86e-bfab35286b44 -sens_W_on_Wf_rel_log = sens_W_on_Wf_log .* Wf_log ./ W_log - -# ╔═╡ 9e6946a4-f207-4bfb-9e19-aa7b77c2a05b -md""" -We are now ready to plot the two normalized sensitivity functions. We provide the time vector (first argument) and a vector of the two sensitivity functions (second argument). Additionally, you can provide a title, (legend) labels and a x- and/or y-label. -""" - -# ╔═╡ f58ce914-f366-4087-a7d1-8cfe69ac623b -plot(t_vals_log, [sens_W_on_μ_rel_log, sens_W_on_Wf_rel_log], title="Normalized sensitivities", label=["W on μ" "W on Wf"], xlabel="Time (day)") - -# ╔═╡ 05972c7f-f64f-4865-b0a7-f33029d0a6fa -md""" -Notice that in the `label` option there is no comma separating the labels. -""" - -# ╔═╡ 04a1d1ad-53a9-4146-be09-65aa422e3730 -md""" -Conclusions: -- From the sensitivity plot of $W$ wrt. $\mu$ it can be seen that $W$ is most sensitive to $\mu$ in the time region $[5, 30]$. The latter corresponds to the region where the yield rate is largest (i.e., when the growth is largest). This makes sense because when looking at the differential equation, $\mu$ is approximately the growth rate for relatively small $W$ values. -- From the sensitivity plot of $W$ wrt. $W_f$ it can be seen that $W$ is most sensitive to $W_f$ in the region where time values are large (cf. operating point). The latter corresponds to the region where the yield rate stagnates (i.e., when the yield reaches a steady value). This makes sense because when looking at the differential equation, $W_f$ is the steady state value. -""" - -# ╔═╡ e176e6ce-33da-4f3b-860e-4db99a931079 -md""" -!!! question - What name do we often give to the steady state value of $W$ or parameter $W_f$? Do these (local) sensitivity results match your expectations in terms of the different impacts of the parameters? -""" - -# ╔═╡ 3a1bf6da-b2e9-4c86-a83d-abe4de5a0483 -md""" -- Answer: missing -""" - -# ╔═╡ 79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 -md""" -## Exercises -""" - -# ╔═╡ fa2270d0-2548-409c-a23f-4369d8bce8ec -md""" -### Exercise 1 - Sensitivity analysis of the exponential growth model -""" - -# ╔═╡ 794b93c9-0a0a-4e77-b13c-0e2c06a9a0ec -md""" -$$\cfrac{dW}{dt} = \mu \left( W_f - W \right)$$\ -$W_0$ = 2.0, $\mu$ = 0.02 and $W_f$ = 10.0 -""" - -# ╔═╡ dca50b37-b06c-4efe-881e-cf966ebc8fd7 -md""" -Create a *reaction network object* for the exponential growth model. Name it `growth_exp`. -""" - -# ╔═╡ 2836f231-3b90-4829-9012-3ed9a09239ff -# Uncomment and complete the instruction -# growth_exp = @reaction_network begin -# @species missing -# @parameters missing -# missing -# end - -# ╔═╡ e8db84be-31a0-415d-a194-064c4c87a293 -md""" -Convert the system to a symbolic differential equation model (name it: `osys_exp`) and verify, by analyzing the differential equation, that your model has been correctly implemented. -""" - -# ╔═╡ 78d175b4-a9b2-49b5-bbc8-eb348799985b -# osys_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 63952c54-3304-4500-9536-b375c5c8f280 -md""" -Initialize a vector `u0_exp` with the initial condition: -""" - -# ╔═╡ 7dc013f7-7b35-42e4-aa77-0dca3755f389 -# u0_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 86785c55-b567-449c-ae76-bc15a16223bc -md""" -We will use the same timespan as before, so no need to redefine it. -""" - -# ╔═╡ 05e96232-ce95-452b-98e4-e79817d45ae2 -md""" -For the sake of clarity, we will use the variables `μ_exp` and `Wf_exp` to store the parameter values. -""" - -# ╔═╡ 9a705b3f-3c45-4896-9b89-e9c37d47fd89 -# μ_exp = missing - -# ╔═╡ 55a13ebc-09e7-433c-83a8-9b6805e2fbec -# Wf_exp = missing - -# ╔═╡ f1293675-b4d0-4827-8714-65a593ecc00e -md""" -Initialize a vector `params_exp` with the parameter values: -""" - -# ╔═╡ 7005d88f-9e28-47fe-9a32-dec023903dc3 -# params_exp = missing # Uncomment and complete the instruction - -# ╔═╡ c8853554-26c5-491c-a536-4c07e8c6a986 -md""" -Create the ODE problem and store it in `oprob_exp`: -""" - -# ╔═╡ 025c1154-b2ae-4e1c-af1b-277b24d648a4 -# oprob_exp = missing; # Uncomment and complete the instruction - -# ╔═╡ f58825d2-55fc-43f1-b164-9555bf9f5b84 -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol_exp`: -""" - -# ╔═╡ 8044dd5f-9996-4062-a54a-8fb0eee30b46 -# osol_exp = missing # Uncomment and complete the instruction - -# ╔═╡ a1752ad4-2c11-416c-8321-9da058f9aaea -md""" -Plot the result: -""" - -# ╔═╡ 0f58fd3d-1495-46c7-9d92-5539395cf12e -# missing # Uncomment and complete the instruction - -# ╔═╡ 6321774c-5bbe-42a7-bdad-0168c891b5ce -md""" -Write a solution function with as argument a vector of the parameters (the values for which we want the sensitivity), and that returns the outputs. -""" - -# ╔═╡ eb1e5f97-3e09-43f0-b6ce-cc6cbc42ec2f -# Uncomment and complete the instruction -# function growth_sim_exp(params) - # μ, Wf = missing - # u0_exp = missing - # tspan = missing - # oprob_exp = missing; - # osol_exp = missing - # return missing -# end - -# ╔═╡ b31b4d24-ae30-4cff-b6b7-348200feaa5d -md""" -Make a function based on the solution function that returns a single output. -""" - -# ╔═╡ f2fa716f-d7b5-4c95-a8c6-6b16fbfe1499 -# growth_sim_W_exp(params) = missing # Uncomment and complete the instruction - -# ╔═╡ 0b413b98-a800-44e9-a917-ba61467bd613 -md""" -Make the time vector. -""" - -# ╔═╡ 6be6fea7-561b-4eec-b249-fa4522a5b039 -# t_vals = missing # Uncomment and complete the instruction - -# ╔═╡ 98070d41-e8c5-49e6-9eea-04c718cbff65 -md""" -Compute the output for the given parameter values. -""" - -# ╔═╡ cef751aa-f9c8-46da-9436-9cc2e5d7515c -# W_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 9e7bb7ec-8fe3-422e-920f-13c2ef055feb -md""" -Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouputt. -""" - -# ╔═╡ b13b5e77-ca60-465b-9f63-be9e5da0482f -# sens_W_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 735f5191-259e-440f-a379-20a2a70ec72c -md""" -Extract the (absolute) sensitivities of the outputs on the different parameters. -""" - -# ╔═╡ 689556c9-a690-4e9e-9052-7ac66e999d4d -# sens_W_on_μ_exp = missing # Uncomment and complete the instruction - -# ╔═╡ b85f0d09-a102-435b-a898-149ba4b29caa -# sens_W_on_Wf_exp = missing # Uncomment and complete the instruction - -# ╔═╡ d067f814-1dd5-4e59-8cfd-3bc0b2d97612 -md""" -Compute the normalized sensitivities. -""" - -# ╔═╡ e01657f3-33ac-4eb8-b39e-e2441a165a8d -# sens_W_on_μ_rel_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 7231e54e-b293-48fc-b1f0-273f45f51539 -# sens_W_on_Wf_rel_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 0524a976-9d4d-4ef9-b088-bdd9102415e6 -md""" -Plot both normalized sensitivity functions (with appropriate title and labels): -""" - -# ╔═╡ 33031b53-6f2d-4256-a0aa-0ba97beaa7af -# missing # Uncomment and complete the instruction - -# ╔═╡ 22b58c97-1aa2-49c4-8a0a-488c63014a90 -md" -Draw your conclusions: - -- missing -- missing -" - -# ╔═╡ 41abf8c4-e67e-4f66-a38a-7204a878d98d -md""" -### Exercise 2 - Sensitivity analysis of the Gompertz growth model -""" - -# ╔═╡ e34fed6f-6b58-4642-84eb-167467881bb2 -md""" -$$\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$$\ -$W_0$ = 2.0, $\mu$ = 0.09 and $D$ = 0.04. -""" - -# ╔═╡ f311e943-c96b-4af3-8757-c662fd302a88 -md""" -Create a *reaction network object* for the Gompertz growth model. Name it `growth_gom`. -""" - -# ╔═╡ 1ae76036-42ef-46e9-88bf-d66d4267addc -# Uncomment and complete the instruction -# growth_gom = @reaction_network begin -# @species missing -# @parameters missing -# missing -# end - -# ╔═╡ e190ceca-30b1-49e2-baf5-ceb62929f4c0 -md""" -Convert the system to a symbolic differential equation model (name it: `osys_gom`) and verify, by analyzing the differential equation, that your model has been correctly implemented. -""" - -# ╔═╡ acaf5b91-accc-4dfa-9371-6495a47c8736 -# osys_gom = missing # Uncomment and complete the instruction - -# ╔═╡ ed1150f1-9c75-4867-b7f5-535d605810f4 -md""" -Initialize a vector `u0_gom` with the initial condition: -""" - -# ╔═╡ d35ef155-61aa-4b19-872f-d6e621f96572 -# u0_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 3f546d58-15ff-442d-a4e8-7c0783ed2fe0 -md""" -We will use the same timespan as before, so no need to redefine it. -""" - -# ╔═╡ b778f786-87bc-4103-a792-2ae5cf004d50 -md""" -For the sake of clarity, we will use the variables `μ_gom` and `D_gom` to store the parameter values. -""" - -# ╔═╡ edd76a4e-e541-4a83-923f-6f593d8fadad -# μ_gom = missing - -# ╔═╡ 05f1ae6c-1229-4924-9c41-9554d6ca4261 -# D_gom = missing - -# ╔═╡ 70df05e7-4527-4087-a762-942a33e89f74 -md""" -Initialize a vector `params_gom` with the parameter values: -""" - -# ╔═╡ bf9291d5-6fc5-4359-874e-167237a22147 -# params_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 9e3158c5-d4b2-456c-b920-8431e2047770 -md""" -Create the ODE problem and store it in `oprob_gom`: -""" - -# ╔═╡ 3aab9073-cf4d-4ee5-9e8a-e3db586d9f68 -# oprob_gom = missing; # Uncomment and complete the instruction - -# ╔═╡ 30c35371-d106-4673-8569-d07b39edbdfe -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol_gom`: -""" - -# ╔═╡ c01fe052-3701-4e22-9a64-f681d52a445a -# osol_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 4f959d21-954b-4116-a498-bafc1aada47a -md""" -Plot the result: -""" - -# ╔═╡ 5f427df2-1b7e-46c7-adb9-66b05deac6e8 -# missing # Uncomment and complete the instruction - -# ╔═╡ 958854fc-8e23-4364-88c5-1ec060b86a16 -md""" -Write a solution function with as argument a vector of the parameters (the values for which we want the sensitivity), and that returns the outputs. -""" - -# ╔═╡ be21e269-16c4-42b0-8930-2d009bd91161 -# Uncomment and complete the instruction -# function growth_sim_gom(params) - # μ, D = missing - # u0_gom = missing - # tspan = missing - # oprob_gom = missing; - # osol_gom = missing - # return missing -# end - -# ╔═╡ e57f5916-b753-429b-8434-8a85a67bd1fb -md""" -Make a function based on the solution function that returns a single output. -""" - -# ╔═╡ 69f96b57-7c8d-4e64-9e6a-9804d001b0b1 -# growth_sim_W_gom(params) = missing # Uncomment and complete the instruction - -# ╔═╡ f7f6c56b-4910-4641-a462-eacfa4b4d034 -md""" -Make the time vector. -""" - -# ╔═╡ 84c1bb38-23c3-4f70-be67-7da2300a737b -# t_vals_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 09eeab39-79a1-420a-8298-72b7002ec168 -md""" -Compute the output for the given parameter values. -""" - -# ╔═╡ 9e15601f-4ad2-41be-8168-db30548a1c3b -# W_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 2b731e4a-d6eb-48f2-91f0-27e916d85683 -md""" -Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouput(s). -""" - -# ╔═╡ 68ad7e05-7f79-4e8d-9031-fefb5f5c0897 -# sens_W_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 362a8e24-8fac-4978-bc0e-13596a05d39e -md""" -Extract the (absolute) sensitivities of the outputs on the different parameters. -""" - -# ╔═╡ e9114d64-fe3e-420d-9fde-4b0fcbbc8527 -# sens_W_on_μ_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 7c9e1112-0a63-417f-a65a-9c2bdfa8bc47 -# sens_W_on_D_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 94e66042-b352-4bf2-a24b-b15789e10fe3 -md""" -Compute the normalized sensitivities. -""" - -# ╔═╡ 1711346a-c161-415a-96f9-1235d786a584 -# sens_W_on_μ_rel_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 7a87bfbb-a8c0-4071-8f86-66513ae40968 -# sens_W_on_D_rel_gom = missing # Uncomment and complete the instruction - -# ╔═╡ 7593a4bf-ad0f-4fd3-aa3c-8b6a5073cb02 -md" -Plot both sensitivity functions (with appropriate title and labels): -" - -# ╔═╡ ad70a1d5-9b68-4cd6-9259-a3c25cad706b -# missing # Uncomment and complete the instruction - -# ╔═╡ 12f64333-36d0-4e2a-9061-e3dcc7a4ae96 -md" -Draw your conclusions: - -- missing -- missing -" - -# ╔═╡ Cell order: -# ╠═5f4fea06-0632-11ef-102e-21f5606d2056 -# ╠═489b5399-fe4c-481a-834f-0101bbe28cea -# ╠═e1e7bc8e-7264-4cbc-98d2-aa73679fa2df -# ╠═ab4a1911-461e-4878-9258-931fc2f1ea06 -# ╟─427b509f-d08e-4d93-99ba-a79f9c244b28 -# ╟─49e7085c-3691-4182-9630-68dc9371ad18 -# ╟─c41894dd-0f6c-483f-b19f-dbf8148f776f -# ╟─7b58f2f9-cb93-4a20-bf90-62a9006b57d6 -# ╟─abd70f04-1bf8-454f-869a-0d8082d68453 -# ╠═e76aedec-69f2-4301-b015-6960e4503c42 -# ╟─a6a97279-0b64-45c3-8312-c22b1a8425d0 -# ╟─25d3ef62-382c-455c-92bb-fadfc650c5a6 -# ╟─2a544bef-d7fe-4300-bc59-69e6f7429304 -# ╟─7107a3a9-15ef-488e-8709-52a6444d0e1c -# ╟─04e95855-7c4a-4d2c-b836-c5dde291adad -# ╟─4673bfdf-8f4a-42bd-a026-21a66d800f2b -# ╟─8d55bc42-1f8f-4bc1-b9fa-e8152fc125ce -# ╠═22489bd4-ab64-4bf9-ad03-5372ea273935 -# ╠═cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 -# ╟─676119ce-f4fe-41f9-8121-b2c21f0dd28c -# ╟─5492210b-6ee6-4baf-b37e-d21358cdeb60 -# ╠═18eece66-46fa-4458-aaed-f4c8fa002c20 -# ╟─8bb0b24b-6cce-49cd-a625-5f375b92d9b7 -# ╠═ba181db8-d176-4b83-9168-d5939ffe9661 -# ╟─cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 -# ╠═cbb2ca49-b019-495b-9310-83fcc00cad26 -# ╠═0b3d35bb-c5b0-44b7-94b3-06fa571d339e -# ╟─be565a3c-31b6-4df1-b73b-08f308a8c09b -# ╠═f62806d1-77e1-470b-9711-33a924c788cc -# ╠═546ed163-26a7-4235-982f-7568ed609488 -# ╠═0da53fa2-5a42-46e6-8bd8-45d6aa903d46 -# ╟─b1298f40-4696-49d0-ac94-896e0cdbc996 -# ╠═270647d2-1371-4272-8bc1-3a6ad77bc716 -# ╠═ac235d86-1d93-4944-aa89-1b4fd38f0e6e -# ╠═3e13efa1-9bc6-456f-8e62-ecd3165e2a65 -# ╟─79fe5411-fb78-490e-a9de-1868d1261aa6 -# ╟─c57938c8-566a-4502-a2e1-69dc77291500 -# ╠═65885bbe-7b73-4efd-a8e7-984a34b80548 -# ╟─4aa73da9-a394-4ca3-a839-d076eb3c3d7f -# ╠═bc201b61-0f58-49d2-a20b-4f18c42fcc96 -# ╟─11f15a0e-2958-4075-928c-5a24bcc00c69 -# ╠═d0f3197f-3094-4f6e-b33c-e5b74e0947f6 -# ╟─ba4eaa90-b97a-4b71-b3e1-968340c6def5 -# ╠═71f897a6-1e91-412b-ad58-c5f1e7cd1adb -# ╠═f9f30a2b-04bd-4563-bd3d-8ec7129ca2e7 -# ╟─b3b97b57-a4f7-4cb6-80c8-6ad893ade75d -# ╠═99864b21-3db0-4d60-a84e-8e96db4de4ae -# ╟─344eb3a7-8614-4349-a536-62d9acef6bda -# ╠═670ccfa4-24fa-4167-a46a-2a48dc19538b -# ╟─c262d3ed-9f62-45ad-a7ee-ec78ab16f35c -# ╠═6600d28e-2522-4069-a5e0-643be43f6117 -# ╠═d9e3a4ac-c138-4b93-a19c-737166a0f0ea -# ╟─f07d8205-e172-45fa-b955-792bd95f3023 -# ╠═1c142797-ada6-4d77-a22f-b981ffd38956 -# ╠═cb397e0b-56f9-420f-a86e-bfab35286b44 -# ╟─9e6946a4-f207-4bfb-9e19-aa7b77c2a05b -# ╠═f58ce914-f366-4087-a7d1-8cfe69ac623b -# ╟─05972c7f-f64f-4865-b0a7-f33029d0a6fa -# ╟─04a1d1ad-53a9-4146-be09-65aa422e3730 -# ╟─e176e6ce-33da-4f3b-860e-4db99a931079 -# ╠═3a1bf6da-b2e9-4c86-a83d-abe4de5a0483 -# ╟─79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 -# ╟─fa2270d0-2548-409c-a23f-4369d8bce8ec -# ╟─794b93c9-0a0a-4e77-b13c-0e2c06a9a0ec -# ╟─dca50b37-b06c-4efe-881e-cf966ebc8fd7 -# ╠═2836f231-3b90-4829-9012-3ed9a09239ff -# ╟─e8db84be-31a0-415d-a194-064c4c87a293 -# ╠═78d175b4-a9b2-49b5-bbc8-eb348799985b -# ╟─63952c54-3304-4500-9536-b375c5c8f280 -# ╠═7dc013f7-7b35-42e4-aa77-0dca3755f389 -# ╟─86785c55-b567-449c-ae76-bc15a16223bc -# ╟─05e96232-ce95-452b-98e4-e79817d45ae2 -# ╠═9a705b3f-3c45-4896-9b89-e9c37d47fd89 -# ╠═55a13ebc-09e7-433c-83a8-9b6805e2fbec -# ╟─f1293675-b4d0-4827-8714-65a593ecc00e -# ╠═7005d88f-9e28-47fe-9a32-dec023903dc3 -# ╟─c8853554-26c5-491c-a536-4c07e8c6a986 -# ╠═025c1154-b2ae-4e1c-af1b-277b24d648a4 -# ╟─f58825d2-55fc-43f1-b164-9555bf9f5b84 -# ╠═8044dd5f-9996-4062-a54a-8fb0eee30b46 -# ╟─a1752ad4-2c11-416c-8321-9da058f9aaea -# ╠═0f58fd3d-1495-46c7-9d92-5539395cf12e -# ╟─6321774c-5bbe-42a7-bdad-0168c891b5ce -# ╠═eb1e5f97-3e09-43f0-b6ce-cc6cbc42ec2f -# ╟─b31b4d24-ae30-4cff-b6b7-348200feaa5d -# ╠═f2fa716f-d7b5-4c95-a8c6-6b16fbfe1499 -# ╟─0b413b98-a800-44e9-a917-ba61467bd613 -# ╠═6be6fea7-561b-4eec-b249-fa4522a5b039 -# ╟─98070d41-e8c5-49e6-9eea-04c718cbff65 -# ╠═cef751aa-f9c8-46da-9436-9cc2e5d7515c -# ╟─9e7bb7ec-8fe3-422e-920f-13c2ef055feb -# ╠═b13b5e77-ca60-465b-9f63-be9e5da0482f -# ╟─735f5191-259e-440f-a379-20a2a70ec72c -# ╠═689556c9-a690-4e9e-9052-7ac66e999d4d -# ╠═b85f0d09-a102-435b-a898-149ba4b29caa -# ╟─d067f814-1dd5-4e59-8cfd-3bc0b2d97612 -# ╠═e01657f3-33ac-4eb8-b39e-e2441a165a8d -# ╠═7231e54e-b293-48fc-b1f0-273f45f51539 -# ╟─0524a976-9d4d-4ef9-b088-bdd9102415e6 -# ╠═33031b53-6f2d-4256-a0aa-0ba97beaa7af -# ╠═22b58c97-1aa2-49c4-8a0a-488c63014a90 -# ╟─41abf8c4-e67e-4f66-a38a-7204a878d98d -# ╟─e34fed6f-6b58-4642-84eb-167467881bb2 -# ╟─f311e943-c96b-4af3-8757-c662fd302a88 -# ╠═1ae76036-42ef-46e9-88bf-d66d4267addc -# ╟─e190ceca-30b1-49e2-baf5-ceb62929f4c0 -# ╠═acaf5b91-accc-4dfa-9371-6495a47c8736 -# ╟─ed1150f1-9c75-4867-b7f5-535d605810f4 -# ╠═d35ef155-61aa-4b19-872f-d6e621f96572 -# ╟─3f546d58-15ff-442d-a4e8-7c0783ed2fe0 -# ╟─b778f786-87bc-4103-a792-2ae5cf004d50 -# ╠═edd76a4e-e541-4a83-923f-6f593d8fadad -# ╠═05f1ae6c-1229-4924-9c41-9554d6ca4261 -# ╟─70df05e7-4527-4087-a762-942a33e89f74 -# ╠═bf9291d5-6fc5-4359-874e-167237a22147 -# ╟─9e3158c5-d4b2-456c-b920-8431e2047770 -# ╠═3aab9073-cf4d-4ee5-9e8a-e3db586d9f68 -# ╟─30c35371-d106-4673-8569-d07b39edbdfe -# ╠═c01fe052-3701-4e22-9a64-f681d52a445a -# ╟─4f959d21-954b-4116-a498-bafc1aada47a -# ╠═5f427df2-1b7e-46c7-adb9-66b05deac6e8 -# ╟─958854fc-8e23-4364-88c5-1ec060b86a16 -# ╠═be21e269-16c4-42b0-8930-2d009bd91161 -# ╟─e57f5916-b753-429b-8434-8a85a67bd1fb -# ╠═69f96b57-7c8d-4e64-9e6a-9804d001b0b1 -# ╟─f7f6c56b-4910-4641-a462-eacfa4b4d034 -# ╠═84c1bb38-23c3-4f70-be67-7da2300a737b -# ╟─09eeab39-79a1-420a-8298-72b7002ec168 -# ╠═9e15601f-4ad2-41be-8168-db30548a1c3b -# ╟─2b731e4a-d6eb-48f2-91f0-27e916d85683 -# ╠═68ad7e05-7f79-4e8d-9031-fefb5f5c0897 -# ╟─362a8e24-8fac-4978-bc0e-13596a05d39e -# ╠═e9114d64-fe3e-420d-9fde-4b0fcbbc8527 -# ╠═7c9e1112-0a63-417f-a65a-9c2bdfa8bc47 -# ╟─94e66042-b352-4bf2-a24b-b15789e10fe3 -# ╠═1711346a-c161-415a-96f9-1235d786a584 -# ╠═7a87bfbb-a8c0-4071-8f86-66513ae40968 -# ╟─7593a4bf-ad0f-4fd3-aa3c-8b6a5073cb02 -# ╠═ad70a1d5-9b68-4cd6-9259-a3c25cad706b -# ╟─12f64333-36d0-4e2a-9061-e3dcc7a4ae96 diff --git a/src/exercises/ssa_model_catalyst_intro.jl b/src/exercises/ssa_model_catalyst_intro.jl deleted file mode 100644 index 4545f752..00000000 --- a/src/exercises/ssa_model_catalyst_intro.jl +++ /dev/null @@ -1,392 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "14" -#> title = "2. SSA catalyst intro" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "SSA catalyst intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ a2582acb-7d17-43ab-b883-d766b1a2c984 -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ e5f8c320-eda0-11ee-37d0-458bdbd94f15 -using Markdown - -# ╔═╡ 2a1306f3-d811-459d-83ca-98cf62dc2db0 -using InteractiveUtils - -# ╔═╡ 4e03b93e-f63e-466c-9941-d66e62306010 -using PlutoUI; TableOfContents() - -# ╔═╡ 9e8fd818-a14f-41cf-b2fd-a7425141b283 -using Catalyst - -# ╔═╡ 0b3921c9-6d6e-4c52-8c21-d883ed493028 -using OrdinaryDiffEq, JumpProcesses, StatsPlots - -# ╔═╡ 62b185be-e327-4ef3-af39-819732d107bf -md""" -# Introduction to Catalyst (SSA) -""" - -# ╔═╡ f5f32d5d-0c13-4865-8024-ca47208c9b8e -md""" -Catalyst.jl is a symbolic modeling package for analysis and high performance simulation of chemical reaction networks. Catalyst defines symbolic ReactionSystems, which can be created programmatically or easily specified using Catalyst's domain specific language (DSL). -""" - -# ╔═╡ 8d9af65e-0499-4a6d-afbf-5afa9903a42e -md""" -This notebook describes the syntax for building chemical reaction network models using Catalyst's **D**omain-**S**pecific **L**anguage (DSL). We will illustrate this by implementing and solving an infection model by means of an SSA (**S**tochastic **S**imulation **A**lgorithm). -""" - -# ╔═╡ 9cfa5b79-2128-4d45-aa22-51da0e74f320 -md""" -## The infection model (revisited) -""" - -# ╔═╡ e1583a47-9171-4db2-a6e9-d4889ee294c7 -md""" -For the sake of clarity we restate the description of the previous infection model. - -It is important to model the outbreak of infectious diseases in order to devise appropriate measures to avoid global epidemics. In this exercise we consider an isolated group of people in which a viral disease is spreading. An infection model (similar to the SIR-model but slightly extended) will be used for this purpose. We are interested in the evolution of the number of susceptible ($S$), infected ($I$), deceased ($D$) and resistant ($R$) persons.\ -We make the following assumptions: -1. Transmission of the disease from an infected person to a susceptible person takes place through direct contact. The chance of any two inhabitants of the group coming into contact with each other is $\beta$, and the probability of infection after contact between an infected and a susceptible person is $\alpha$. -2. Note that the above assumption implicitly states that the probability of two neighbours coming into contact with each other is as high as the probability of two people living at two extremes of the territory coming into contact with each other. -3. A pereson leaves the infection period at a rate $r$ (hence, a person is contagious for an average of $1/r$ days. Without appropriate medication, a fraction $m$ of infected people die and a fraction $(1-m)$ of infected people acquire immunity after healing. -4. We assume that no one crosses the territory borders. -""" - -# ╔═╡ 723c2c53-7f75-4f2d-8608-11ef0ef274d9 -md""" -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``S`` | *persons* | number of susceptible persons | -| ``I`` | *persons* | number of infected persons | -| ``D`` | *persons* | number of deceased persons | -| ``R`` | *persons* | number of resistant persons | -""" - -# ╔═╡ 0148d340-a072-49c0-9b3c-29249c21a334 -md""" -| Variable | Unit | Meaning | -|:---------- |:---------- |:------------| -| ``\alpha`` | ``\frac{persons}{contact}`` | chances of getting infected after contact | -| ``\beta`` | ``\frac{contact}{persons^2\,day}`` | contact rate | -| ``r`` | ``\frac{1}{day}`` | rate of leaving infection period | -| ``m`` | ``\frac{person}{person}`` | fraction of persons deceasing | -| ``1-m`` | ``\frac{person}{person}`` | fraction of persons becoming resistant | -""" - -# ╔═╡ 66d75717-3bcd-4e73-a8de-ed3879efb509 -md""" -Hence, the infection rate is ``\alpha \beta``. This means that a susceptible person meets an infected person: ``S+I``, this will result in ``2I`` at a rate ``\alpha \beta``. Futhermore, an infected person ``I`` will either become a deceased person ``D`` at a rate ``m r`` or become a resistant person ``R`` at rate ``(1-m) r`` -""" - -# ╔═╡ e884ea42-87ac-4ccc-bf19-937837a2645d -md""" -Our infection model has three reaction events: - -- Infection, where a susceptible persons meets an infected persons and also becomes infected. -- Deceasing, where an infected person die. -- Recovery, where an infected person recovers. -""" - -# ╔═╡ 10c93224-ac8d-4512-be8f-6961b885712f -md""" -Each reaction is also associated with a specific rate: - -- ``\alpha \beta``, the infection rate. -- ``m r``, the death rate. -- ``(1-m) r``, the recovery rate. -""" - -# ╔═╡ 7064d3d4-7ccb-4af2-a1ed-64430c50651f -md""" -Hence, the following *infection reactions* are: - -$$S + I \xrightarrow[]{\alpha \beta} 2I$$ -$$I \xrightarrow[]{mr} D$$ -$$I \xrightarrow[]{(1-m)r} R$$ -""" - -# ╔═╡ 4dd77999-2ed0-415e-b044-1af1de1b4ae3 -md""" -We are going to implement this system of *reactions* using Catalyst. -""" - -# ╔═╡ 30a0e2ec-ec30-401b-9270-939a767b54a8 -md""" -We first load the Catalyst package, which is required for the code in this introduction to run: -""" - -# ╔═╡ d888c734-044b-424d-b895-f8bc738346ed -md""" -### Implementation of the system - -First we create a *reaction network object*, that we have named `infection_model`, that implements the aforementioned *reactions*. -""" - -# ╔═╡ c792559f-db9b-4d9a-8e79-c7e8f82b4603 -infection_model = @reaction_network begin - α * β, S + I --> 2I - r * m, I --> D - r * (1 - m), I --> R -end - -# ╔═╡ 2085fd23-28fc-4b68-a3e7-590c50f2c9c6 -md""" -You can get a list of the different *reaction* **species** with the command `species` -""" - -# ╔═╡ f3e1feb4-d36a-4095-8e25-df02c73078e9 -species(infection_model) - -# ╔═╡ 22145b7a-5a55-4db1-9061-ad51e907490f -md""" -The *reaction model* can be converted to a symbolic differential equation model via -""" - -# ╔═╡ 7f15968c-dadc-4860-8071-9b072673e414 -osys = convert(ODESystem, infection_model) - -# ╔═╡ f9feffe6-c7d6-4962-ada5-85a51664ff2a -md""" -You can get a list of the differential equations with the command `equations`: -""" - -# ╔═╡ a0ee4837-074a-4587-a9d6-b8892a6c99f4 -equations(osys) - -# ╔═╡ 1d6f5b69-ab47-4344-985f-3e9c9430fddd -md""" -To get a list of the state variables, you can use the command `unknowns`: -""" - -# ╔═╡ 6bc9574c-0f04-45c0-a457-6601f1333c0e -unknowns(osys) - -# ╔═╡ b3584f8c-50f4-4800-96aa-776cfc2b8db3 -md""" -To get a list of the parameters, you can use the command `parameters`: -""" - -# ╔═╡ 98fa7987-b60b-4748-a3e8-38259bb0cd8c -parameters(osys) - -# ╔═╡ d94a467a-3194-4573-a3e0-27a265147e66 -md""" -### Simulating the system as a (Discrete) Jump problem -""" - -# ╔═╡ d8b11407-e8c0-4ad0-a49d-8e422ccc3a9c -md""" -We first need to load the OrdinaryDiffEq and StatsPlots packages, which are required for simulating the system and plotting the results. Additionally, the **JumpProcesses** package is needed to define and solve Jump problems. -""" - -# ╔═╡ 78e55389-3431-41de-9f4a-b6b45cb8988b -md""" -Instead of simulating our model with the species defined as decimal numbers, we will simulate the individual reaction events through the so-called **Gillespie algorithm**. This algorithm is a so-called **Stochastic Simulation Algorithm** (SSA).\ -The Gillespie algorithm is a computational method used to simulate **discrete** and **stochastic** (random) processes. The algorithm models the changes in a system over time by considering individual events and their probabilities, this allows to understand how random fluctuations affect the system's behavior. -""" - -# ╔═╡ 9bed9d84-600a-44f0-87a1-0997d547791f -md""" -To illustrate the simulation based on the Gillespie-algorithm, we will use the same infection model as before, but considering much less individuals. Hence, we will use different initial conditions, parameter values and timespan as with the ODE problem. -""" - -# ╔═╡ a2349455-5850-4950-bb10-221ae813b26f -md""" -Assume in this example that there are $50$ people on the territory, and that initially 1 person is infected. Hence, $I_0 = 1$, $S_0 = 50-I_0 = 49$, $D_0 = 0$ and $R_0 = 0$.\ -Furthermore, we take the following values for the parameters: $\alpha = 0.15\;person/contact$, $\beta = 0.1\;contact/(person^2\,day)$, $r = 0.2\;day^{-1}$ (i.e. a person is contagious for an average of $5\;days$) and $m=0.6$.\ -Finally, we want to run our simulation from day $0$ till day $60$. -""" - -# ╔═╡ 60b150fc-b9e5-426d-8d48-95efc907692a -md""" -### Setting initial conditions - -The vector holding the initial conditions for $S$, $I$, $D$ and $R$ is: -""" - -# ╔═╡ bca8459b-8f05-4e4e-92f1-43659ef652ba -u0 = [:S => 49, :I => 1, :D => 0, :R => 0] - -# ╔═╡ ad8215f6-676e-4352-b0c9-8e9701da3bc6 -md""" -### Setting parameter values - -The vector holding the parameter values for $\alpha$, $\beta$, $r$ and $m$ is: -""" - -# ╔═╡ da136f6e-7605-4f1f-81bc-f0e81ed7f528 -params = [:α => 0.15, :β => 0.1, :r => 0.2, :m => 0.6] - -# ╔═╡ cef064bf-2168-4904-b753-a012a9c9f070 -md""" -### Setting the timespan -""" - -# ╔═╡ 69b38ef3-d99e-4f4d-8c53-79e20d0094f0 -tspan = (0.0, 60.0) - -# ╔═╡ 57567d6d-6303-4ad8-b171-f904b594b3fe -md""" -### Creating an DiscreteProblem -""" - -# ╔═╡ 92c77720-5120-4f55-8cf6-168ec8553638 -md""" -Unlike the previous approach with ODEProblem (denoting a deterministic ordinary differential equation), we wish to simulate our model as a jump process (where each reaction event denotes a single jump in the state of the system). We do this by first creating a **DiscreteProblem**, and then using this as an input to a **JumpProblem**. -""" - -# ╔═╡ 65e8b392-4a4c-4e09-a95e-a633a42e4bee -md""" -We create a DiscreteProblem by calling the `DiscreteProblem` function. Applying this function ensures that the problem is approached at a level of individual infections (reactions). Hence, the variable values will be integers. *Note that the order in which the input (the model name, the initial condition, the timespan, and the parameter values) is provided to* `DiscreteProblem` *matters!* Here, we save our DiscreteProblem in the `dprob` variable. -""" - -# ╔═╡ e2f215e7-8d20-4e72-9e0b-cc0ce150e55b -dprob = DiscreteProblem(infection_model, u0, tspan, params) - -# ╔═╡ d061c4e2-8fbb-4ac5-95cf-c37d2916e669 -md""" -Next, we create a so-called JumpProblem by calling the `JumpProblem` function. Applying this function ensures that the infections (reactions) will happen stochastically. *Note again that the order in which the input (the model name, the DiscreteProblem variable, the simulation method) is provided to* `JumpProblem` *matters!* The simulation method is denoted by the option `Direct()`, which we recommend for now. -""" - -# ╔═╡ b462661a-3908-48e7-b162-f3b78d37231f -jprob = JumpProblem(infection_model, dprob, Direct()) - -# ╔═╡ bb1538ec-99e4-4ad0-8aaa-d552c6291c6d -md""" -### Solving the DiscreteProblem -""" - -# ╔═╡ 89c75cee-2488-4e9c-a470-b2111dcfc871 -md""" -Finally, we can simulate our model using the solve function, and plot the solution using the `plot` function. Here, the `solve` function also has a second argument `SSAStepper()`, which we recommend for now. This is a time stepping algorithm that calls the `Direct` solver method to advance a simulation. -""" - -# ╔═╡ 8fbb4942-888c-4798-b972-7860e695d5ba -dsol = solve(jprob, SSAStepper()) - -# ╔═╡ 5cfe9878-cbc2-4852-a757-ad8247d1f3d6 -md""" -Note that at the different time points the variables values in the solution are integer numbers and reflect the number of persons in either state ($S$, $I$, $D$ and $R$).\ -Futhermore, note that executing the `solve` command at different occasions will result in other solutions because of the stochastic character of the applied method. -""" - -# ╔═╡ ed8dce8b-d83b-40bc-b1eb-1ab369611cc1 -md""" -Finally, we can plot the solution through the plot function. -""" - -# ╔═╡ f855160a-2f04-4d11-97de-688113067c1c -plot(dsol) - -# ╔═╡ 719af6d7-3c98-499e-b789-a6de5af2a027 -md""" -Below is a piece of code that solves the problem a $1000$ times and stores the time values at which the number of infected persons becomes zero. -""" - -# ╔═╡ bcd1e77b-0869-4b36-9309-598b8c771d80 -begin - times = [] # make empty vector - while length(times) < 1000 # while statement - dsol2 = solve(jprob, SSAStepper()) # solve the problem - j = findfirst(dsol2[:I] .== 0) # find index of first 0 - if j != nothing # if index is a valid index - append!(times, dsol2.t[j]) # append time to vector times - end - end -end - -# ╔═╡ e97ca371-9ee5-4419-afcd-2a35ed7426d1 -md""" -The vector `times` is now filled with time values at which the number of infected persons becomes zero. -""" - -# ╔═╡ a0e70668-6c4e-4395-b1e4-925952a52dea -times - -# ╔═╡ 764c7739-faad-4cf3-a64c-8ce3cae64074 -md""" -With this vector we make a histogram so that you can have an idea of the distribution when the infected persons becomes zero. -""" - -# ╔═╡ 1971359b-df64-49e5-9011-c992da211c74 -histogram(times, bins=range(0, 60, length=61)) -# histogram(times, bins=range(0, 60, length=61), normalize=:pdf) - -# ╔═╡ Cell order: -# ╠═e5f8c320-eda0-11ee-37d0-458bdbd94f15 -# ╠═2a1306f3-d811-459d-83ca-98cf62dc2db0 -# ╠═a2582acb-7d17-43ab-b883-d766b1a2c984 -# ╠═4e03b93e-f63e-466c-9941-d66e62306010 -# ╟─62b185be-e327-4ef3-af39-819732d107bf -# ╟─f5f32d5d-0c13-4865-8024-ca47208c9b8e -# ╟─8d9af65e-0499-4a6d-afbf-5afa9903a42e -# ╟─9cfa5b79-2128-4d45-aa22-51da0e74f320 -# ╟─e1583a47-9171-4db2-a6e9-d4889ee294c7 -# ╟─723c2c53-7f75-4f2d-8608-11ef0ef274d9 -# ╟─0148d340-a072-49c0-9b3c-29249c21a334 -# ╟─66d75717-3bcd-4e73-a8de-ed3879efb509 -# ╟─e884ea42-87ac-4ccc-bf19-937837a2645d -# ╟─10c93224-ac8d-4512-be8f-6961b885712f -# ╟─7064d3d4-7ccb-4af2-a1ed-64430c50651f -# ╟─4dd77999-2ed0-415e-b044-1af1de1b4ae3 -# ╟─30a0e2ec-ec30-401b-9270-939a767b54a8 -# ╠═9e8fd818-a14f-41cf-b2fd-a7425141b283 -# ╟─d888c734-044b-424d-b895-f8bc738346ed -# ╠═c792559f-db9b-4d9a-8e79-c7e8f82b4603 -# ╟─2085fd23-28fc-4b68-a3e7-590c50f2c9c6 -# ╠═f3e1feb4-d36a-4095-8e25-df02c73078e9 -# ╟─22145b7a-5a55-4db1-9061-ad51e907490f -# ╠═7f15968c-dadc-4860-8071-9b072673e414 -# ╟─f9feffe6-c7d6-4962-ada5-85a51664ff2a -# ╠═a0ee4837-074a-4587-a9d6-b8892a6c99f4 -# ╟─1d6f5b69-ab47-4344-985f-3e9c9430fddd -# ╠═6bc9574c-0f04-45c0-a457-6601f1333c0e -# ╟─b3584f8c-50f4-4800-96aa-776cfc2b8db3 -# ╠═98fa7987-b60b-4748-a3e8-38259bb0cd8c -# ╟─d94a467a-3194-4573-a3e0-27a265147e66 -# ╟─d8b11407-e8c0-4ad0-a49d-8e422ccc3a9c -# ╠═0b3921c9-6d6e-4c52-8c21-d883ed493028 -# ╟─78e55389-3431-41de-9f4a-b6b45cb8988b -# ╟─9bed9d84-600a-44f0-87a1-0997d547791f -# ╟─a2349455-5850-4950-bb10-221ae813b26f -# ╟─60b150fc-b9e5-426d-8d48-95efc907692a -# ╠═bca8459b-8f05-4e4e-92f1-43659ef652ba -# ╟─ad8215f6-676e-4352-b0c9-8e9701da3bc6 -# ╠═da136f6e-7605-4f1f-81bc-f0e81ed7f528 -# ╟─cef064bf-2168-4904-b753-a012a9c9f070 -# ╠═69b38ef3-d99e-4f4d-8c53-79e20d0094f0 -# ╟─57567d6d-6303-4ad8-b171-f904b594b3fe -# ╟─92c77720-5120-4f55-8cf6-168ec8553638 -# ╟─65e8b392-4a4c-4e09-a95e-a633a42e4bee -# ╠═e2f215e7-8d20-4e72-9e0b-cc0ce150e55b -# ╟─d061c4e2-8fbb-4ac5-95cf-c37d2916e669 -# ╠═b462661a-3908-48e7-b162-f3b78d37231f -# ╟─bb1538ec-99e4-4ad0-8aaa-d552c6291c6d -# ╟─89c75cee-2488-4e9c-a470-b2111dcfc871 -# ╠═8fbb4942-888c-4798-b972-7860e695d5ba -# ╟─5cfe9878-cbc2-4852-a757-ad8247d1f3d6 -# ╟─ed8dce8b-d83b-40bc-b1eb-1ab369611cc1 -# ╠═f855160a-2f04-4d11-97de-688113067c1c -# ╟─719af6d7-3c98-499e-b789-a6de5af2a027 -# ╠═bcd1e77b-0869-4b36-9309-598b8c771d80 -# ╟─e97ca371-9ee5-4419-afcd-2a35ed7426d1 -# ╠═a0e70668-6c4e-4395-b1e4-925952a52dea -# ╟─764c7739-faad-4cf3-a64c-8ce3cae64074 -# ╠═1971359b-df64-49e5-9011-c992da211c74 diff --git a/src/exercises/ssa_model_foxes_rabbits.jl b/src/exercises/ssa_model_foxes_rabbits.jl deleted file mode 100644 index c5a65317..00000000 --- a/src/exercises/ssa_model_foxes_rabbits.jl +++ /dev/null @@ -1,263 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "15" -#> title = "2. SSA foxes rabbits" -#> date = "2025-02-07" -#> tags = ["exercises"] -#> description = "SSA foxes rabbits" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - - -using Markdown -using InteractiveUtils - -# ╔═╡ e8dd4f61-4f51-4255-9193-62ea1368240b -begin - # add this cell if you want the notebook to use the environment from where the Pluto server is launched - using Pkg - Pkg.activate("../../pluto-deployment-environment") -end - -# ╔═╡ 9b9d35c4-9136-11ef-284a-d5964e231d9e -using Markdown - -# ╔═╡ ae107329-e87d-4ff7-a068-362cb03fe815 -using InteractiveUtils - -# ╔═╡ c7c12802-f3e7-4e42-be3f-f91eabe432d8 -using Catalyst - -# ╔═╡ 92c50b0a-d9f1-45a9-9c8a-ced71a6ee967 -using OrdinaryDiffEq, JumpProcesses, StatsPlots - -# ╔═╡ 2a129f80-f4c2-4429-85e8-98d946fb3f36 -using PlutoUI; TableOfContents() - -# ╔═╡ 3a1a01f6-7f6e-411d-958b-0dce35b791ff -md""" -# Exercise - foxes and rabbits -## An ODE and a discrete (jump) problem -""" - -# ╔═╡ 92a0efee-1e40-4080-aee2-831d98312176 -md""" -Rabbits live on some secluded territory. Their maximum growth rate coefficient is $r$ $[year^{-1}]$, and their population capacity is $R_m$ $[\#rabbits]$. The rabbits die of old age or sickness with a dying rate coefficient $d$ $[year^{-1}]$. - -At $t=0$, foxes intrude the territory and stay there. The foxes exclusively feed themselves with the rabbits. They hunt the rabbits at a rate proportional to the number of foxes (proportionality factor is $h$ $[year^{-1} \#foxes^{-1}]$). The population of foxes grows at a rate proportional to the number of rabbits (proportionality factor is $g$ $[year^{-1} \#rabbits^{-1}]$). The foxes die of old age or sickness with a dying rate coefficient $\delta$ $[year^{-1}]$. - -The initial number of rabbits on the territory is 89, the initial number of -foxes intruding the territory is 2. -""" - -# ╔═╡ 91e77fb0-cab3-4da7-8bc9-6f657df2497c -md""" -**Exercises:** - -1. Make simulations of the evolution of rabbits and foxes as a ODE problem in the time interval $[0, 10]\;years$. - -2. Make simulations of the evolution of rabbits and foxes as a discrete (jump) problem in the time interval $[0, 10]\;years$. - -Assume the following parameter values: $r = 18.4\;year^{-1}$, $R_m = 120\;(\#rabbits)$, $d = 2.0\;year^{-1}$, $h = 1.4\;year^{-1}(\#foxes)^{-1}$, $g = 0.05\;year^{-1}(\#rabbits)^{-1}$ and $\delta = 1.0\;year^{-1}$. -""" - -# ╔═╡ d11b4e97-df16-4641-9e32-c7f2a098ffb7 -md""" -Create a *reaction network object* model for the aforementioned problem. Name it `foxes_rabbits_rn`. - -Hints: -- Use the variable names `R` and `F` for the rabbits and foxes respectively. -- Use the variable names `r`, `Rm`, `d`, `h`, `g` and `δ` for the parameters. -""" - -# ╔═╡ 6d49c334-76e0-4210-890b-e3e79097222d -# Uncomment and complete the instruction -# foxes_rabbits_rn = @reaction_network begin -# @species missing -# @parameters missing -# missing # natural population growth of the rabbits -# missing # deaths by age or sickness of the rabbits -# missing # hunting of rabbits by the foxes -# missing # gaining of foxes by hinting rabbits -# missing # deaths by age or sickness of the foxes -# end - -# ╔═╡ 1c2c38f9-6c76-4274-8e87-178ca7813706 -md""" -Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model makes sense. -""" - -# ╔═╡ 3ffbec86-a33a-4a71-9c8c-9f895a3a1f26 -# osys = missing # Uncomment and complete the instruction - -# ╔═╡ 577ae94c-543c-4717-981b-ce90c02d1828 -md""" -Initialize a vector `u0` with the initial conditions: -""" - -# ╔═╡ 55c208e5-f123-4c4e-8b03-c3e3d3eda605 -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 5270da4a-7d14-44cd-8672-9e481888704a -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 10f878d8-d2da-4701-b190-7a2feaa0c009 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 1a835253-3677-4698-9347-d59937f9e7c0 -md""" -Initialize a vector `params` with the parameter values: -""" - -# ╔═╡ 2bd4dd93-1be0-427b-b372-88cb9849a124 -# params = missing # Uncomment and complete the instruction - -# ╔═╡ 4953f436-afed-4363-b840-956440fddc25 -md""" -### Exercise 1 - Solve the problem as an ODE problem. -""" - -# ╔═╡ a52b62fe-dd92-49ca-b09e-fadb29cc1f35 -md""" -Create the ODE problem and store it in `oprob`: -""" - -# ╔═╡ 629126b3-0b6b-43f0-9142-2d4d7d6270d9 -# oprob = missing # Uncomment and complete the instruction - -# ╔═╡ 3c071a33-5092-42f7-80b2-63066bdf3f22 -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=0.05`. Store the solution in `osol`: -""" - -# ╔═╡ 0f17c44c-a2e6-4b4a-be9c-e2e9763a3374 -# osol = missing # Uncomment and complete the instruction - -# ╔═╡ e4bf9eca-ffee-4dca-8318-74875c636245 -md""" -Plot the solution. -""" - -# ╔═╡ a35eccae-7890-41a1-82a0-6cf98488d8a7 -# missing # Uncomment and complete the instruction - -# ╔═╡ c9ca9a36-784a-4560-93bf-4381812f41ac -md""" -### Exercise 2 - Solve the problem as a Discrete (jump) problem. -""" - -# ╔═╡ 7d6c894e-90f2-44ad-9d2e-adf5efd4213e -md""" -Create a DiscreteProblem and store it in `dprob`: -""" - -# ╔═╡ 581a5b6e-04a7-4fd1-9f69-a9cf8655a06c -# dprob = missing # Uncomment and complete the instruction - -# ╔═╡ 497fe4e8-b451-4dd7-b961-6e7968c6cb3d -md""" -Create a JumpProblem and store it in `jdprob`. Use the simulation method `Direct()`. -""" - -# ╔═╡ 74e91495-998e-48a0-b14a-9d7be2c6410d -# jdprob = missing # Uncomment and complete the instruction - -# ╔═╡ 9c9f097b-2a84-4383-843c-0cdc1b40e061 -md""" -Solve the problem and store it in `jdsol`. Use the `SSAStepper()` stepping algorithm. -""" - -# ╔═╡ c80337e8-6dbe-4c78-a793-d3a1e968c319 -# jdsol = missing # Uncomment and complete the instruction - -# ╔═╡ a743af00-cce6-4e6e-b426-107ad04960d8 -md""" -Plot the solution. -""" - -# ╔═╡ 46610efe-927a-4c5f-bd6a-fa8001949aa7 -# missing # Uncomment and complete the instruction - -# ╔═╡ 969bc08d-0709-4941-8fa8-3be707c5b0ed -md""" -Solve the problem several times by running the cell which solves the problem and see what happens in the plot. -""" - -# ╔═╡ a039ee1b-3232-459c-885b-4166b973ab1b -md""" -Think of your class of probability theory, why does the SSA model alsways lead to extinction? -""" - -# ╔═╡ 125809f6-46e4-49ec-8f96-f193d3036df6 -md"- Answer: missing" - -# ╔═╡ db421cb6-ba09-4476-9021-846a145bea5c -md""" -Write a piece of code that solves the problem a $1000$ times and stores the time values at which the rabbits die out. -""" - -# ╔═╡ a6bc48a8-3bbc-4abc-918b-da70eab39017 -# Uncomment and complete the instruction -# begin -# times = [] # make empty vector -# # missing -# # ... -# end - -# ╔═╡ 2eb41790-23ef-43fc-8b69-7dd9dbb9f820 -md""" -Make a histogram so that you can have an idea of the distribution when the rabbits die out. Use `bins=range(0, 10, length=121)`. -""" - -# ╔═╡ 1c8188de-e8b8-44ae-b0b6-7d490f3a15e8 -# missing # Uncomment and complete the instruction - -# ╔═╡ Cell order: -# ╠═9b9d35c4-9136-11ef-284a-d5964e231d9e -# ╠═ae107329-e87d-4ff7-a068-362cb03fe815 -# ╠═e8dd4f61-4f51-4255-9193-62ea1368240b -# ╠═c7c12802-f3e7-4e42-be3f-f91eabe432d8 -# ╠═92c50b0a-d9f1-45a9-9c8a-ced71a6ee967 -# ╠═2a129f80-f4c2-4429-85e8-98d946fb3f36 -# ╟─3a1a01f6-7f6e-411d-958b-0dce35b791ff -# ╟─92a0efee-1e40-4080-aee2-831d98312176 -# ╟─91e77fb0-cab3-4da7-8bc9-6f657df2497c -# ╟─d11b4e97-df16-4641-9e32-c7f2a098ffb7 -# ╠═6d49c334-76e0-4210-890b-e3e79097222d -# ╟─1c2c38f9-6c76-4274-8e87-178ca7813706 -# ╠═3ffbec86-a33a-4a71-9c8c-9f895a3a1f26 -# ╟─577ae94c-543c-4717-981b-ce90c02d1828 -# ╠═55c208e5-f123-4c4e-8b03-c3e3d3eda605 -# ╟─5270da4a-7d14-44cd-8672-9e481888704a -# ╠═10f878d8-d2da-4701-b190-7a2feaa0c009 -# ╟─1a835253-3677-4698-9347-d59937f9e7c0 -# ╠═2bd4dd93-1be0-427b-b372-88cb9849a124 -# ╟─4953f436-afed-4363-b840-956440fddc25 -# ╟─a52b62fe-dd92-49ca-b09e-fadb29cc1f35 -# ╠═629126b3-0b6b-43f0-9142-2d4d7d6270d9 -# ╟─3c071a33-5092-42f7-80b2-63066bdf3f22 -# ╠═0f17c44c-a2e6-4b4a-be9c-e2e9763a3374 -# ╟─e4bf9eca-ffee-4dca-8318-74875c636245 -# ╠═a35eccae-7890-41a1-82a0-6cf98488d8a7 -# ╟─c9ca9a36-784a-4560-93bf-4381812f41ac -# ╟─7d6c894e-90f2-44ad-9d2e-adf5efd4213e -# ╠═581a5b6e-04a7-4fd1-9f69-a9cf8655a06c -# ╟─497fe4e8-b451-4dd7-b961-6e7968c6cb3d -# ╠═74e91495-998e-48a0-b14a-9d7be2c6410d -# ╟─9c9f097b-2a84-4383-843c-0cdc1b40e061 -# ╠═c80337e8-6dbe-4c78-a793-d3a1e968c319 -# ╟─a743af00-cce6-4e6e-b426-107ad04960d8 -# ╠═46610efe-927a-4c5f-bd6a-fa8001949aa7 -# ╟─969bc08d-0709-4941-8fa8-3be707c5b0ed -# ╟─a039ee1b-3232-459c-885b-4166b973ab1b -# ╟─125809f6-46e4-49ec-8f96-f193d3036df6 -# ╟─db421cb6-ba09-4476-9021-846a145bea5c -# ╠═a6bc48a8-3bbc-4abc-918b-da70eab39017 -# ╟─2eb41790-23ef-43fc-8b69-7dd9dbb9f820 -# ╠═1c8188de-e8b8-44ae-b0b6-7d490f3a15e8 diff --git a/src/exercises/uncert_bitrophic_model.jl b/src/exercises/uncert_bitrophic_model.jl deleted file mode 100755 index c23c9939..00000000 --- a/src/exercises/uncert_bitrophic_model.jl +++ /dev/null @@ -1,167 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "34" -#> title = "6. Uncertainty bithropic model" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Uncertainty bithropic model" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 7d2ce89c-de3e-4fff-8f23-8756351b377e -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 079bc7cc-0c53-11ef-2323-8b475737e481 -using Markdown - -# ╔═╡ 3d189b53-8aff-4ca0-a784-11b0bca02374 -using InteractiveUtils - -# ╔═╡ d89c7b51-a94c-42e5-94f1-2dc1e641dfb3 -using Catalyst - -# ╔═╡ 49c93319-f76d-485e-a2e2-e06210e25df3 -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ b8c575fd-a94b-4221-8751-74c51f10ec7d -using Measurements - -# ╔═╡ bedf7419-590c-47fd-8089-b890dded0468 -md""" -### Exercise: Bitrophic model - Uncertainty analysis -""" - -# ╔═╡ 8f795d92-b842-4392-8bc3-956080fcc6a8 -md""" -In one of the previous practicals we were introduced to a bitrophic model in which the dynamic relationship between a field crop $C$ and a voracious insect population $A$ within an ecosystem was modelled. - -$$\begin{eqnarray*} -\frac{dC}{dt} &= \theta C \left(1-\frac{C}{k}\right)-fCA \\ -\frac{dA}{dt} &= \phi f CA -(1 + p)\, \mu A -\end{eqnarray*}$$ -""" - -# ╔═╡ bc2e3724-de5f-4455-a4be-019c6a8accae -md""" -The *reaction network object* for this model could be set-up as: -""" - -# ╔═╡ 90e9da81-7576-43bb-b4ce-1293a48bf3ec -bitrophic_model = @reaction_network begin - θ*(1-C/k), C --> 2C - f, C+A --> (1+ϕ)*A - (1+p)*μ, A --> 0 -end - -# ╔═╡ 465e4ea9-7066-4929-98c4-e5c378ef34d8 -md""" -Assume the uncertainties in the following parameter values: - -- $\theta=0.20 \pm 0.02\;d^{-1}$ -- $\phi=0.20 \pm 0.02$ -- $p=3.0 \pm 0.2$ - -and that the uncertainty in the other parameters values: $k=4000\;kg/ha$, $f=0.001\;ha/(kg\,d)$ and $\mu=0.1\;d^{-1}$ are negligible. Suppose that at the beginning of a season, $100\;kg$ of the crop and $0.5\;kg$ of insects per $ha$ are present. Perform an uncertainty analysis by plotting the uncertainty bands on the simulation results of $C$ and $A$ in a timespan of $[0, 200]\,days$. - -Interpret your results. -""" - -# ╔═╡ 1129972e-3e3d-4b3b-8c2c-3c57eaa3728a -md""" -Initialize a vector `u0` with the initial conditions, and set the timespan: -""" - -# ╔═╡ 79802166-efbd-4944-ae58-fb26a5d9ad4b -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 146855a0-5cda-4783-89af-7918c067e952 -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 4e8fea9e-e16c-4b17-8a6c-52bd8721cfd8 -md""" -We initialize a vector `params_uncert` with the parameter values and their corresponding uncertainty: -""" - -# ╔═╡ 0588cc16-a4b1-4b3f-8ba9-742680996e11 -# params_uncert = missing # Uncomment and complete the instruction - -# ╔═╡ ff76c3e0-396c-4b01-b979-2b95644203a0 - - -# ╔═╡ 6893ccdb-7f6c-4ec9-8d6b-520e784acd03 -md""" -We create the corresponding ODE problem and store it in `oprob_uncert`: -""" - -# ╔═╡ 60a6fb20-93d2-47d3-8ee1-3087d48c569d -# oprob_uncert = missing # Uncomment and complete the instruction - -# ╔═╡ 0739d816-0456-4cc2-beb7-2e926cf2e229 -md""" -We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert`: -""" - -# ╔═╡ f829fc66-3df8-4ac1-a1bb-8eb2ed722423 -# osol_uncert = missing # Uncomment and complete the instruction - -# ╔═╡ d4d6f69c-844b-488a-9170-b09e7c22dd25 -md""" -Plot the results (simulation of the output variables $C$ and $A$ together with their uncertainty band): -""" - -# ╔═╡ 2152a72b-31cd-44b9-90d8-615610e8d261 -# missing # Uncomment and complete the instruction - -# ╔═╡ 600dd2e9-aed9-41da-a510-9ba78788f207 -md""" -Try to relate the local sensitivity analysis to the uncertainty analysis. Hence, study the effect of the individual parameter uncertainties on the output variables $C$ and $A$ and compare with your local sensitivity results of the corresponding parameter. - -In order to do that, analyse the effect on the uncertainty bands for $C$ and $A$ by taking one uncertainty on a parameter at a time. In other words, analyse the subsequent cases separately: -- Assume uncertainty only in $\theta$ -- Assume uncertainty only in $\phi$ -- Assume uncertainty only in $p$ -""" - -# ╔═╡ 42817641-cca5-47d7-b1f3-57181a207bf4 -md""" -!!! question - Draw your conclusions. -""" - -# ╔═╡ d7cddaef-c1b9-431f-9835-94cece962ca3 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═079bc7cc-0c53-11ef-2323-8b475737e481 -# ╠═3d189b53-8aff-4ca0-a784-11b0bca02374 -# ╠═7d2ce89c-de3e-4fff-8f23-8756351b377e -# ╠═d89c7b51-a94c-42e5-94f1-2dc1e641dfb3 -# ╠═49c93319-f76d-485e-a2e2-e06210e25df3 -# ╠═b8c575fd-a94b-4221-8751-74c51f10ec7d -# ╟─bedf7419-590c-47fd-8089-b890dded0468 -# ╟─8f795d92-b842-4392-8bc3-956080fcc6a8 -# ╟─bc2e3724-de5f-4455-a4be-019c6a8accae -# ╠═90e9da81-7576-43bb-b4ce-1293a48bf3ec -# ╟─465e4ea9-7066-4929-98c4-e5c378ef34d8 -# ╟─1129972e-3e3d-4b3b-8c2c-3c57eaa3728a -# ╠═79802166-efbd-4944-ae58-fb26a5d9ad4b -# ╠═146855a0-5cda-4783-89af-7918c067e952 -# ╟─4e8fea9e-e16c-4b17-8a6c-52bd8721cfd8 -# ╠═0588cc16-a4b1-4b3f-8ba9-742680996e11 -# ╠═ff76c3e0-396c-4b01-b979-2b95644203a0 -# ╟─6893ccdb-7f6c-4ec9-8d6b-520e784acd03 -# ╠═60a6fb20-93d2-47d3-8ee1-3087d48c569d -# ╟─0739d816-0456-4cc2-beb7-2e926cf2e229 -# ╠═f829fc66-3df8-4ac1-a1bb-8eb2ed722423 -# ╟─d4d6f69c-844b-488a-9170-b09e7c22dd25 -# ╠═2152a72b-31cd-44b9-90d8-615610e8d261 -# ╟─600dd2e9-aed9-41da-a510-9ba78788f207 -# ╟─42817641-cca5-47d7-b1f3-57181a207bf4 -# ╠═d7cddaef-c1b9-431f-9835-94cece962ca3 diff --git a/src/exercises/uncert_fermenter_monod.jl b/src/exercises/uncert_fermenter_monod.jl deleted file mode 100755 index d47fe862..00000000 --- a/src/exercises/uncert_fermenter_monod.jl +++ /dev/null @@ -1,181 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.4 - -#> [frontmatter] -#> order = "33" -#> title = "6. Uncertainty fermenter monod" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Uncertainty fermenter monod" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 9d8acf40-e635-4dc4-9938-ec63ab68e3bd -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ 1ec7def2-0c43-11ef-0850-ddefa680d2a4 -using Markdown - -# ╔═╡ 2861bc17-7810-461e-8857-a55b6392977a -using InteractiveUtils - -# ╔═╡ 093679cc-122b-4b0a-84c3-15cf3a6f5c1b -using Catalyst - -# ╔═╡ 0720732e-105a-40c2-bf45-f8fbbc10e5bc -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 28bb60c9-ab56-497f-b039-0d6577501be5 -using Measurements - -# ╔═╡ 437364b6-a953-428b-a891-e17ffd531656 -md""" -### Exercise: Fermenter - Monod kinetics - Uncertainty analysis -""" - -# ╔═╡ c44e10f3-8452-4a60-b840-f4a7e6ec3238 -md""" -In one of the previous practicals we were introduced to a fermenter in which biomass $X$ [$g/L$] grows by breaking down substrate $S$ [$g/L$]. The reactor is fed with a inlet flow rate $Q_{in}$ [$L/h$], which consist of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. This process was modelled using Monod kinetics, resulting in the model below: - -$$\begin{eqnarray*} -S + X \xrightarrow[\quad\quad]{k} (1 + Y) \, X \quad\quad\quad\quad \textrm{with} \quad k = \cfrac{\mu_{max}}{S + K_s} -\end{eqnarray*}$$ -""" - -# ╔═╡ 356822df-bad3-41f9-8556-de04234b0a09 -md""" -The *reaction network object* for this model could be set-up as: -""" - -# ╔═╡ c551c33e-95b0-4ea8-85d4-6c68db72b8bc -fermenter_monod = @reaction_network begin - μmax/(S+Ks), S + X --> (1 + Y)*X - Q/V, (S, X) --> 0 - Q/V*Sin, 0 --> S -end - -# ╔═╡ 69f8c265-fe91-4e4b-971b-395325d4474e -md""" -which resulted in the following differential equations: -""" - -# ╔═╡ 2806654f-7a24-4f8e-8b5a-3129b2431c95 -md""" -$$\begin{eqnarray*} -\cfrac{dS}{dt} &=& \cfrac{Q}{V} \left(S_{in} - S \right) - \mu_{max}\cfrac{S}{S + K_s} X\\ -\cfrac{dX}{dt} &=& -\cfrac{Q}{V} X + Y \mu_{max}\cfrac{S}{S + K_s} X -\end{eqnarray*}$$ -""" - -# ╔═╡ 5d6a881d-421e-4dea-9668-38b3f3378bf1 -# osys = missing - -# ╔═╡ 5802078d-12bc-489e-8566-15f77a11e8ba -md""" -Assume the uncertainties in the following parameter values: - -- $\mu_{max} = 0.40 \pm 0.06\;h^{-1}$ -- $K_s = 0.015 \pm 0.003 \;g/L$ -- $S_{in} = 0.022 \pm 0.004\;g/L$ - -and that the uncertainty in the other parameters values: $Y = 0.67$, $Q = 2.0\;L/h$, $V = 40.0\;L$ are negligible. Suppose that at $t=0$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concetration of $0.0005\;g/L$. Perform an uncertainty analysis by plotting the uncertainty bands on the simulation results of $S$ and $X$ in a timespan of $[0, 100]\,h$. - -Interpret your results. -""" - -# ╔═╡ 562e0006-bb79-4895-98b4-c376ea6ce856 -md""" -Initialize a vector `u0` with the initial conditions, and set the timespan: -""" - -# ╔═╡ 6afb23f2-3da7-43e0-8a3b-fc4bb5eb9bde -# u0 = missing # Uncomment and complete the instruction - -# ╔═╡ 1b09c8cb-d3b7-4a15-b6ed-d03019c5f57b -# tspan = missing # Uncomment and complete the instruction - -# ╔═╡ 40a8c697-56d1-4488-b089-00ddf41cdb5a -md""" -We initialize a vector `params_uncert` with the parameter values and their corresponding uncertainty: -""" - -# ╔═╡ 62f4049b-617f-4268-beba-b2c634b46642 -# params_uncert = missing # Uncomment and complete the instruction - -# ╔═╡ 0e4c73d3-1630-463b-994d-073170c1a6c3 -md""" -We create the corresponding ODE problem and store it in `oprob_uncert`: -""" - -# ╔═╡ 3cad11d6-c2eb-4865-858a-d407aa25d780 -# oprob_uncert = missing # Uncomment and complete the instruction - -# ╔═╡ a7b3b68f-d34e-4e9a-aa44-224919c44fec -md""" -We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert`: -""" - -# ╔═╡ 40168902-0780-4f92-8eca-23982c80511e -# osol_uncert = missing # Uncomment and complete the instruction - -# ╔═╡ 6b2abf2a-f1f3-4c2d-96e8-cc626848fa68 -md""" -Plot the results (simulation of the output variables $S$ and $X$ together with their uncertainty band): -""" - -# ╔═╡ 4d753fdb-089d-41dc-89ef-45a1733af786 -# missing - -# ╔═╡ 4f3900d7-4ff8-4b4b-a59a-f7079ac6c457 -md""" -Try to relate the local sensitivity analysis to the uncertainty analysis. Hence, study the effect of the individual parameter uncertainties on the output variables $S$ and $X$ and compare with your local sensitivity results of the corresponding parameter. - -In order to do that, analyse the effect on the uncertainty bands for $S$ and $X$ by taking one uncertainty on a parameter at a time. In other words, analyse the subsequent cases separately: -- Assume uncertainty only in $\mu_{max}$ -- Assume uncertainty only in $K_s$ -- Assume uncertainty only in $S_{in}$ -""" - -# ╔═╡ ff3e6f75-c02b-4530-8b9a-6ff40915cb5c -md""" -!!! question - Draw your conclusions. -""" - -# ╔═╡ 33b4d1c8-5da2-4490-afe9-4778e11d3cc9 -md"- Answer: missing" - -# ╔═╡ Cell order: -# ╠═1ec7def2-0c43-11ef-0850-ddefa680d2a4 -# ╠═2861bc17-7810-461e-8857-a55b6392977a -# ╠═9d8acf40-e635-4dc4-9938-ec63ab68e3bd -# ╠═093679cc-122b-4b0a-84c3-15cf3a6f5c1b -# ╠═0720732e-105a-40c2-bf45-f8fbbc10e5bc -# ╠═28bb60c9-ab56-497f-b039-0d6577501be5 -# ╟─437364b6-a953-428b-a891-e17ffd531656 -# ╟─c44e10f3-8452-4a60-b840-f4a7e6ec3238 -# ╟─356822df-bad3-41f9-8556-de04234b0a09 -# ╠═c551c33e-95b0-4ea8-85d4-6c68db72b8bc -# ╟─69f8c265-fe91-4e4b-971b-395325d4474e -# ╟─2806654f-7a24-4f8e-8b5a-3129b2431c95 -# ╠═5d6a881d-421e-4dea-9668-38b3f3378bf1 -# ╟─5802078d-12bc-489e-8566-15f77a11e8ba -# ╟─562e0006-bb79-4895-98b4-c376ea6ce856 -# ╠═6afb23f2-3da7-43e0-8a3b-fc4bb5eb9bde -# ╠═1b09c8cb-d3b7-4a15-b6ed-d03019c5f57b -# ╟─40a8c697-56d1-4488-b089-00ddf41cdb5a -# ╠═62f4049b-617f-4268-beba-b2c634b46642 -# ╟─0e4c73d3-1630-463b-994d-073170c1a6c3 -# ╠═3cad11d6-c2eb-4865-858a-d407aa25d780 -# ╟─a7b3b68f-d34e-4e9a-aa44-224919c44fec -# ╠═40168902-0780-4f92-8eca-23982c80511e -# ╟─6b2abf2a-f1f3-4c2d-96e8-cc626848fa68 -# ╠═4d753fdb-089d-41dc-89ef-45a1733af786 -# ╟─4f3900d7-4ff8-4b4b-a59a-f7079ac6c457 -# ╟─ff3e6f75-c02b-4530-8b9a-6ff40915cb5c -# ╠═33b4d1c8-5da2-4490-afe9-4778e11d3cc9 diff --git a/src/exercises/uncert_intro.jl b/src/exercises/uncert_intro.jl deleted file mode 100755 index 54ed23f7..00000000 --- a/src/exercises/uncert_intro.jl +++ /dev/null @@ -1,421 +0,0 @@ -### A Pluto.jl notebook ### -# v0.20.6 - -#> [frontmatter] -#> order = "32" -#> title = "6. Uncertainty intro" -#> date = "2025-08-06" -#> tags = ["exercises"] -#> description = "Uncertainty intro" -#> layout = "layout.jlhtml" -#> -#> [[frontmatter.author]] -#> name = "Gauthier Vanhaelewyn" - -using Markdown -using InteractiveUtils - -# ╔═╡ 8349306c-e98c-4221-9a1b-322fde3e18cb -using Pkg; Pkg.activate("../../pluto-deployment-environment") - -# ╔═╡ d2c4d230-0943-11ef-3aad-5719e74bb20e -using Markdown - -# ╔═╡ 47790920-74d2-4f21-b0f2-45dc19d25156 -using InteractiveUtils - -# ╔═╡ 83f3d978-aefa-40c1-a647-b26e837aeed6 -using Catalyst - -# ╔═╡ 73d5f921-82e4-4160-a3c4-fc1935f4c58d -using OrdinaryDiffEq, StatsPlots - -# ╔═╡ 4260699d-4db2-4ac7-b8c8-7bc43fbc1c59 -using PlutoUI; TableOfContents() - -# ╔═╡ 5185d0eb-7392-4775-9336-3e0f9e1449ce -using Measurements - -# ╔═╡ 241a8a65-c59f-44f1-be39-d5edd1321b49 -md" -# Introduction to uncertainty analysis -" - -# ╔═╡ 0f518e36-bc96-4599-807e-728504e5ca7b -md" -## Goal of this practicum -" - -# ╔═╡ da3cdc89-b911-4af9-9d4a-526301cba581 -md" -Parameter uncertainty plays a crucial role in shaping the behavior of output variables. Model equations describe how systems evolve over time, often incorporating parameters representing various aspects of the system's characteristics. However, these parameters are rarely known with absolute certainty and they often come with inherent uncertainty due to measurement errors, variability in real-world conditions, or incomplete knowledge about the system. This uncertainty can propagate through the model equations, leading to uncertainties in the predicted outcomes. Consequently, understanding the influence of parameter uncertainty becomes essential for assessing the reliability and robustness of the model predictions, as well as for making informed decisions based on these predictions. Techniques such as sensitivity analysis and uncertainty quantification are employed to explore and quantify the impact of parameter uncertainty on the output variables, providing insights into the system's behavior and guiding the refinement of models for improved accuracy and reliability. -" - -# ╔═╡ 78afaded-5a19-4386-aa76-7974977ea354 -md" -Uncertainty in model parameters manifests as variability in the predicted outcomes, resulting in error bars around the output variables. These error bars represent the range of potential values that the output variables could take due to the uncertainty in the parameters. As the uncertainty in parameters increases, the width of these error bars typically expands, reflecting the increased variability and unpredictability in the model's predictions. -" - -# ╔═╡ eae11742-14c6-4b1b-a939-232710dfa10e -md" -We will now compute the variability in the output variables reflected as error bars assuming some uncertainty in the model parameters. To illustrate this concept, we first revisit the two simple models modelling the growth of grass. -" - -# ╔═╡ e5ab4490-6fd4-4b51-bfa7-1366438efefc -md" -## Grass growth models -" - -# ╔═╡ 3e2750e7-220a-47b2-b445-eb3315734dec -md" -In this notebook, two different models will be used, each modelling the yield of grass in a grassland: - -- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ -- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ - -with output $W$ the grass yield, and $W_f$ and $\mu$ parameters. The table below show some typical values for the parameters together with their uncertainties: - -| | $\mu$ | $W_f$ | -|:----------- |:----------:|:-----------:| -| Logistic | 0.07$\pm$0.02 | 10.0$\pm$0.15 | -| Exponential | 0.02$\pm$0.01 | 10.0$\pm$0.15 | - -We will use an initial condition of $W_0 = 2.0$ for each and a simulation time of $100$ days. -" - -# ╔═╡ c874a08c-7d82-4631-b611-c598dcaded09 -md""" -We will illustrate how to compute the error bars, due to model parameter uncertainty, in conjunction with the output variable simulation for the logistic model. The same will be left as exercises below for the exponential model. - -**Important:** -- We will use consequently `_log` and `_exp` appended to relevant variables names in order to indicate their model origin **and** to prevent cell-disabling that occurs when using the same variables names in these Notebooks. -""" - -# ╔═╡ 9d3192bc-ee69-44a1-9273-06b8a55c60ef -md""" -## Uncertainty analysis of the logistic growth model - -We will start by modelling our system and simulating using the aforementioned parameters values and uncertainties, initial condition and timespan. -""" - -# ╔═╡ 8acb7ea2-54ef-428b-bb4f-364377d2c38d -md""" -Implementation of the system: -""" - -# ╔═╡ 460d98ef-2177-49bf-87a4-35412f4183ed -growth_mod_log = @reaction_network begin - μ*(1-W/Wf), W --> 2W -end - -# ╔═╡ 38e0f746-1d04-46ba-81cc-21522b9ce6a4 -md""" -Convert the *reaction model* to check that we work with the correct differential equation: -""" - -# ╔═╡ 2538bd6e-027f-47fe-813c-db0a02586d2c -osys_log = convert(ODESystem, growth_mod_log) - -# ╔═╡ 72b53ba6-dd05-4f2d-a65e-52783e76a1e9 -md""" -In order to use uncertainties in the parameter values, we will need to load the package `Measurements`: -""" - -# ╔═╡ 1a67db96-6973-446d-aa5b-253dd0008a73 -md""" -Setting initial conditions: -""" - -# ╔═╡ 048112b0-10f1-47c5-834f-c3851d3078e8 -u0_log = [:W => 2.0] - -# ╔═╡ 037cbf2b-dc0c-4b62-879b-aa702c964877 -md""" -Set the timespan for the simulation: -""" - -# ╔═╡ 40c1192c-bc76-4d3a-8144-dd76ba72fba2 -tspan = (0.0, 100.0) - -# ╔═╡ 0b5493eb-6ce6-43c1-9289-4c2e6f20d014 -md""" -In order to see the effect of the individual parameter uncertainties on the output variable, we will analyse this (only for didactical reasons) assuming different study cases: - -1. $\mu$ has a nominal value of $0.07$ and a standard deviation of $0.02$ while $W_f$ is exactly known and equal to $10.0$. -2. $W_f$ has a nominal value of $10.0$ and a standard deviation of $1.5$ while $\mu$ is exactly known and equal to $0.07$. -3. $\mu$ has a nominal value of $0.07$ and a standard deviation of $0.02$ and similarily $W_f$ has a nominal value of $10.0$ and a standard deviation of $1.5$. - -The last study case is the true case because in reality all uncertainties will contribute at once. -""" - -# ╔═╡ 5b9eaf9d-e84d-480f-8404-29ad3550d19b -md""" -!!! tip "Tip" - If `p` is a parameter and `σₚ` is its standard deviation, then we will define the uncertainty in the parameter `p` as `p ± σₚ` in the vector with parameters and uncertainties. The symbol `±` can be visualized by typing a **backslash** followed by the letters `pm` (**p**lus-**m**inus) and then the **TAB** key. -""" - -# ╔═╡ b4c76f21-9839-4db5-b952-43869d3a1efe -md""" -### Study case 1 -""" - -# ╔═╡ 6ab73fc5-7315-4ec6-a3ee-9b5c689ce82e -md""" -We initialize a vector `params1_uncert_log` with the parameter values and uncertainty (standard deviation) **only in the first parameter**: -""" - -# ╔═╡ b0f32f69-b6a2-4050-97e2-8018d07463d8 -params1_uncert_log = [:μ => 0.07±0.02, :Wf => 10.0] - -# ╔═╡ c1a0702c-6fa5-47d7-bd72-6bfcbdeba93f -md""" -We create the corresponding ODE problem and store it in `oprob1_uncert_log`: -""" - -# ╔═╡ 2fc91add-8468-4402-bcd8-42da5544f612 -oprob1_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, params1_uncert_log); - -# ╔═╡ 5fa951d3-23ee-4510-873c-8158df4f2faf -md""" -We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol1_uncert_log`: -""" - -# ╔═╡ d54635e8-1693-42bc-91f4-7e504a8661c7 -osol1_uncert_log = solve(oprob1_uncert_log, Tsit5(), saveat=2.0) - -# ╔═╡ 707c6023-49ae-4a13-a220-d827a76a352c -md""" -Plot the results (simulation of the output variable $W$ and uncertainty band): -""" - -# ╔═╡ 48e92497-f6b3-484c-bc5f-3ce69f4f1a91 -plot(osol1_uncert_log) - -# ╔═╡ 9b30b23b-6e92-49cf-bf93-e12a187fa2ba -md""" -When thinking back of the sensitivity of $W$ to the parameter $\mu$, we saw that the corresponding sensitivity function had a maximum around $t=33\;s$. Looking at the above plot with error bars, we can see that the largest error bars (largest uncertainty in the output variable) occurs at the time points where the sensitivity is strongest. -""" - -# ╔═╡ 86f8d937-c42a-47ff-a0f6-6cae9d637fcf -md""" -### Study case 2 -""" - -# ╔═╡ 0755ea1c-50a0-4c8a-9adc-be2d63948a10 -md""" -We initialize a vector `params2_uncert_log` with the parameter values and uncertainty (standard deviation) **only in the second parameter**: -""" - -# ╔═╡ 7befd387-f3a6-4b34-90b0-5f04b5429252 -params2_uncert_log = [:μ => 0.07, :Wf => 10.0±1.5] - -# ╔═╡ 9e969176-f197-482f-9e2e-8b6d89de8f03 -md""" -We create the corresponding ODE problem and store it in `oprob2_uncert_log`: -""" - -# ╔═╡ 50cc4ee1-6601-442c-bac7-4d23b07db87e -oprob2_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, params2_uncert_log); - -# ╔═╡ 6d69f64e-8e3a-46e5-bb2d-bfb6f55e129e -md""" -We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol2_uncert_log`: -""" - -# ╔═╡ ee548df5-1334-47dc-9258-a25062de3f67 -osol2_uncert_log = solve(oprob2_uncert_log, Tsit5(), saveat=2.0) - -# ╔═╡ afdb7450-e883-477b-b9a2-8e8585d16c40 -md""" -Plot the results (simulation of the output variable $W$ and uncertainty band): -""" - -# ╔═╡ 25bfbf46-69b1-47eb-a473-788aad066d4a -plot(osol2_uncert_log) - -# ╔═╡ b8896997-ddec-4d4a-a7c4-0e1008089d1b -md""" -When thinking back of the sensitivity of $W$ to the parameter $W_f$, we saw that the corresponding sensitivity function was strongest in the tail of the curve around the steady state value. Looking at the above plot with error bars, we can see that the largest error bars (largest uncertainty in the output variable) occurs at the tail of the curve where the sensitivity is strongest. -""" - -# ╔═╡ 5522a5c2-ff7e-4093-86d0-ffb879fcc6e8 -md""" -### Study case 3 -""" - -# ╔═╡ 5e3da6ca-656e-41ad-ba04-bf3d6b51939d -md""" -We initialize a vector `params_uncert_log` with the parameter values and uncertainty (standard deviation) **in all parameters**: -""" - -# ╔═╡ 7be29c69-7fa0-4f41-80c9-f69aa01e8ea4 -params_uncert_log = [:μ => 0.07±0.02, :Wf => 10.0±1.5] - -# ╔═╡ d48c378e-df1c-45f7-9c3a-c5e9640e25a5 -md""" -We create the corresponding ODE problem and store it in `oprob_uncert_log`: -""" - -# ╔═╡ f209f4fb-985c-4a9b-b4dd-d50cc3fae1cf -oprob_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, params_uncert_log); - -# ╔═╡ de96646b-b752-4f72-ac78-2a503478bf50 -md""" -We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert_log`: -""" - -# ╔═╡ 6b1a3c2a-0a88-41ad-8866-41a33da5cd90 -osol_uncert_log = solve(oprob_uncert_log, Tsit5(), saveat=2.0) - -# ╔═╡ 127ead82-2e2d-4c2e-9417-0490b6839514 -md""" -Plot the results (simulation of the output variable $W$ and uncertainty band): -""" - -# ╔═╡ a5c254ad-b629-4a9e-89e9-90460e84620c -plot(osol_uncert_log) - -# ╔═╡ c181d973-4fcc-4fbe-b37c-5f71dd33b0d2 -md""" -Now we see the combined effect of uncertainty in both parameters, $\mu$ and $W_f$. -""" - -# ╔═╡ e9f49fbc-d877-4db7-b320-a2db964ecaaa -md""" -## Exercise - Uncertainty analysis of the exponential growth model - -Perform an uncertainty analysis of the exponential growth model. Use the parameter uncertainties mentioned in the *Table* in the *Grass growth models* sections. -""" - -# ╔═╡ 5201995b-3298-4984-8839-dd25dc73a20f -md""" -A possible *reaction network object* for the exponential growth model can be implemented as follows: -""" - -# ╔═╡ 97bdfce1-a670-469a-b8a9-34e1d1964409 -growth_exp = @reaction_network begin - (μ*Wf, μ), 0 <--> W -end - -# ╔═╡ 52991cca-0365-48fb-b157-62a71c5573ee -md""" -The vector `u0_exp` with the initial condition is: -""" - -# ╔═╡ f7241453-755e-466c-a963-a504bc9ea446 -# u0_exp = missing - -# ╔═╡ 11b81610-90b1-4d71-aeba-0bf33709dbda -md""" -Initialize a vector `params_uncert_exp` with the parameter values and their uncertainties (standard deviation) **in all parameters**.\ -**Remark**: you can use the same variable and leave a single uncertainty if you want to see the effect of the uncertainty in only one parameter later on. -""" - -# ╔═╡ 1af01af7-f5ee-4029-94ff-418c2f9748f2 -# params_uncert_exp = missing # Uncomment and complete the instruction - -# ╔═╡ c25160d9-471a-414c-be99-10aa2efbb6d4 -md""" -Create the corresponding ODE problem and store it in `oprob_uncert_exp`: -""" - -# ╔═╡ 7fcec514-192b-4694-b149-14f5f87bae17 -# oprob_uncert_exp = missing; # Uncomment and complete the instruction - -# ╔═╡ bd120dbc-827a-499a-a11c-25423ec5f397 -md""" -Solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert_exp`: -""" - -# ╔═╡ 2dd58666-80ae-4346-ae15-34792eea2d25 -# osol_uncert_exp = missing # Uncomment and complete the instruction - -# ╔═╡ 8059e00b-8e9c-4c11-a484-bea2a0bf155e -md""" -Plot the results (simulation of the output variable $W$ and uncertainty band): -""" - -# ╔═╡ a89677f3-602b-4ef4-8f1a-ab203584f8df -# missing # Uncomment and complete the instruction - -# ╔═╡ 2a470290-bca7-459f-b4f8-a5ac9fc031ed -md""" -Draw your conclusions: -""" - -# ╔═╡ 1a3c8230-17ff-433e-9db5-807eb0dcb9a3 -md"- missing" - -# ╔═╡ Cell order: -# ╠═d2c4d230-0943-11ef-3aad-5719e74bb20e -# ╠═47790920-74d2-4f21-b0f2-45dc19d25156 -# ╠═8349306c-e98c-4221-9a1b-322fde3e18cb -# ╠═83f3d978-aefa-40c1-a647-b26e837aeed6 -# ╠═73d5f921-82e4-4160-a3c4-fc1935f4c58d -# ╠═4260699d-4db2-4ac7-b8c8-7bc43fbc1c59 -# ╟─241a8a65-c59f-44f1-be39-d5edd1321b49 -# ╟─0f518e36-bc96-4599-807e-728504e5ca7b -# ╟─da3cdc89-b911-4af9-9d4a-526301cba581 -# ╟─78afaded-5a19-4386-aa76-7974977ea354 -# ╟─eae11742-14c6-4b1b-a939-232710dfa10e -# ╟─e5ab4490-6fd4-4b51-bfa7-1366438efefc -# ╟─3e2750e7-220a-47b2-b445-eb3315734dec -# ╟─c874a08c-7d82-4631-b611-c598dcaded09 -# ╟─9d3192bc-ee69-44a1-9273-06b8a55c60ef -# ╟─8acb7ea2-54ef-428b-bb4f-364377d2c38d -# ╠═460d98ef-2177-49bf-87a4-35412f4183ed -# ╟─38e0f746-1d04-46ba-81cc-21522b9ce6a4 -# ╠═2538bd6e-027f-47fe-813c-db0a02586d2c -# ╟─72b53ba6-dd05-4f2d-a65e-52783e76a1e9 -# ╠═5185d0eb-7392-4775-9336-3e0f9e1449ce -# ╟─1a67db96-6973-446d-aa5b-253dd0008a73 -# ╠═048112b0-10f1-47c5-834f-c3851d3078e8 -# ╟─037cbf2b-dc0c-4b62-879b-aa702c964877 -# ╠═40c1192c-bc76-4d3a-8144-dd76ba72fba2 -# ╟─0b5493eb-6ce6-43c1-9289-4c2e6f20d014 -# ╟─5b9eaf9d-e84d-480f-8404-29ad3550d19b -# ╟─b4c76f21-9839-4db5-b952-43869d3a1efe -# ╟─6ab73fc5-7315-4ec6-a3ee-9b5c689ce82e -# ╠═b0f32f69-b6a2-4050-97e2-8018d07463d8 -# ╟─c1a0702c-6fa5-47d7-bd72-6bfcbdeba93f -# ╠═2fc91add-8468-4402-bcd8-42da5544f612 -# ╟─5fa951d3-23ee-4510-873c-8158df4f2faf -# ╠═d54635e8-1693-42bc-91f4-7e504a8661c7 -# ╟─707c6023-49ae-4a13-a220-d827a76a352c -# ╠═48e92497-f6b3-484c-bc5f-3ce69f4f1a91 -# ╟─9b30b23b-6e92-49cf-bf93-e12a187fa2ba -# ╟─86f8d937-c42a-47ff-a0f6-6cae9d637fcf -# ╟─0755ea1c-50a0-4c8a-9adc-be2d63948a10 -# ╠═7befd387-f3a6-4b34-90b0-5f04b5429252 -# ╟─9e969176-f197-482f-9e2e-8b6d89de8f03 -# ╠═50cc4ee1-6601-442c-bac7-4d23b07db87e -# ╟─6d69f64e-8e3a-46e5-bb2d-bfb6f55e129e -# ╠═ee548df5-1334-47dc-9258-a25062de3f67 -# ╟─afdb7450-e883-477b-b9a2-8e8585d16c40 -# ╠═25bfbf46-69b1-47eb-a473-788aad066d4a -# ╟─b8896997-ddec-4d4a-a7c4-0e1008089d1b -# ╟─5522a5c2-ff7e-4093-86d0-ffb879fcc6e8 -# ╟─5e3da6ca-656e-41ad-ba04-bf3d6b51939d -# ╠═7be29c69-7fa0-4f41-80c9-f69aa01e8ea4 -# ╟─d48c378e-df1c-45f7-9c3a-c5e9640e25a5 -# ╠═f209f4fb-985c-4a9b-b4dd-d50cc3fae1cf -# ╟─de96646b-b752-4f72-ac78-2a503478bf50 -# ╠═6b1a3c2a-0a88-41ad-8866-41a33da5cd90 -# ╟─127ead82-2e2d-4c2e-9417-0490b6839514 -# ╠═a5c254ad-b629-4a9e-89e9-90460e84620c -# ╟─c181d973-4fcc-4fbe-b37c-5f71dd33b0d2 -# ╟─e9f49fbc-d877-4db7-b320-a2db964ecaaa -# ╟─5201995b-3298-4984-8839-dd25dc73a20f -# ╠═97bdfce1-a670-469a-b8a9-34e1d1964409 -# ╟─52991cca-0365-48fb-b157-62a71c5573ee -# ╠═f7241453-755e-466c-a963-a504bc9ea446 -# ╟─11b81610-90b1-4d71-aeba-0bf33709dbda -# ╠═1af01af7-f5ee-4029-94ff-418c2f9748f2 -# ╟─c25160d9-471a-414c-be99-10aa2efbb6d4 -# ╠═7fcec514-192b-4694-b149-14f5f87bae17 -# ╟─bd120dbc-827a-499a-a11c-25423ec5f397 -# ╠═2dd58666-80ae-4346-ae15-34792eea2d25 -# ╟─8059e00b-8e9c-4c11-a484-bea2a0bf155e -# ╠═a89677f3-602b-4ef4-8f1a-ab203584f8df -# ╟─2a470290-bca7-459f-b4f8-a5ac9fc031ed -# ╠═1a3c8230-17ff-433e-9db5-807eb0dcb9a3 From 2b5746ce5bd49264a3b5a216be1107b202a43d81 Mon Sep 17 00:00:00 2001 From: jarjarbinks02 Date: Thu, 13 Aug 2026 14:09:02 +0200 Subject: [PATCH 2/5] adding practical 2 --- src/exercises/exercises.md | 42 + .../ode_model_XTRA_anaerobic_fermentation.jl | 467 ++++++++++ .../ode_model_XTRA_fermenter_firstorder.jl | 199 +++++ .../ode_model_XTRA_soil_cont_plant_uptake.jl | 189 ++++ .../ode_model_XTRA_water_evap_infil.jl | 240 +++++ src/exercises/ode_model_birth_death.jl | 258 ++++++ src/exercises/ode_model_catalyst_intro.jl | 786 ++++++++++++++++ src/exercises/ode_model_fermenter_monod.jl | 470 ++++++++++ src/exercises/ode_model_infection.jl | 845 ++++++++++++++++++ src/exercises/ode_model_mtk_intro.jl | 4 +- 10 files changed, 3499 insertions(+), 1 deletion(-) create mode 100644 src/exercises/exercises.md create mode 100644 src/exercises/ode_model_XTRA_anaerobic_fermentation.jl create mode 100644 src/exercises/ode_model_XTRA_fermenter_firstorder.jl create mode 100644 src/exercises/ode_model_XTRA_soil_cont_plant_uptake.jl create mode 100644 src/exercises/ode_model_XTRA_water_evap_infil.jl create mode 100644 src/exercises/ode_model_birth_death.jl create mode 100644 src/exercises/ode_model_catalyst_intro.jl create mode 100644 src/exercises/ode_model_fermenter_monod.jl create mode 100644 src/exercises/ode_model_infection.jl diff --git a/src/exercises/exercises.md b/src/exercises/exercises.md new file mode 100644 index 00000000..d9f7a750 --- /dev/null +++ b/src/exercises/exercises.md @@ -0,0 +1,42 @@ +--- +title: "Introduction" +tags: ["exercises"] +order: 1 +layout: "md.jlmd" +--- + + + +# Exercises description + +Here are renders of the exercises, see all pages on the left. + +To download all exercises: see Ufora. + +We have annotated the exercises with either a number or an extra prefix. + +> 1. XYZ + +Will be covered in exercise lession 1. + +> EXTRA. XYZ + +Additional exercises that will not be covered in the guided exercises. + +# Notes on the dependencies + +If you insist on downloading the exercises from this website, note that because we are rendering the notebooks here, we make use of our a specific environment. You will need to update this on your system. Look out for the cell with: +```julia +using Pkg +Pkg.activate("../../pluto-deployment-environment") +``` +Change this to the your current folder so that the Project and Manifest files are generated there: +```julia +using Pkg +Pkg.activate(".") +``` \ No newline at end of file diff --git a/src/exercises/ode_model_XTRA_anaerobic_fermentation.jl b/src/exercises/ode_model_XTRA_anaerobic_fermentation.jl new file mode 100644 index 00000000..dec38649 --- /dev/null +++ b/src/exercises/ode_model_XTRA_anaerobic_fermentation.jl @@ -0,0 +1,467 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "14" +#> title = "2. ODE_model_Xtra_anaerobic_fermentation" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Extra exercise on anaerobic fermentation" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 2552c020-1e29-451e-9f59-c4bde047faad +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 84e21b44-a1b0-11ef-014d-c58a169e3de3 +using Markdown, InteractiveUtils + +# ╔═╡ 896b4151-e26b-40ee-bfb9-c56dfc4e7048 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ aaf6da21-60cc-478c-b447-3f33aa375240 +using OrdinaryDiffEq, Catalyst + +# ╔═╡ bb10266a-1f6c-4fda-b276-6a9cf3a86e90 +md""" +# Exercise - Anaerobic fermentation +""" + +# ╔═╡ cce70a4d-0911-48e1-9cc0-2a69fda6e882 +md""" +![](https://users.ugent.be/~gvhaelew/fig/anaerobic_fermentation.png) +""" + +# ╔═╡ c3f85244-e0a8-4808-aa2d-15ab6bbb1b26 +md""" +## Part 1 +""" + +# ╔═╡ 2e7c2eab-a3bc-4574-a0fd-0a45b59b803b +md""" +An operator at a beverage factory would like to model the anaerobic fermentation that occurs in one of his reactors. After a literature review, he finds that sucrose ($S$) is converted to ethanol ($E$) via glucose ($G$) under the action of an enzyme called invertase ($I$) from yeast using the following reaction stoichiometry (all components have the unit $mol\;L^{-1}$): + +$$C_{12}H_{22}O_{11} + I \xrightarrow{r_1} 2C_{6}H_{12}O_{6} + I$$ + +$$C_{6}H_{12}O_{6} \xrightarrow{r_2} 2C_{2}H_{5}OH + 2CO_2$$ + +The operator knows that both reactions are carried out in isothermal conditions in a reactor with volume $V$ $[L]$. + +The operator knows from literature that the reaction rate $r_1$ is first-order in both sucrose and invertase and with specific reaction rate $k_1$. The reaction rate $r_2$ is second-order with respect to glucose with specific reaction rate $k_2$. Additionally, the reaction is inhibited by ethanol itself according to $\cfrac{K}{E+K}$ where $K$ represents the ethanol concentration at which $r_2$ achieves half of its maximum reaction rate. + +The initial concentrations and the parameter values are summarised in the following tables: + +| $S_0$ | $I_0$ | $G_0$ | $E_0$ | $CO_{2,0}$ | +|:---------:|:---------:|:---------:|:---------:|:----------:| +| $0.04$ | $0.02$ | $0.00$ | $0.01$ | $0.00$ | + +| $k_1$ | $k_2$ | $K$ | +|:---------:|:---------:|:---------:| +| $0.40$ | $0.65$ | $0.50$ | + +""" + +# ╔═╡ 35f2015d-9c28-4bc8-83cd-c185757db0dc +md" +#### Implementation of the system +" + +# ╔═╡ bfd7fc6f-57f6-40d8-8618-d514d1e5d9ad +md""" +Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of $S$, $I$, $G$, $E$ and $CO_2$ during $1440\;min$ ($=24\;h$). Name it `anaerobic_fermentation1`. + +Tips: +- For the reaction with reaction rate $r_2$, in order to have a second-order reaction with respect to glucose, you need to double the stoichiometric coefficients, i.e., you reaction should be $2G \rightarrow 4E + 4CO_2$. +- For the inhibition factor $\cfrac{K}{E+K}$ you can use the function [`mmr(..., ..., ...)`](https://docs.sciml.ai/Catalyst/stable/api/#Catalyst.mmr). +""" + +# ╔═╡ 53c32175-4298-4450-ae85-132ea0cd6a9b +# Uncomment and complete the instruction +# anaerobic_fermentation1 = @reaction_network begin +# @species missing +# missing +# missing +# end + +# ╔═╡ 485c2b25-f281-4ee9-bcb8-7ca010205930 +md""" +Check out the species. +""" + +# ╔═╡ 86200a5e-55ce-4d58-87ba-fe30f65c9120 +missing + +# ╔═╡ 579318a9-bad9-4392-9420-3f5474ccfff8 +md""" +Convert the system to a symbolic differential equation model and inspect your differential equations. +""" + +# ╔═╡ bc56d0fb-db19-4f63-9f34-617308da6c8a +osys1 = missing + +# ╔═╡ 5983b9d3-5119-47ac-9a0c-8a2755ad23e6 +md""" +Initialize a vector `u01` with the initial conditions: +""" + +# ╔═╡ c822fcbc-e8d3-4952-93bc-38a41f1786a7 +u01 = missing + +# ╔═╡ eb7fda8e-d1db-4bcc-91d6-964a6dcbbe90 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ 124571f0-fe33-44a2-9ab4-f6a418ac4f51 +tspan1 = missing + +# ╔═╡ 703d2eb0-8504-496f-9abe-8c6911a0fdbc +md""" +Initialize a vector `parms1` with the parameter values: +""" + +# ╔═╡ 9dd45f30-9076-45d5-827b-02ae2d73ef97 +parms1 = missing + +# ╔═╡ 64f85604-9562-4a97-bedd-ea5d28ada096 +md""" +Create the ODE problem and store it in `oprob1`: +""" + +# ╔═╡ 04816732-3419-40e3-a927-d6e102949553 +oprob1 = missing; + +# ╔═╡ 9a517512-a7c0-4a92-a104-b19a8bf786c7 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol1`: +""" + +# ╔═╡ 69f9ce3a-0422-499a-8492-ea70de43d587 +osol1 = missing + +# ╔═╡ f1c134c9-9127-4ed4-84a9-e1d218a82295 +md""" +Plot the results. Use a line width of 2 (`linewidth=...`). +""" + +# ╔═╡ e8eeefd3-f83a-4154-945d-568ac629cd94 +missing + +# ╔═╡ 622f65d3-0592-419f-8101-bc6f8b8ea5ed +md""" +Interprete the results. Try to come up with an answer to the following questions: +""" + +# ╔═╡ ad1347a2-9fbf-4681-b82b-4643d7337ed9 +md""" +!!! question + 1. Why is the concentration of invertase ($I$) constant, and the concentration of sucrose ($S$) becoming zero? +""" + +# ╔═╡ 39e96a71-7483-4c85-b44d-76cafc3b22dc +md"- Answer: missing" + +# ╔═╡ 192ec8c6-ff60-4584-91f9-d257399bac76 +md""" +!!! question + 2. Try to explain the peak in the glucose ($G$) concentration. +""" + +# ╔═╡ e2681cdb-581d-41d7-93c6-b137ef54c083 +md"- Answer: missing" + +# ╔═╡ 9f5bf914-7ec6-48ba-8fed-2087f42b37d8 +md""" +!!! question + 3. Why is the difference in ethanol ($E$) and $CO_2$ concentration constant? +""" + +# ╔═╡ e50013b7-be37-4fa9-bdd6-2a902443f222 +md"- Answer: missing" + +# ╔═╡ 939716cd-08c9-44a0-9217-146f9e312d35 +md""" +## Part 2 +""" + +# ╔═╡ 194a596b-22de-412e-8ead-94a8c02c98c6 +md""" +Additionally, sucrose and glucose are added at a flow rate $Q_{in},[L\; min^{-1}]$ and respective concentrations $S_{in}$ and $G_{in}$. The same flow rate is removed from the reactor but the invertase $I$ stays in the reactor. Now the volume $V$ of the reactor will matter. Furthermore, the invertase enzyme degrades at a rate $d=0.003\;min^{-1}$. + +The additional parameter values are summarised in the following table: + +| $Q_{in}$ | $V$ | $S_{in}$ | $G_{in}$ | $d$ | +|:---------:|:---------:|:---------:|:---------:|:--------:| +| $1.00$ | $100$ | $0.12$ | $0.05$ | $0.003$ | +""" + +# ╔═╡ 76b1d8ba-cc55-48f7-bee4-09f899582e62 +md""" +Make a copy of the content of the previous *reaction network object* and complement it with the new information. Name it `anaerobic_fermentation2`. +""" + +# ╔═╡ 4165677e-a483-404c-a8c5-6cd85e5a8090 +# Uncomment and complete the instruction +# anaerobic_fermentation2 = @reaction_network begin +# @species missing +# @parameters missing +# missing +# ... +# missing +# end + +# ╔═╡ 3da7ee15-6d30-42bb-be03-6f9129febc60 +md""" +Convert the system to a symbolic differential equation model and inspect your differential equations. +""" + +# ╔═╡ 58b8545f-1699-45b4-91c0-86722668a428 +osys2 = missing + +# ╔═╡ f6d58bac-7256-4b57-836d-6d243bf292c0 +md""" +Make an exact copy of `u01` and rename it to `u02` with the initial conditions: +""" + +# ╔═╡ 75eb088a-33fd-47d7-892a-da934ec9896a +u02 = missing + +# ╔═╡ b4c4fc78-b6e4-40db-9948-6be83ee57d87 +md""" +Make an exact copy of `tspan1` and rename it to `tspan2`: +""" + +# ╔═╡ 4a0e9abf-99e7-4fe6-82ae-aa958ab51dd0 +tspan2 = missing + +# ╔═╡ 0ec39752-6654-452b-98db-eadd8c02b27f +md""" +Make a copy of `parms1`, rename it to `parms2` and supplement it with the new parameter values: +""" + +# ╔═╡ 3aea0dbe-1cb3-4b19-bb38-53e58db153dd +parms2 = missing + +# ╔═╡ 49984e8e-a194-464a-994f-501342800026 +md""" +Create the ODE problem and store it in `oprob2`: +""" + +# ╔═╡ 18a3513f-8ecc-4c2f-bb39-5d0cb39a3d92 +oprob2 = missing; + +# ╔═╡ 5318708e-3410-4675-8162-827c0c6e039c +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol2`: +""" + +# ╔═╡ e5476343-8376-4a9a-8286-f69e0d023939 +osol2 = missing + +# ╔═╡ 29a3cd1b-fd8f-4e5c-9ec7-a69fe328d824 +md""" +Plot the results. Use a line width of 2 (`linewidth=...`). If you only want to see the curves for, e.g., $E$, $S$ and $G$, you can use the option `idxs=[:E, :S, :G]` in the `plot` command. +""" + +# ╔═╡ 0c179e6b-bcaf-4e89-8d04-59f24b456127 +missing + +# ╔═╡ e596f49e-1b3a-422e-be9a-f72133f042d5 +md""" +Interprete the results. +""" + +# ╔═╡ 3505c10d-270c-49fc-a83a-3753974d4d5a +md"- Answer: missing" + +# ╔═╡ 5f865fec-09d5-4358-be57-0d568740968d +md""" +Check out the last concentrations (at the end time) for each of the species. + +Tips: +- You can see the last values of all species with `osol2.u[end]` +- If later you need all last values separately, you can access the last value of $S$ with `osol2[:S][end]` and then you can put everything on one line separating the values with comma's. +""" + +# ╔═╡ acc8acb3-f014-4108-80d7-bff893ce07d1 +missing + +# ╔═╡ 053cca09-0e31-44c0-89db-26a581816744 +# osol2[:S][end], ..., ..., ..., ...) + +# ╔═╡ 83cfd592-896d-42f5-ae2a-b1d0a9f14aac +md""" +Create a vector named `u_guess` in the same way as `u02`, but now with the end values of the species. +""" + +# ╔═╡ f5a8f97b-7cbb-4eb4-b5ff-82c184b7de85 +u_guess2 = missing + +# ╔═╡ ce67b1f7-a82d-4fa3-a22b-f8ee3d2f4af0 +md""" +Calculate the steady-state values of the species: +""" + +# ╔═╡ 3a014879-883b-4cab-a7ac-0c3a708f5da6 +# Sw2, Iw2, Gw2, Ew2, CO2w2 = missing + +# ╔═╡ d7bde85c-05f1-4794-9e91-c4e5e120f876 +md""" +Check ou the steady states: +""" + +# ╔═╡ 876683d7-b3cd-4a06-94b2-d38a1d4a34b6 +missing + +# ╔═╡ 55d303ec-29cb-4e8e-ab52-808d881ae0f2 +md""" +## Part 3 +""" + +# ╔═╡ e0ef008c-9276-41e5-a85a-649a276c4666 +md""" +We now want to keep a relatively high production of ethanol. Therefore, if the invertase decreases to $0.008$, then the invertase is instantaneously renewed to the initial concentration of $0.02$. Apply the change in the invertase concentration using a continuous event. +""" + +# ╔═╡ 81db6771-ee45-42c8-ab23-fe1960370b86 +md""" +Create the correct condition. +""" + +# ╔═╡ 166a21f0-46a5-4d7a-9f54-788d7a51a487 +condition3 = missing + +# ╔═╡ 5e6c5431-6fad-4272-a539-5e3af83fa226 +md""" +Include the condition into the *reaction network model*. +""" + +# ╔═╡ 98be4b39-4097-4cfa-886e-171558da0df6 +# @named anaerobic_fermentation3_c = missing + +# ╔═╡ 66568f6a-b874-42cb-834d-ab9d4df815d1 +md""" +Complete the *reaction network model*. +""" + +# ╔═╡ 66441227-c202-48da-a6ed-58e25ffde3ce +anaerobic_fermentation3_c_com = missing + +# ╔═╡ 71b4bd6e-b906-4d88-a9a3-18700528ea12 +md""" +Create a new ODE problem. +""" + +# ╔═╡ 16d0d671-464c-4de3-bd73-593e1a73168b +oprob3 = missing; + +# ╔═╡ 66015536-5f23-45b5-8d57-f1d27d604f8c +md""" +Solve the new ODE problem. Make a `deepcopy`, use `Tsit5()` and `saveat=0.5`. +""" + +# ╔═╡ 7f0777a2-6eb9-4c6d-9fb7-25a1f32f291d +osol3 = missing + +# ╔═╡ 124d578f-42ea-488e-af65-312793f104ca +md""" +Plot the results. +""" + +# ╔═╡ 9ad3aff4-76ea-452f-bd7b-fc379725df70 +missing + +# ╔═╡ e07e1667-2f2c-4086-9818-90dff54296de +md""" +Interprete the results. +""" + +# ╔═╡ 91af34c0-9b38-4a62-8387-5e3c60b4b145 +md"- Answer: missing" + +# ╔═╡ Cell order: +# ╟─bb10266a-1f6c-4fda-b276-6a9cf3a86e90 +# ╠═84e21b44-a1b0-11ef-014d-c58a169e3de3 +# ╠═2552c020-1e29-451e-9f59-c4bde047faad +# ╠═896b4151-e26b-40ee-bfb9-c56dfc4e7048 +# ╠═aaf6da21-60cc-478c-b447-3f33aa375240 +# ╟─cce70a4d-0911-48e1-9cc0-2a69fda6e882 +# ╟─c3f85244-e0a8-4808-aa2d-15ab6bbb1b26 +# ╟─2e7c2eab-a3bc-4574-a0fd-0a45b59b803b +# ╟─35f2015d-9c28-4bc8-83cd-c185757db0dc +# ╟─bfd7fc6f-57f6-40d8-8618-d514d1e5d9ad +# ╠═53c32175-4298-4450-ae85-132ea0cd6a9b +# ╟─485c2b25-f281-4ee9-bcb8-7ca010205930 +# ╠═86200a5e-55ce-4d58-87ba-fe30f65c9120 +# ╟─579318a9-bad9-4392-9420-3f5474ccfff8 +# ╠═bc56d0fb-db19-4f63-9f34-617308da6c8a +# ╟─5983b9d3-5119-47ac-9a0c-8a2755ad23e6 +# ╠═c822fcbc-e8d3-4952-93bc-38a41f1786a7 +# ╟─eb7fda8e-d1db-4bcc-91d6-964a6dcbbe90 +# ╠═124571f0-fe33-44a2-9ab4-f6a418ac4f51 +# ╟─703d2eb0-8504-496f-9abe-8c6911a0fdbc +# ╠═9dd45f30-9076-45d5-827b-02ae2d73ef97 +# ╟─64f85604-9562-4a97-bedd-ea5d28ada096 +# ╠═04816732-3419-40e3-a927-d6e102949553 +# ╟─9a517512-a7c0-4a92-a104-b19a8bf786c7 +# ╠═69f9ce3a-0422-499a-8492-ea70de43d587 +# ╟─f1c134c9-9127-4ed4-84a9-e1d218a82295 +# ╠═e8eeefd3-f83a-4154-945d-568ac629cd94 +# ╟─622f65d3-0592-419f-8101-bc6f8b8ea5ed +# ╟─ad1347a2-9fbf-4681-b82b-4643d7337ed9 +# ╠═39e96a71-7483-4c85-b44d-76cafc3b22dc +# ╟─192ec8c6-ff60-4584-91f9-d257399bac76 +# ╠═e2681cdb-581d-41d7-93c6-b137ef54c083 +# ╟─9f5bf914-7ec6-48ba-8fed-2087f42b37d8 +# ╠═e50013b7-be37-4fa9-bdd6-2a902443f222 +# ╟─939716cd-08c9-44a0-9217-146f9e312d35 +# ╟─194a596b-22de-412e-8ead-94a8c02c98c6 +# ╟─76b1d8ba-cc55-48f7-bee4-09f899582e62 +# ╠═4165677e-a483-404c-a8c5-6cd85e5a8090 +# ╟─3da7ee15-6d30-42bb-be03-6f9129febc60 +# ╠═58b8545f-1699-45b4-91c0-86722668a428 +# ╟─f6d58bac-7256-4b57-836d-6d243bf292c0 +# ╠═75eb088a-33fd-47d7-892a-da934ec9896a +# ╟─b4c4fc78-b6e4-40db-9948-6be83ee57d87 +# ╠═4a0e9abf-99e7-4fe6-82ae-aa958ab51dd0 +# ╟─0ec39752-6654-452b-98db-eadd8c02b27f +# ╠═3aea0dbe-1cb3-4b19-bb38-53e58db153dd +# ╟─49984e8e-a194-464a-994f-501342800026 +# ╠═18a3513f-8ecc-4c2f-bb39-5d0cb39a3d92 +# ╟─5318708e-3410-4675-8162-827c0c6e039c +# ╠═e5476343-8376-4a9a-8286-f69e0d023939 +# ╟─29a3cd1b-fd8f-4e5c-9ec7-a69fe328d824 +# ╠═0c179e6b-bcaf-4e89-8d04-59f24b456127 +# ╟─e596f49e-1b3a-422e-be9a-f72133f042d5 +# ╠═3505c10d-270c-49fc-a83a-3753974d4d5a +# ╟─5f865fec-09d5-4358-be57-0d568740968d +# ╠═acc8acb3-f014-4108-80d7-bff893ce07d1 +# ╠═053cca09-0e31-44c0-89db-26a581816744 +# ╟─83cfd592-896d-42f5-ae2a-b1d0a9f14aac +# ╠═f5a8f97b-7cbb-4eb4-b5ff-82c184b7de85 +# ╟─ce67b1f7-a82d-4fa3-a22b-f8ee3d2f4af0 +# ╠═3a014879-883b-4cab-a7ac-0c3a708f5da6 +# ╟─d7bde85c-05f1-4794-9e91-c4e5e120f876 +# ╠═876683d7-b3cd-4a06-94b2-d38a1d4a34b6 +# ╟─55d303ec-29cb-4e8e-ab52-808d881ae0f2 +# ╟─e0ef008c-9276-41e5-a85a-649a276c4666 +# ╟─81db6771-ee45-42c8-ab23-fe1960370b86 +# ╠═166a21f0-46a5-4d7a-9f54-788d7a51a487 +# ╟─5e6c5431-6fad-4272-a539-5e3af83fa226 +# ╠═98be4b39-4097-4cfa-886e-171558da0df6 +# ╟─66568f6a-b874-42cb-834d-ab9d4df815d1 +# ╠═66441227-c202-48da-a6ed-58e25ffde3ce +# ╟─71b4bd6e-b906-4d88-a9a3-18700528ea12 +# ╠═16d0d671-464c-4de3-bd73-593e1a73168b +# ╟─66015536-5f23-45b5-8d57-f1d27d604f8c +# ╠═7f0777a2-6eb9-4c6d-9fb7-25a1f32f291d +# ╟─124d578f-42ea-488e-af65-312793f104ca +# ╠═9ad3aff4-76ea-452f-bd7b-fc379725df70 +# ╟─e07e1667-2f2c-4086-9818-90dff54296de +# ╠═91af34c0-9b38-4a62-8387-5e3c60b4b145 diff --git a/src/exercises/ode_model_XTRA_fermenter_firstorder.jl b/src/exercises/ode_model_XTRA_fermenter_firstorder.jl new file mode 100644 index 00000000..c8943e77 --- /dev/null +++ b/src/exercises/ode_model_XTRA_fermenter_firstorder.jl @@ -0,0 +1,199 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "13" +#> title = "2. ODE_model_Xtra_fermenter_firstorder" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Extra exercise on a fermenter with first-order kinetics" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ e99680dc-73af-40aa-bf57-a06d3a7372be +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 2e58f4ae-f711-11ee-2598-7f3a6f2e2013 +using Markdown, InteractiveUtils + +# ╔═╡ b33193b3-220e-49fc-acd1-6d1bec3caf88 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ e66518ee-b6f6-4cca-a224-30e01cffddbe +using OrdinaryDiffEq, Catalyst + +# ╔═╡ 7856d878-8586-4cfd-9cf6-d61234450e41 +md""" +# Exercise: Fermenter - First order kinetics +""" + +# ╔═╡ 8500c35e-6bc3-4900-81bf-7705ddd61532 +md""" +In a fermenter reactor biomass $X$ grows on substrate $S$. The reactor is fed with a inlet flow rate $Q_{in}$ [$L/h$], which consist of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. Inside the reactor, biomass, with a concentration of $X$ [$g/L$], is produced through first-order kinetics (first-order in $S$): + +$$\begin{eqnarray*} +S \xrightarrow[\quad\quad]{\beta} Y \, X +\end{eqnarray*}$$ + +with $\beta$ [$h^{-1}$] the reaction rate constant, and $Y$ [$gX/gS$] the yield coefficient which is defined here by the amount of produced biomass by consumption of one unit of substrate. The reaction rate is actually: + +$$\begin{eqnarray*} +r = \beta \, S +\end{eqnarray*}$$ + +Futhermore, the reactor is drained with an outlet flow $Q$ [$L/h$], which consist of the current concentrations of substrate $S$ [$g/L$] and biomass $X$ [$g/L$] inside the reactor. The volume $V$ [$L$] of the reactor content is kept constant by setting $Q_{in} = Q$. +""" + +# ╔═╡ f1350528-07a5-4860-ad2d-627588186abc +md""" +Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of substrate $S$ and biomass $X$ with time. Name it `fermenter_firstorder`. +""" + + +# ╔═╡ 331a34f4-89d4-4193-896c-c14ab0bf04e7 +# Uncomment and complete the instruction +# fermenter_firstorder = @reaction_network begin +# missing # Y*X is created from one S at a rate β +# missing # S is created at a rate Q/V*Sin +# missing # S and X are degraded at a rate Q/V*S +# end + +# ╔═╡ 55746566-2d46-4475-851a-02b7fad87a1a +md""" +Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. +""" + +# ╔═╡ ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac +osys = missing + +# ╔═╡ 67117a27-dcea-4b43-b962-9ad9fd07f4f4 +md""" +The parameter values are $\beta = 0.98$, $Y = 0.80$, $Q = 2.0$, $V = 40.0$ and $S_{in} = 2.2\;g/L$. With the latter values, the fermenter reactor is in steady state operation with concentrations for substrate $S = 0.1068\;g/L$ and biomass $X = 1.6746\;g/L$. Suppose that at timepoint $t = 20\;h$, the concentration of substrate in the inlet flow (cf. $S_{in}$) is suddently increased to $3.4\;g/L$. Simulate the evolution of $S$ and $X$ during 120 hours. +""" + + +# ╔═╡ d13e6e38-037e-4812-85e9-2c18bed360f6 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ 4b556cf0-8fad-434d-be56-dc1848d898ae +u0 = missing + +# ╔═╡ ea55d648-7575-43c3-a385-5f4979996ef2 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ 1365c12e-e662-4858-983b-02ba94cd9f0d +tspan = missing + +# ╔═╡ 3941bd60-a83c-4f72-84b3-28e28cb845d0 +md""" +Initialize a vector `parms` with the parameter values: +""" + +# ╔═╡ d6c1316a-cf96-43d1-854a-f25925cf4a55 +parms = missing + +# ╔═╡ eeb8ec6e-154e-4fe9-8b5b-edbe71914985 +md""" +Create the *condition* that contains the timepoint for the sudden change in $S_{in}$. Store it in `condition`: +""" + +# ╔═╡ 7ca8efaa-97b6-46f2-b4d3-6ca8aa97dda7 +condition = missing + +# ╔═╡ d864bfc3-05b2-483b-9a55-da026931703f +md""" +Make a new *reaction system* where the discrete event is included. Name it `fermenter_firstorder_c`. +""" + +# ╔═╡ d758b918-d184-473f-9b9e-ed6da7b0f088 +# @named fermenter_firstorder_c = missing + +# ╔═╡ ef596d8c-efdc-4b5c-9584-335d799acfe8 +md""" +Complete the new *reaction system*. Name it `fermenter_firstorder_c_com`. +""" + +# ╔═╡ c07b3121-4c27-454c-b9e4-7dfa27371ebd +fermenter_firstorder_c_com = missing + +# ╔═╡ c6e81f41-a244-48c9-9d18-b3b9e0984fbb +md""" +Create the ODE problem and store it in `oprob`: +""" + +# ╔═╡ ed56f8d6-2260-4829-9190-69b60b7d7599 +oprob = missing; + +# ╔═╡ 8b73b16b-7f7d-4d2e-a1c2-7e1adf2336e9 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol`: +""" + +# ╔═╡ 433db8d1-f038-4e7b-9133-90bfeccabd07 +osol = missing + +# ╔═╡ fc824241-f718-46a8-b50b-a680470c062b +md""" +Plot the results: +""" + +# ╔═╡ e2609226-5f2f-4802-89e3-0efeac740081 +missing + +# ╔═╡ 41b9d4bc-280b-4f05-945b-981c9fcddf82 +md""" +!!! questions + Interpret the results. Ask yourself the following questions: + 1. Can you clearly see the effect of the increase in $S_{in}$? + 2. Can you argue, by means of reasoning or by determining and analyzing the operating point, why the increase of $X$ is larger than the increase of $S$? +""" + +# ╔═╡ ef932b49-8c77-48b5-86ac-2a4fee404cab +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ Cell order: +# ╠═2e58f4ae-f711-11ee-2598-7f3a6f2e2013 +# ╠═e99680dc-73af-40aa-bf57-a06d3a7372be +# ╠═b33193b3-220e-49fc-acd1-6d1bec3caf88 +# ╠═e66518ee-b6f6-4cca-a224-30e01cffddbe +# ╟─7856d878-8586-4cfd-9cf6-d61234450e41 +# ╟─8500c35e-6bc3-4900-81bf-7705ddd61532 +# ╟─f1350528-07a5-4860-ad2d-627588186abc +# ╠═331a34f4-89d4-4193-896c-c14ab0bf04e7 +# ╟─55746566-2d46-4475-851a-02b7fad87a1a +# ╠═ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac +# ╟─67117a27-dcea-4b43-b962-9ad9fd07f4f4 +# ╟─d13e6e38-037e-4812-85e9-2c18bed360f6 +# ╠═4b556cf0-8fad-434d-be56-dc1848d898ae +# ╟─ea55d648-7575-43c3-a385-5f4979996ef2 +# ╠═1365c12e-e662-4858-983b-02ba94cd9f0d +# ╟─3941bd60-a83c-4f72-84b3-28e28cb845d0 +# ╠═d6c1316a-cf96-43d1-854a-f25925cf4a55 +# ╟─eeb8ec6e-154e-4fe9-8b5b-edbe71914985 +# ╠═7ca8efaa-97b6-46f2-b4d3-6ca8aa97dda7 +# ╟─d864bfc3-05b2-483b-9a55-da026931703f +# ╠═d758b918-d184-473f-9b9e-ed6da7b0f088 +# ╟─ef596d8c-efdc-4b5c-9584-335d799acfe8 +# ╠═c07b3121-4c27-454c-b9e4-7dfa27371ebd +# ╟─c6e81f41-a244-48c9-9d18-b3b9e0984fbb +# ╠═ed56f8d6-2260-4829-9190-69b60b7d7599 +# ╟─8b73b16b-7f7d-4d2e-a1c2-7e1adf2336e9 +# ╠═433db8d1-f038-4e7b-9133-90bfeccabd07 +# ╟─fc824241-f718-46a8-b50b-a680470c062b +# ╠═e2609226-5f2f-4802-89e3-0efeac740081 +# ╠═41b9d4bc-280b-4f05-945b-981c9fcddf82 +# ╠═ef932b49-8c77-48b5-86ac-2a4fee404cab diff --git a/src/exercises/ode_model_XTRA_soil_cont_plant_uptake.jl b/src/exercises/ode_model_XTRA_soil_cont_plant_uptake.jl new file mode 100644 index 00000000..0b688d7f --- /dev/null +++ b/src/exercises/ode_model_XTRA_soil_cont_plant_uptake.jl @@ -0,0 +1,189 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "15" +#> title = "2. ODE_model_Xtra_soil_contamination" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Extra exercise on soil contamination with plant uptake" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 6c7911b4-fec2-4139-8b54-36a4fb5916a0 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 121df656-f57a-11ee-140e-dfb61e112370 +using Markdown, InteractiveUtils + +# ╔═╡ 895c1016-a8ce-43ae-8094-5d6ea75a6053 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 28e9e96c-fce6-4507-97c9-37337a0731bc +using OrdinaryDiffEq, Catalyst + +# ╔═╡ 2f0c3dd4-9429-4f5b-9150-011970b003f4 +md""" +# Exercise: Soil Contamination with Plant Uptake +""" + +# ╔═╡ 2a8fdc6b-2372-4a97-a094-d218a22274d8 +md""" +![](https://users.ugent.be/~gvhaelew/fig/soil_plant_cont_model.png) +""" + +# ╔═╡ c0c35547-9eed-428b-b513-4b5166decb7e +md""" +The following system of differential equations models the decay of a pollutant in soil and its uptake by plants. The variable $C(t)$ (in $mg/kg$) is the concentration of the pollutant in the soil and $P(t)$ (in $mg/kg$) is the concentration of the pollutant in the plants at time $t$. + +$$\begin{align} +\cfrac{dC}{dt} &= r-k_1 C(t)-k_2 C(t) P(t)\\ +\cfrac{dP}{dt} &= k_2 C(t) P(t)-k_3 P(t) +\end{align}$$ + +The interpratation of the parameters is the following: + +- $r$ represents the rate at which the pollutant enters the soil from external sources. +- $k_1$ is the natural degradation rate of the pollutant in the soil. +- $k_2$ is the uptake coefficient, representing the rate at which plants absorb pollutant from the soil. +- $k_3$ is the natural degradation rate of the pollutant in the plant. + +The natural degradation of the pollutant in the soil or plant could be accounted for by processes like radiation decay, microbial degradation, volatilization, or leaching. +""" + +# ╔═╡ 34e7090b-66c6-4e07-a2ad-b4f82a3669a0 +md""" +Model the aforementioned system of differential equations using a *reaction network object*. Name it `soil_cont_plant_uptake`. +""" + +# ╔═╡ ba6c2c9b-6ba9-48b8-9137-6b43813815ec +# Uncomment and complete the instruction +# soil_cont_plant_uptake = @reaction_network begin +# missing +# end + +# ╔═╡ 16402872-3590-44dd-922f-1846640c92fa +md""" +Convert the system to a symbolic differential equation model and verify that you get the same system of differential equations as given in the problem. +""" + +# ╔═╡ 1a7c3080-5773-44b9-a5c5-bb16f25048a3 +osys = missing + +# ╔═╡ 7c4767ac-ee72-4f46-8699-68f4bfb15d92 +md""" +Suppose that we simulate the evoluation of the pollutant in the soil and plant during $400$ days. The inital pollutant concentrations in the soil and plant both have the value of $0.001\;mg/kg$. In the simulation, the soil is being contaminated at a rate $0.06\;mg/(kg \cdot day)$. The degradation rates and uptake coefficient have the following values: $k_1 = 4.1 \times 10^{-3}$, $k_2 = 1.9 \times 10^{-2}$, $k_3 = 2.2 \times 10^{-2}$. There units are consistent with the units of the aforementioned values. +""" + +# ╔═╡ 340328cc-78ed-4c6c-a0bf-73fd1be21d21 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ 9d4fc31d-32b4-49c7-9e4c-577530199513 +u0 = missing + +# ╔═╡ 51ffec7c-6033-47a9-b65a-5f9a7ab96fb9 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ 20b43337-58fd-4b23-8e4d-c4fd8234bb5f +tspan = missing + +# ╔═╡ ad63f799-bc65-4f2c-ba6c-461fa10139d0 +md""" +Initialize a vector `parms` with the parameter values: +""" + +# ╔═╡ 01e33f75-fdfe-4983-a3bd-4cf074152390 +parms = missing + +# ╔═╡ 0f326aa7-044c-4c6f-be71-acf5c032f796 +md""" +Create the ODE problem and store it in `oprob`: +""" + +# ╔═╡ 6f84b532-a718-4d42-9829-91366693b51c +oprob = missing; + +# ╔═╡ ca65797f-a1dd-42dd-992c-ba067932a018 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=1.0`. Store the solution in `osol`: +""" + +# ╔═╡ 3d66d40f-f627-4268-890f-ab662c0efdd6 +osol = missing + +# ╔═╡ 22dc63ad-47d5-45c4-8902-e9d7abc0a4f6 +md""" +Plot the solutions: +""" + +# ╔═╡ 0f920caa-5993-448c-a449-6449feea121a +missing + +# ╔═╡ 62c66f8a-6561-4d50-b6d9-4dfc43cef0a8 +md""" +!!! question + 1. Interprate the simulation results (cf. peak in $C$ and increase of $P$) in terms of the used parameter values. +""" + +# ╔═╡ 27c1e08f-5e6a-43e3-b8e3-bba78e093556 +md"- Answer: missing" + +# ╔═╡ 73b63510-e1a1-44a6-8902-67ee383e6582 +md""" +!!! question + 2. How would you modify the basic model to make it a more realistic biological model (cf. hill, monod, ...)? +""" + +# ╔═╡ a7068013-ed98-486a-ba59-a57f26d12d1c +md"- Answer: missing" + +# ╔═╡ ad11f590-aa18-45f6-99be-571039ccbbae +md""" +!!! question + 3. What are the units of the parameters k₁, k₂ and k₃? +""" + +# ╔═╡ 1ebbac82-068a-4754-8eec-1afa662feb96 +md"- Answer: missing" + +# ╔═╡ Cell order: +# ╟─2f0c3dd4-9429-4f5b-9150-011970b003f4 +# ╠═121df656-f57a-11ee-140e-dfb61e112370 +# ╠═6c7911b4-fec2-4139-8b54-36a4fb5916a0 +# ╠═895c1016-a8ce-43ae-8094-5d6ea75a6053 +# ╠═28e9e96c-fce6-4507-97c9-37337a0731bc +# ╟─2a8fdc6b-2372-4a97-a094-d218a22274d8 +# ╟─c0c35547-9eed-428b-b513-4b5166decb7e +# ╟─34e7090b-66c6-4e07-a2ad-b4f82a3669a0 +# ╠═ba6c2c9b-6ba9-48b8-9137-6b43813815ec +# ╟─16402872-3590-44dd-922f-1846640c92fa +# ╠═1a7c3080-5773-44b9-a5c5-bb16f25048a3 +# ╟─7c4767ac-ee72-4f46-8699-68f4bfb15d92 +# ╟─340328cc-78ed-4c6c-a0bf-73fd1be21d21 +# ╠═9d4fc31d-32b4-49c7-9e4c-577530199513 +# ╟─51ffec7c-6033-47a9-b65a-5f9a7ab96fb9 +# ╠═20b43337-58fd-4b23-8e4d-c4fd8234bb5f +# ╟─ad63f799-bc65-4f2c-ba6c-461fa10139d0 +# ╠═01e33f75-fdfe-4983-a3bd-4cf074152390 +# ╟─0f326aa7-044c-4c6f-be71-acf5c032f796 +# ╠═6f84b532-a718-4d42-9829-91366693b51c +# ╟─ca65797f-a1dd-42dd-992c-ba067932a018 +# ╠═3d66d40f-f627-4268-890f-ab662c0efdd6 +# ╟─22dc63ad-47d5-45c4-8902-e9d7abc0a4f6 +# ╠═0f920caa-5993-448c-a449-6449feea121a +# ╟─62c66f8a-6561-4d50-b6d9-4dfc43cef0a8 +# ╠═27c1e08f-5e6a-43e3-b8e3-bba78e093556 +# ╟─73b63510-e1a1-44a6-8902-67ee383e6582 +# ╠═a7068013-ed98-486a-ba59-a57f26d12d1c +# ╟─ad11f590-aa18-45f6-99be-571039ccbbae +# ╠═1ebbac82-068a-4754-8eec-1afa662feb96 diff --git a/src/exercises/ode_model_XTRA_water_evap_infil.jl b/src/exercises/ode_model_XTRA_water_evap_infil.jl new file mode 100644 index 00000000..6d5dcc11 --- /dev/null +++ b/src/exercises/ode_model_XTRA_water_evap_infil.jl @@ -0,0 +1,240 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "16" +#> title = "2. ODE_model_Xtra_evaporation" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Extra exercise on water evaporation and infiltration in a reservoir" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ e079823b-8b40-42a2-a63f-1645a97b33f0 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 62dc7706-f58a-11ee-2d3d-f78f7ceca914 +using Markdown, InteractiveUtils + +# ╔═╡ 65571bb8-e260-4a82-b0d2-198e47c56271 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 6c4b3d09-09c2-4439-9167-63b59b078104 +using OrdinaryDiffEq, Plots + +# ╔═╡ c131694c-ad64-44b5-a4fa-0976cd3ec3dd +md""" +# Exercise: Water evaporation and infiltration +""" + +# ╔═╡ 66e8a12c-74b6-4077-b90e-3d85e5a61d6e +hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text])); + +# ╔═╡ d01091d7-2593-482d-b734-01209dde2249 +md""" +![](https://users.ugent.be/~gvhaelew/fig/water_evap_infil_model.png) +""" + +# ╔═╡ f55e0ca3-07ff-43a1-907f-9ad640227822 +md""" +Consider a water reservoir, such as a lake, where the water in the reservoir is in contact with the air as well as with the groundwater. We will denote the water level in the reservoir as $W$ and the groundwater level as $G$. + +The water in the reservoir evaporates at a rate $k_1$ (i.e. the *evaporation coefficient*) and there can be infiltration into or from the groundwater at a rate $k_2$ (i.e., *infiltration coefficient*) depending on the difference in the water level in the reservoir and groundwater (cf. $(W-G)$) + +There is a natural constant inflow of water into the reservoir at a rate $I$. At time $t=0\;s$ a pumping device is switched on such that the reservoir is rapidly being emptied at an outflow rate $O$ until the level of the water reservoir drops to zero. From then on, the pump is switched off. +""" + +# ╔═╡ a551f3c5-2fc3-4236-ab4a-3d9c14afc62b +md""" +!!! question +Set-up a system of differential equations modelling the above problem. +""" + +# ╔═╡ 4eb95688-ba9d-4524-a522-3b8343f4e2be +hint( +md""" +The system of differential equations that models the water level in a reservoir ($W$) and the groundwater level ($G$) considering evaporation, infiltration, inlet flow and outlet flow can be written down as: + +$$\begin{align} +\frac{dW}{dt} & = I - O - k_1 \cdot W - k_2 \cdot (W - G) \\ +\frac{dG}{dt} & = k_2 \cdot (W - G) +\end{align}$$ +""" +) + +# ╔═╡ dc9d4427-0a9e-41ab-9074-3e4f10dbae7f +md""" +Model the aforementioned system of differential equations using a *reaction network object*. Name it `water_evap_infil`. +""" + +# ╔═╡ 3c275181-503e-4806-b0e6-1731ae881a30 +# Uncomment and complete the instruction +# water_evap_infil = @reaction_network begin +# missing +# end + +# ╔═╡ 608c61bc-c029-433e-ad8f-b13cfb40bc3d +md""" +Convert the system to a symbolic differential equation model and verify that you get the same system of differential equations as given in the problem. +""" + +# ╔═╡ 5647d122-5e8d-4ff9-a798-4076ee93b771 +# osys = missing # Uncomment and complete the instruction + +# ╔═╡ acab2bf0-b792-4ccc-bee0-7611bedab23c +md""" +Both water levels are initially $6.75\;m$. The inflow rate is constant and is $2.7\; m/min$. The evaporation and infiltration coefficient are $0.4\;min^{-1}$ and $1.0\;min^{-1}$ respectively. The outflow rate due to the pump is $20\;m/min$ and the pump stops working when $W$ equals zero. We wish to simulate the evolution of $W$ and $G$ during $20\;min$. +""" + +# ╔═╡ 44cf2ac6-75e2-4440-94f1-8fe6887ee1e0 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ 733bdb56-fb4f-4bc6-b50c-e3245fd59730 +# u0 = missing # Uncomment and complete the instruction + +# ╔═╡ 521fcca5-3c88-463b-9395-e5871b9fc5a3 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ f2267cc9-4bfd-44af-984c-cf54aa855f91 +# tspan = missing # Uncomment and complete the instruction + +# ╔═╡ e12840ed-d448-4f7c-893d-cfe974f62f9a +md""" +Initialize a vector `param` with the parameter values: +""" + +# ╔═╡ 5a3d520d-fd4f-485c-a8b0-63858eca4bfc +# params = missing # Uncomment and complete the instruction + +# ╔═╡ 666c3aa1-26ef-4d77-bb81-f8830e66eea2 +md""" +Set-up a the *condition*, name it `condition`. +""" + +# ╔═╡ ce612639-0791-4df9-bbd1-11da5ae8b247 +# condition = missing # Uncomment and complete the instruction + +# ╔═╡ b4e50fbc-6780-4559-8cab-d7f2fd533eba +md""" +Make a new *reaction system* where the discrete event is included. Name it `water_evap_infil_c`. +""" + +# ╔═╡ 82db28cb-d842-442f-a566-32c6fe3acc90 +# @named water_evap_infil_c = missing # Uncomment and complete the instruction + +# ╔═╡ 7b649517-ccab-4f30-b75d-527295cd24a2 +md""" +Complete the new *reaction system*. Name it `water_evap_infil_c_com`. +""" + +# ╔═╡ 2d72257d-cf23-4a07-b659-4b886abe5abc +# water_evap_infil_c_com = missing # Uncomment and complete the instruction + +# ╔═╡ aafb49a4-b468-49b2-838d-5ddfcc852d48 +md""" +Create the ODE problem and store it in `oprob`: +""" + +# ╔═╡ a25d3652-13f5-47ef-9f16-c6698547a734 +# oprob = missing; # Uncomment and complete the instruction + +# ╔═╡ 451a3c66-5bcc-4161-af81-f89af33b5862 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.1`. Store the solution in `osol`: +""" + +# ╔═╡ b9d2c6cb-88f0-4a88-9e61-eecc905ff3e6 +# osol = missing # Uncomment and complete the instruction + +# ╔═╡ ab77c284-d379-47d1-bd86-88fa77749165 +md""" +Plot the results: +""" + +# ╔═╡ e29a3294-245b-445d-bb1e-12cafb2ec175 +# missing # Uncomment and complete the instruction + +# ╔═╡ 66588291-d399-4169-9383-ac6c05cdf906 +md""" +Interpret the results. Ask yourself the following questions: + +1. Can you clearly see the drop in $W$? To what value does $W$ drops? +""" + +# ╔═╡ 89ece6c0-3690-4265-8b1d-c3a3cc8b095f +md"- Answer: missing" + +# ╔═╡ 92fa2a5a-123b-434c-a97f-39d9727a5ab6 +md""" +2. Why does $G$ also drop when $W$ drops? Explain. +""" + +# ╔═╡ 71dde4bd-711c-40c3-8ee2-9f7a79d72aa2 +md"- Answer: missing" + +# ╔═╡ d04d906d-1432-4753-81b9-b03a06df9816 +md""" +3. To what values are $W$ and $G$ tending to go? Was the system with the initial values for $W$ and $G$ and no outflow in equilibrium? Explain. +""" + +# ╔═╡ 4ce3328e-3436-45ad-b899-9b901b53a8ea +md"- Answer: missing" + +# ╔═╡ 2574879f-28d1-4d30-a1aa-a637dd1b216a +using Catalyst + +# ╔═╡ 10143dd3-2143-4293-9411-a3b34d6bb80e +using OrdinaryDiffEq, Catalyst + +# ╔═╡ Cell order: +# ╟─c131694c-ad64-44b5-a4fa-0976cd3ec3dd +# ╠═62dc7706-f58a-11ee-2d3d-f78f7ceca914 +# ╠═e079823b-8b40-42a2-a63f-1645a97b33f0 +# ╠═65571bb8-e260-4a82-b0d2-198e47c56271 +# ╠═10143dd3-2143-4293-9411-a3b34d6bb80e +# ╟─66e8a12c-74b6-4077-b90e-3d85e5a61d6e +# ╟─d01091d7-2593-482d-b734-01209dde2249 +# ╟─f55e0ca3-07ff-43a1-907f-9ad640227822 +# ╟─a551f3c5-2fc3-4236-ab4a-3d9c14afc62b +# ╟─4eb95688-ba9d-4524-a522-3b8343f4e2be +# ╠═2574879f-28d1-4d30-a1aa-a637dd1b216a +# ╟─dc9d4427-0a9e-41ab-9074-3e4f10dbae7f +# ╠═3c275181-503e-4806-b0e6-1731ae881a30 +# ╟─608c61bc-c029-433e-ad8f-b13cfb40bc3d +# ╠═5647d122-5e8d-4ff9-a798-4076ee93b771 +# ╠═6c4b3d09-09c2-4439-9167-63b59b078104 +# ╟─acab2bf0-b792-4ccc-bee0-7611bedab23c +# ╟─44cf2ac6-75e2-4440-94f1-8fe6887ee1e0 +# ╠═733bdb56-fb4f-4bc6-b50c-e3245fd59730 +# ╟─521fcca5-3c88-463b-9395-e5871b9fc5a3 +# ╠═f2267cc9-4bfd-44af-984c-cf54aa855f91 +# ╟─e12840ed-d448-4f7c-893d-cfe974f62f9a +# ╠═5a3d520d-fd4f-485c-a8b0-63858eca4bfc +# ╟─666c3aa1-26ef-4d77-bb81-f8830e66eea2 +# ╠═ce612639-0791-4df9-bbd1-11da5ae8b247 +# ╟─b4e50fbc-6780-4559-8cab-d7f2fd533eba +# ╠═82db28cb-d842-442f-a566-32c6fe3acc90 +# ╟─7b649517-ccab-4f30-b75d-527295cd24a2 +# ╠═2d72257d-cf23-4a07-b659-4b886abe5abc +# ╟─aafb49a4-b468-49b2-838d-5ddfcc852d48 +# ╠═a25d3652-13f5-47ef-9f16-c6698547a734 +# ╟─451a3c66-5bcc-4161-af81-f89af33b5862 +# ╠═b9d2c6cb-88f0-4a88-9e61-eecc905ff3e6 +# ╟─ab77c284-d379-47d1-bd86-88fa77749165 +# ╠═e29a3294-245b-445d-bb1e-12cafb2ec175 +# ╟─66588291-d399-4169-9383-ac6c05cdf906 +# ╟─89ece6c0-3690-4265-8b1d-c3a3cc8b095f +# ╟─92fa2a5a-123b-434c-a97f-39d9727a5ab6 +# ╟─71dde4bd-711c-40c3-8ee2-9f7a79d72aa2 +# ╟─d04d906d-1432-4753-81b9-b03a06df9816 +# ╟─4ce3328e-3436-45ad-b899-9b901b53a8ea diff --git a/src/exercises/ode_model_birth_death.jl b/src/exercises/ode_model_birth_death.jl new file mode 100644 index 00000000..5f91edd5 --- /dev/null +++ b/src/exercises/ode_model_birth_death.jl @@ -0,0 +1,258 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "10" +#> title = "2. ODE_model_birth_death" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Simple birth-death model for a mice population" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 12fee37d-eef9-4e90-b0af-d892469fad08 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 4a3d066c-f5b1-11ee-0145-2da7c11147a5 +using Markdown, InteractiveUtils + +# ╔═╡ 34a2f237-848c-4d07-9cea-fa5505a9e215 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ b721cc4d-43ba-4221-bbd1-15293aaf54b5 +using OrdinaryDiffEq, Catalyst + +# ╔═╡ c701d64e-640c-473f-b0fa-688024962f28 +md""" +# Exercise: Simple birth-death model for mice +""" + +# ╔═╡ 6e4f08fa-541d-4ca5-a152-6e98a4518690 +md""" +![](https://users.ugent.be/~gvhaelew/fig/mice_model_part1.png) +""" + +# ╔═╡ f1743720-bdeb-4f25-9274-f7e01674dcb1 +md""" +In a simple birth-death model for mice, the birth rate of mice represents the +rate at which new individuals are added to the population through reproduction. +This rate is influenced by factors such as the number of reproductive females, +their fertility, and the frequency of reproduction cycles. Conversely, +the death rate reflects the rate at which individuals are removed from +the population due to mortality factors such as predation, disease, +and environmental stressors. Together, these rates interact dynamically +to shape the population dynamics of mice in their natural habitat. +Denote the number of mice by $X$, the average birth rate by $b$ ($mice / day$), and the average death rate by $d$ ($day^{-1}$). Hence, assume for this overly simplified model, that the birth of mice is a zeroth-order process and that the death of mice is a first-order process. +""" + +# ╔═╡ b583efef-dc7d-4447-bf27-e6373f5872c4 +md""" +Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of $X$ with time. Name it `birth_death`. +""" + +# ╔═╡ 74c3f7ed-d705-4cb9-b52c-e06c9df5ca13 +# birth_death = @reaction_network begin +# missing +# end + +# ╔═╡ c84f2a32-e66b-4a36-a257-7031ce799225 +md""" +Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. +""" + +# ╔═╡ 124eb20e-6945-4eec-a05a-6b835efbdd2e +osys = missing + +# ╔═╡ 04c645ad-873d-44c1-9bc0-940a412630b9 +md""" +## Part 1 + +Simulate the evolution of the number of mice **per day** during $10$ years starting off with $2$ mice. **Assume that per year 25 pups are born.** Suppose the death rate to be $0.0015\;day^{-1}$. +""" + +# ╔═╡ 1cd5136a-f472-4329-980a-b8382d1c04ea +md""" +First, calculate the birth rate in $mice/day$. +""" + +# ╔═╡ 6b64ec8b-d85e-4a4f-aa3f-f177f5e34180 +missing + +# ╔═╡ 52ee74d2-4dca-47ef-8b97-f826ef31ddf9 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ 23f282d8-2eeb-43dd-8ba9-84684daca7a4 +u0 = missing + +# ╔═╡ 30fa6fc6-54e0-4233-9334-b3d15f630489 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ b461a225-bae6-45b8-bb42-54742a72b98f +tspan = missing + +# ╔═╡ c753d65f-9ed3-4e60-8e36-6e4bca77c19b +md""" +Initialize a vector `parms` with the parameter values: +""" + +# ╔═╡ e7942ba9-5434-4b22-b1d4-f18f6227320d +parms = missing + +# ╔═╡ 2af7f6b9-9d95-4bf6-af67-449803440639 +md""" +Create the ODE problem and store it in `oprob`: +""" + +# ╔═╡ 9432f90b-7141-4518-b6fd-d55a1389e14a +oprob = missing; + +# ╔═╡ ab197ac7-413b-4305-8352-2723bf9f2aff +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=1.0`. Store the solution in `osol`: +""" + +# ╔═╡ 8e517114-7c74-4d7c-954c-2787d975a1df +osol = missing + +# ╔═╡ 9eaf1ca5-3d65-4263-a307-991bc8bf62d2 +md""" +Plot the results: +""" + +# ╔═╡ 83e36e63-7e6b-4c17-8f7d-ed0e07f36fc5 +missing + +# ╔═╡ 5fc64787-795c-4e17-b751-cd3683f8016b +md""" +!!! question + Interpret the results. Ask yourself the following questions: + - What is the (approximate) steady state value for $X$? +""" + +# ╔═╡ f5718c08-c8aa-4d74-b70f-8c08c6decab2 +md"- Answer: missing" + +# ╔═╡ fe4526c8-4504-4d91-841a-a83aeef55fc7 +md""" +## Part 2 + +Suppose that at $t = 3\;years$ the death rate of the mice population increases by $50\,\%$ due to a new predator species in the area. Use the same initial condition, timespan and parameter values. Simulate the evolution of the number of mice. +""" + +# ╔═╡ 1e869025-c537-4bf1-9c38-93e24b598156 +md""" +Create the *condition*. Store it in `condition2`: +""" + +# ╔═╡ 1c5d8daa-5c7e-49ef-a6da-6459a1131320 +condition2 = missing + +# ╔═╡ 4d9ba697-5db3-4879-be88-278765e766f1 +md""" +Make a new *reaction system* where the discrete event is included. Name it `birth_death2`. +""" + +# ╔═╡ d40c2598-5783-49c5-916d-296e946de3f6 +# @named birth_death2 = missing + +# ╔═╡ afbc4f00-0b4a-427a-9630-3a279ccbc330 +md""" +Complete the new *reaction system*. Name it `birth_death2_com`. +""" + +# ╔═╡ 3dd3febc-fc3f-477f-a5be-335ae90223e3 +birth_death2_com = missing + +# ╔═╡ cf05864f-37f0-4fbb-93a5-ef406a2ae014 +md""" +Create the ODE problem and store it in `oprob2`: +""" + +# ╔═╡ 65e24cee-06c3-4cb7-8f8c-61e02b7dcd29 +oprob2 = missing; + +# ╔═╡ 9cab94b5-6d2d-4b5a-96f3-3b8eee1c61a1 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=1.0`. Store the solution in `osol2`: +""" + +# ╔═╡ 00750aa4-6a00-4d33-a0ab-2d7abe952078 +osol2 = missing + +# ╔═╡ 694e5b50-f4c9-4ecc-8860-1b217696e1ea +md""" +Plot the results: +""" + +# ╔═╡ c69d0e86-0d15-4915-a890-8abd378da59a +missing + +# ╔═╡ 1f869579-7019-4120-8d14-b38f48d05bdd +md""" +!!! questions + Interpret the results. Ask yourself the following questions: + 1. Can you clearly see the effect of the increase in the death rate? + 2. If the death rate increases at a different timepoint, would you reach the same steady state value for $X$? Explain. +""" + +# ╔═╡ 775f2cf3-ffbb-491a-9002-3a39b9c70f3c +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ Cell order: +# ╟─c701d64e-640c-473f-b0fa-688024962f28 +# ╠═4a3d066c-f5b1-11ee-0145-2da7c11147a5 +# ╠═12fee37d-eef9-4e90-b0af-d892469fad08 +# ╠═34a2f237-848c-4d07-9cea-fa5505a9e215 +# ╠═b721cc4d-43ba-4221-bbd1-15293aaf54b5 +# ╟─6e4f08fa-541d-4ca5-a152-6e98a4518690 +# ╟─f1743720-bdeb-4f25-9274-f7e01674dcb1 +# ╟─b583efef-dc7d-4447-bf27-e6373f5872c4 +# ╠═74c3f7ed-d705-4cb9-b52c-e06c9df5ca13 +# ╟─c84f2a32-e66b-4a36-a257-7031ce799225 +# ╠═124eb20e-6945-4eec-a05a-6b835efbdd2e +# ╟─04c645ad-873d-44c1-9bc0-940a412630b9 +# ╟─1cd5136a-f472-4329-980a-b8382d1c04ea +# ╠═6b64ec8b-d85e-4a4f-aa3f-f177f5e34180 +# ╟─52ee74d2-4dca-47ef-8b97-f826ef31ddf9 +# ╠═23f282d8-2eeb-43dd-8ba9-84684daca7a4 +# ╟─30fa6fc6-54e0-4233-9334-b3d15f630489 +# ╠═b461a225-bae6-45b8-bb42-54742a72b98f +# ╟─c753d65f-9ed3-4e60-8e36-6e4bca77c19b +# ╠═e7942ba9-5434-4b22-b1d4-f18f6227320d +# ╟─2af7f6b9-9d95-4bf6-af67-449803440639 +# ╠═9432f90b-7141-4518-b6fd-d55a1389e14a +# ╟─ab197ac7-413b-4305-8352-2723bf9f2aff +# ╠═8e517114-7c74-4d7c-954c-2787d975a1df +# ╟─9eaf1ca5-3d65-4263-a307-991bc8bf62d2 +# ╠═83e36e63-7e6b-4c17-8f7d-ed0e07f36fc5 +# ╟─5fc64787-795c-4e17-b751-cd3683f8016b +# ╠═f5718c08-c8aa-4d74-b70f-8c08c6decab2 +# ╟─fe4526c8-4504-4d91-841a-a83aeef55fc7 +# ╟─1e869025-c537-4bf1-9c38-93e24b598156 +# ╠═1c5d8daa-5c7e-49ef-a6da-6459a1131320 +# ╟─4d9ba697-5db3-4879-be88-278765e766f1 +# ╠═d40c2598-5783-49c5-916d-296e946de3f6 +# ╟─afbc4f00-0b4a-427a-9630-3a279ccbc330 +# ╠═3dd3febc-fc3f-477f-a5be-335ae90223e3 +# ╟─cf05864f-37f0-4fbb-93a5-ef406a2ae014 +# ╠═65e24cee-06c3-4cb7-8f8c-61e02b7dcd29 +# ╟─9cab94b5-6d2d-4b5a-96f3-3b8eee1c61a1 +# ╠═00750aa4-6a00-4d33-a0ab-2d7abe952078 +# ╟─694e5b50-f4c9-4ecc-8860-1b217696e1ea +# ╠═c69d0e86-0d15-4915-a890-8abd378da59a +# ╟─1f869579-7019-4120-8d14-b38f48d05bdd +# ╠═775f2cf3-ffbb-491a-9002-3a39b9c70f3c diff --git a/src/exercises/ode_model_catalyst_intro.jl b/src/exercises/ode_model_catalyst_intro.jl new file mode 100644 index 00000000..3cffc2cb --- /dev/null +++ b/src/exercises/ode_model_catalyst_intro.jl @@ -0,0 +1,786 @@ +### A Pluto.jl notebook ### +# v0.20.13 + +#> [frontmatter] +#> order = "9" +#> title = "2. ODE_model_Catalyst_intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Introduction to Catalyst as an alternative to ModelingToolkit" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ e6e748a5-b692-4c7c-b39e-ef0fe9e46c35 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ be326550-25ea-4c5f-ac4a-dd74d12bc89a +using Markdown, InteractiveUtils + +# ╔═╡ 3cfe381f-5871-4447-9149-fc023c787b42 +using PlutoUI; TableOfContents() + +# ╔═╡ b1bfe3b7-782c-46c9-86db-829942b6a9d0 +using Catalyst + +# ╔═╡ 19398992-dca7-441a-a5fd-7f9d0ad30be5 +using OrdinaryDiffEq, StatsPlots + +# ╔═╡ 9ea49d7a-e524-11ee-1b68-9d1d71aaba24 +md""" +# Introduction to Catalyst (ODE) +""" + +# ╔═╡ ba4dfc95-b74f-4d36-b34b-5eb2836a5cd6 +md""" +In practical 1, we learned to solve ODEs by making use of ModelingToolkit.jl (MTK). In this practical we will learn to use Catalyst.jl which is an alternative for defining ODEs. So bear in mind that all problems you will solve in this practical can also be solved +by making use of ModelingToolkit.jl (this could be a nice test for yourself). +Catalyst.jl is a symbolic modelling package for construction, analysis and high performance simulation of chemical reaction networks. In essence, the package simply provides an alternative way for defining ModelingToolkit's symbolic systems, +using the notation of chemical reaction networks. These can be created programmatically or easily specified using Catalyst's **D**omain **S**pecific **L**anguage (DSL).""" + +# ╔═╡ 94440e99-7c0b-4d87-8038-141a2bc5fcb8 +md""" +This notebook describes the syntax for building chemical reaction network models using Catalyst's DSL. We will illustrate this by implementing and solving an infection model by means of ODE (**O**rdinary **D**ifferential **E**quations). +""" + +# ╔═╡ 83fa478c-ac54-482b-92de-8eb6a01ddfaf +md""" +## The infection model +""" + +# ╔═╡ d257102b-8480-4ce3-bdba-50995ccbdc26 +md""" +It is important to model the outbreak of infectious diseases in order to devise appropriate measures to avoid global epidemics. In this exercise, we consider an isolated group of people in which a viral disease is spreading. We use an infection model (similar to the SIR-model but slightly extended) for this purpose. We are interested in the evolution of the number of susceptible ($S$), infected ($I$), deceased ($D$) and resistant ($R$) persons.\ +We make the following assumptions: +1. Transmission of the disease from an infected person to a susceptible person takes place through direct contact. The chance of any two inhabitants of the group coming into contact with each other is $\beta$, and the probability of infection after contact between an infected and a susceptible person is $\alpha$. +2. Note that the above assumption implicitly states that the probability of two neighbours coming into contact with each other is as high as the probability of two people living at two extremes of the territory coming into contact with each other. +3. A person leaves the infection period at a rate $r$ (hence, a person is contagious for an average of $1/r$ days. Without appropriate medication, a fraction $m$ of infected people die and a fraction $(1-m)$ of infected people acquire immunity after healing. +4. We assume that there is no migration in or out of the population. +""" + +# ╔═╡ df9a46ab-56eb-4d3d-a3a8-e1f2da59b32e +md" +Below we summarize the **relevant variables** (**species**): +" + +# ╔═╡ 7dd37224-071b-4ec7-88d5-39fd4482e615 +md""" +| Variable | Unit | Meaning | +|:---------- |:---------- |:------------| +| ``S`` | *persons* | number of susceptible persons | +| ``I`` | *persons* | number of infected persons | +| ``D`` | *persons* | number of deceased persons | +| ``R`` | *persons* | number of resistant persons | +""" + +# ╔═╡ 97d34ab6-a1c6-404d-af1f-7da85287757f +md" +Below we summarize the **parameters**: +" + +# ╔═╡ b5e6cb25-da6f-475a-bc73-041c4c9256d5 +md""" +| Variable | Unit | Meaning | +|:---------- |:---------- |:------------| +| ``\alpha`` | ``\frac{persons}{contact}`` | chances of getting infected after contact | +| ``\beta`` | ``\frac{contact}{persons^2\,day}`` | contact rate | +| ``r`` | ``\frac{1}{day}`` | rate of leaving infection period | +| ``m`` | ``\frac{person}{person}`` | fraction of persons deceasing | +| ``1-m`` | ``\frac{person}{person}`` | fraction of persons becoming resistant | +""" + +# ╔═╡ 529ddac3-bb7a-4cc3-9bbd-ca31f5796e27 +md""" +Hence, the infection rate is ``\alpha \beta``. This means that a susceptible person meets an infected person: ``S+I``, this will result in ``2I`` at a rate ``\alpha \beta``. Futhermore, an infected person ``I`` will either become a deceased person ``D`` at a rate ``m r`` or become a resistant person ``R`` at rate ``(1-m) r`` +""" + +# ╔═╡ f9ca0b86-1447-48c1-88e1-876180d7628a +md""" +Our infection model has three reaction events: + +1. Infection, where a susceptible persons meets an infected persons and also becomes infected. +2. Deceasing, where an infected person die. +3. Recovery, where an infected person recovers and becomes resitant. +""" + +# ╔═╡ bf2b06d8-884b-4a35-b308-83a2f3d6696f +md""" +Each reaction is also associated with a specific rate: + +1. ``\alpha \beta``, the infection rate. +2. ``m r``, the death rate. +3. ``(1-m) r``, the recovery rate. +""" + +# ╔═╡ 1f405b09-53cd-43e0-b498-70eafef92acb +md""" +Hence, the following *infection reactions* are: + +$$S + I \xrightarrow[]{\alpha \beta} 2I$$ +$$I \xrightarrow[]{mr} D$$ +$$I \xrightarrow[]{(1-m)r} R$$ +""" + +# ╔═╡ 39d80a3f-14d2-49d6-905b-171adf5930a0 +md""" +We are going to implement this system of *reactions* using Catalyst. +""" + +# ╔═╡ 44c6cdc0-8610-43bd-b3b3-a7441ee62615 +md""" +We first load the Catalyst package, which is required for the code in this introduction to run: +""" + +# ╔═╡ cd3da548-b24f-426f-8b46-2db70e0a979c +md""" +### Implementation of the system + +The following code creates a so called *reaction network object*, that we have named `infection_model`, that implements the aforementioned *reactions*. +""" + +# ╔═╡ 7d2f8bf9-3a00-4631-84ff-1a36b6d7e19b +infection_model = @reaction_network begin + @species S(t)=9_999_000.0 I(t)=0.0 + @parameters α=1e-6 + α * β, S + I --> 2I + r * m, I --> D + r * (1 - m), I --> R +end + +# ╔═╡ 4a504e36-8269-4822-8919-75b7f6dcaf6d +md" +Each line (between `begin` and `end`) corresponds to a *reaction*. Each *reaction* consists of: + +- a **reaction rate** (the expression on the left-hand side of `,`), +- a set of **substrates** (the expression in-between `,` and `-->`), +- a set of **products** (the expression on the right-hand side of `-->`). + +The substrates and the products may contain one or more reactants, separated by `+`. +" + +# ╔═╡ bb5578b8-40d4-48c9-be87-cfaeadb8c5f9 +md""" +!!! tip "Tip" + The Greek letters can be visualized by typing a **backslash** followed by the **name of the Greek letter** and then the **TAB** key. For example `\alpha` followed by the TAB key results in in a list where you can choose `α`. +""" + +# ╔═╡ af6b169d-55c3-4384-9f57-ad629e02bad6 +md" +The *reaction model* is stored in the variable `infection_model` (the variable name can be chosen freely). It is a symbolic representation of the (chemical) network. +" + +# ╔═╡ 52b2cc8f-1eac-47c1-8d42-09c1b91d1350 +md""" +You can get a list of the different *reaction* **species** with the command `species` +""" + +# ╔═╡ 5f1d6198-ccdc-4a63-bf98-2032a6d683ad +species(infection_model) + +# ╔═╡ 59686253-3e0e-454e-8bc6-dde189ee2197 +md""" +To get a list of the *reaction* **parameters**, you can use the command `parameters`: +""" + +# ╔═╡ fdb79a26-fbc5-482a-8cdb-760d18f57a77 +parameters(infection_model) + +# ╔═╡ 2bd02577-7321-46b5-9f42-1484ed86d1d3 +md""" +!!! important "Important" + You can also get the different species and parameters using `@unpack` followed by comma separated species and/or parameter names followed by the equal sign and the name of the reaction network model. For example: +""" + +# ╔═╡ 64455b26-4374-435f-b71b-8b9600cab263 +@unpack S, I, D, R = infection_model; + +# ╔═╡ 64a48327-03ec-46a0-9a76-79c10acb6e20 +md""" +The *reaction model* is essentially a `ModelingToolkit` `ODESystem` with some extra information added on top (such as: what to return when you call the function `species` on it). It can be converted to a classic `ODESystem` via +""" + +# ╔═╡ 78a22ba2-1e60-40b3-989c-922dcf9ca054 +osys = convert(ODESystem, infection_model) + +# ╔═╡ eeb25e50-165b-4e93-8397-5b09fe8e7242 +md""" +Note that the model equations are essentially: + +$$\cfrac{dS(t)}{dt} = -\alpha \beta S(t) I(t)$$ +$$\cfrac{dI(t)}{dt} = \alpha \beta S(t) I(t) - r I(t)$$ +$$\cfrac{dD(t)}{dt} = m r I(t)$$ +$$\cfrac{dR(t)}{dt} = (1-m) r I(t)$$ +""" + +# ╔═╡ 0d39fbbc-4fcb-45eb-b24b-14f2e093c98c +md""" +You can get a list of the differential equations with the command `equations`: +""" + +# ╔═╡ ca18795a-d270-493f-807f-a3b8c78aa6d7 +equations(osys) + +# ╔═╡ 3f6f38a3-12ec-4b5b-8ebb-9e48190d3f7e +md""" +To get a list of the state variables, you can use the command `unknowns`: +""" + +# ╔═╡ 72f7f870-b2fc-4df1-9325-3c587be7e014 +unknowns(osys) + +# ╔═╡ da5cfd6e-b57a-4235-b79d-a1c2f148328b +md""" +To get a list of the parameters, you can use the command `parameters`: +""" + +# ╔═╡ a09b3a39-bedc-4180-ae5b-8cec708a64cb +parameters(osys) + +# ╔═╡ 7fc8c671-b75c-4487-a013-779ee2422c8b +md""" +### Simulating the system as an ODE-problem + +We first need to load the `OrdinaryDiffEq` and `StatsPlots` packages, which are required for simulating the system and plotting the results. +""" + +# ╔═╡ 3197244f-655b-4dca-80f3-794b30722551 +md""" +Now we wish to simulate our model. To do this, we need to provide the following information: + +- Initial conditions for the state variables $S$, $I$, $D$ and $R$. +- The parameter values for $\alpha$, $\beta$, $r$ and $m$. +- The timespan, which is the timeframe over which we wish to run the simulation. + +Assume in this example that there are $10\,000\,000$ people in the country, and that initially $1\,000$ persons are infected. Hence, $I_0 = 1\,000$, $S_0 = 10\,000\,000-I_0 = 9\,999\,000$, $D_0 = 0$ and $R_0 = 0.$\ +Furthermore, we take the following values for the parameters: $\alpha = 0.08\;person/contact$, $\beta = 10^{-6}\;contact/(person^2\,day)$, $r = 0.2\;day^{-1}$ (i.e. a person is contagious for an average of $5\;days$) and $m=0.4$. The following table summarizes the above values: + +|Initial conditions |Parameters | +|:------------------------------------|:-------------------| +|$S_0 = 9\,999\,000$ |$\alpha = 0.08$ | +|$I_0 = 1\,000$ |$\beta = 10^{-6}$ | +|$D_0 = 0$ |$r = 0.2$ | +|$R_0 = 0$ |$m=0.4$ | + +Finally, we want to run our simulation from day $0$ till day $90$. +""" + +# ╔═╡ c4e83ef8-9490-4361-a2a9-5abc45e242be +md""" +### Creating the ODEProblem +""" + +# ╔═╡ a7a99fde-c1bd-484c-a7b6-a7d8af809ecc +md""" +Creating the ODE problem works the same as when working with a `ModelingToolkit` model: we use the function `ODEProblem` and provide the symbolic system, the initial conditions, the time span, and the parameters. There is **one important difference**: as we defined our variables and parameters inside the Catalyst model, they are generally **not defined in the global environment**. We have a few options to solve this: +1. Use the variable names instead as `Symbol`s, for example: `:S` +1. Specify the variable or parameter as an element of the Catalyst model using the following syntax: `model.X`, for example: `infection_model.S` +1. Use `@unpack` to bring the variables into the global environment (does not work for the parameters). +""" + +# ╔═╡ 35bd9a1a-bb4a-4285-98f0-853b03c95cb7 +u0 = [:S => 9_999_000.0, :I => 1_000.0, :D => 0.0, :R => 0.0] + +# ╔═╡ a25d5925-a254-488b-b782-d29cff4470a2 +tspan = (0.0, 90.0) + +# ╔═╡ 9a9440fa-d8a3-44bc-8037-4bf1f8af40b0 +parms = [:α => 0.08, :β => 1.0e-6, :r => 0.2, :m => 0.4] + +# ╔═╡ c6d2dd69-8c61-4a40-894f-664b2d2d14be +oprob = ODEProblem(infection_model, u0, tspan, parms); + +# ╔═╡ 050a3786-5ce3-44c2-b2c2-94cb640326aa +md""" +!!! note + The time span is given in parentheses `()` rather than square brackets `[]` to make it into a `Tuple` rather than a `Vector`. `Tuple`s are very similar to `Vector`s, but are used when the amount of elements inside is important. This is the case here because our time span always needs exactly 2 elements: a starting time and an ending time. The parameters and initial conditions, on the other hand, could contain any amount of elements, and therefore we use `Vector`s for them. +""" + +# ╔═╡ 3c253bf3-886d-4e86-82ac-7751d23f342f +md""" +### Solving the ODEProblem +""" + +# ╔═╡ 14756171-9e8e-4cb0-b7af-74c2d649fe9f +md""" +We can now simulate our model. Solving the model is again done using `OrdinaryDiffEq`, and is therefore exactly the same as the first practical. If you'd like some more examples, there are some [examples](https://docs.sciml.ai/DiffEqDocs/stable/getting_started/) online on how to solve ODE problems with Julia's ecosystem for differential equations. Additionally, if you're interested in all the ODE solvers available, there is a [nice overview](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Full-List-of-Methods) available. +""" + +# ╔═╡ 142e3e48-bf75-4498-ad0e-9f47cb921045 +# osol = solve(oprob) +osol = solve(oprob, Tsit5(), saveat=0.5) + +# ╔═╡ a3599781-a690-4fa3-b483-cd47727935cb +md""" +Note that at the different time points the variables values in the solution are decimal numbers (and not integer numbers), despite the fact that we are applying the model to individuals. This is inherent to using an ODE approach. Later on, we will see how we can discretise the problem, and hence, work on the level of individual infections (reactions).\ +Futhermore, note that executing the `solve` command at different occasions with an ODE problem will never modify the solution because ODE problems are **deterministic**. This will become different when simulating the individual infection (reaction) events by means of a stochastic (random) algorithm. +""" + +# ╔═╡ 8ac90e5c-ce85-4113-bc1b-9dc17cf7e928 +md""" +### Plotting the results +""" + +# ╔═╡ 0af3c166-46b6-455d-af6b-a72c4d2a5ce4 +md""" +Finally, we can plot the solution through the `plot` function. +""" + +# ╔═╡ 513c037b-c54c-47fa-b97a-06f69a983386 +plot(osol) + +# ╔═╡ 5672749b-283a-4e8e-8f90-76b5312b30ac +md""" +If you want to plot less species, like for example just $S$ and $I$, you can specify this with the option `idxs=[:S, :I]` (*notice the brackets*) in the plot function. +""" + +# ╔═╡ 01188934-ce0f-4d65-b880-caac4837d796 +plot(osol, idxs=[:S, :I]) # brackets [ ] + +# ╔═╡ f3cf01a1-3c65-4a37-bf9a-cd2233cb470b +md""" +If you want a phase plot of, for example, just $I$ versus $S$, you can specify this with the option `idxs=(:S, :I)` (*notice the parentheses*) in the plot function. You can indicate the $S$ and $I$ axes with the additional options `xlab="S"` and `ylab="I"`. +""" + +# ╔═╡ ff8f4c23-5695-48aa-9965-02677103f2c9 +plot(osol, idxs=(:S, :I), xlab="S", ylab="I") # parentheses ( ) + +# ╔═╡ b07f09c6-0515-4d27-9ca1-c45427a5988c +md""" +If you want to see the final values of $S$, $I$, $D$ and $R$, type: +""" + +# ╔═╡ ab4940b8-b8ec-4835-8d6e-4e57a5e2e464 +osol.u[end] + +# ╔═╡ a8b68546-9401-4815-9f33-cc22b7a0579d +md""" +If you want the vector of, e.g., $S$ values separately, type: +""" + +# ╔═╡ 89cc8970-693d-4885-b0b5-46110db4ca32 +osol[:S] + +# ╔═╡ dcdd693d-f2f3-406c-8647-31cdedcb823b +md""" +If you want the last value in the S vector, type: +""" + +# ╔═╡ 0a2ed6a6-1535-4721-a6da-008f62b16942 +osol[:S][end] + +# ╔═╡ 757d9f1e-5cba-4ae8-9172-e0c2fceb8458 +md""" +If you want the time vector separately, type: +""" + +# ╔═╡ 54bedb4d-51c5-4f58-bc4b-50cc469b7b04 +osol.t + +# ╔═╡ 764e2f1a-f974-4916-8573-cacba897cf07 +md" +## More advanced examples +" + +# ╔═╡ 2ae76ddb-71f5-49d7-a250-429d6c0138f6 +md""" +In Example 1 we will show you one way of how you could analyze the simulation results for a limited range of parameter values. + +In Examples 2 and 3 we add **discrete** and **continuous** events. These are used to affect, e.g., one or more parameter values or state variables during the solving process based on one or more *conditions* (also called *events*). These conditions can be either *time* or *state variable* related: + +- A time related condition is a vector of one (or more) timepoint(s) for which the value of one (or more) parameter(s) or state variable(s) need to be altered. We refer to them as **discrete events**. +- A state variable related condition is usually a condition for a certain value of a state variable. We will refer to them as **continuous events**. +""" + +# ╔═╡ 590f1b49-7442-4a71-af8c-8acdea071448 +md""" +**Important remark:**\ +You may have noticed that while using the Pluto notebooks, when you change the value of some variable (e.g., a parameter or an initial condition) that your results/plots will subsequently and automatically be altered based on the currect variable values in memory. In some cases this can be advantageous, in others not. For the latter reason, in this notebook, we will use slightly different variable names for some variables in order not to alter other results. +""" + +# ╔═╡ 45c1c238-a9f7-4f7b-a0ce-07b5bb4768d4 +md""" +### Example 1 - Influence of $r$ + +Influence of the duration of infection $1/r$ for average infection periods of between $10$ days and $1$ day of being contagious. +""" + +# ╔═╡ ae38c663-0ee4-409e-bfca-5f13ed88b67d +md""" +We will create a new parameter value vector, ODE problem and solution object by putting `1` at the end of the corresponding variable names. In that way, the previous simulation results will be unaffected! The model, the initial conditions and the timespan are identical as before. For the value of parameter `r`, we define a slider a little further on. +""" + +# ╔═╡ b6baafc2-6d5e-43c3-8ef9-845961cdd20b +md""" +We will create a slider for the $r$-values between $0.1$ and $1.0$, stepsize $0.1$, default value $0.1$. +""" + +# ╔═╡ b65e948d-b13a-4021-a545-b7912fd86e94 +@bind r Slider(0.1:0.1:1, default=0.1, show_value=true) + +# ╔═╡ d44da6c6-c93d-4c61-8125-9eee464c897e +parms1 = [:α => 0.08, :β => 1.0e-6, :r => r, :m => 0.4] + +# ╔═╡ 00a72697-d36a-41cc-9eec-8e821829ce0e +# type a semi-colon at end of an instruction to avoid seeing its output +oprob1 = ODEProblem(infection_model, u0, tspan, parms1); + +# ╔═╡ cf39b4cf-9cd0-4755-80db-ca4aea7c1084 +osol1 = solve(oprob1, Tsit5(), saveat=0.5); + +# ╔═╡ 52901bbf-e47b-4da1-95c4-f0869812398c +plot(osol1, ylim=(0, 1e7)) + +# ╔═╡ 102b4fbc-23b1-46ed-bb72-124eb88517ce +md""" +Now, change the value of $r$ in the `param1` vector and analyze the effect in the plot. +""" + +# ╔═╡ a627e3c0-e29f-4592-9dc6-5cf48f80252d +md""" +!!! note + You can see here we use the value of `r` in the parameter vector **before** defining it in the slider. This is possible in Pluto because it knows what code cells depend on each other, and it will simply run the definition of `r` before running the code that uses `r`, even though the definition appears later in the notebook. +""" + +# ╔═╡ ade413c2-d7d9-4250-8490-75534900a389 +md""" +### Example 2 - Discrete Event + +Suppose that regulations are such that on day 14, people need to reduce their contacts by 50%. Hence, this means that the parameter value $\beta$ needs to be divided by a factor of 2 at timepoint 14. +""" + +# ╔═╡ 58730ac6-d83b-420d-a000-2f50545f0d39 +md""" +We need to state that the parameter $\beta$ needs to be reduced by $50\%$ at time $t=14$. We include this condition in a variable named `condition2` in the following way: +""" + +# ╔═╡ 7411474c-fac7-4b7c-8ded-4c2df5956fb0 +condition2 = [14.0] => [infection_model.β ~ infection_model.β/2] + +# ╔═╡ cf715eac-c2d0-4f4d-a274-61237e994dc5 +md""" +!!! note + Events only recognize symbolic variables, not variable names, so we need to specify `β` as `infection_model.β` (see the problem creation section). +""" + +# ╔═╡ e6949052-6d12-4414-ac67-cb9435b46290 +md""" +The discrete time event needs to be now included in our model. +""" + +# ╔═╡ 8752373f-a602-437b-9b3a-2602c8babf87 +@named infection_model2 = ReactionSystem(equations(infection_model), discrete_events=condition2) + +# ╔═╡ ee596a6c-29bf-4d18-b687-be943c128aa7 +md""" +After that, we need to *complete* our *reaction network model*, so that the model can be simulated. +""" + +# ╔═╡ 40f849cb-20f9-4bbe-806e-512abd6f3210 +infection_model2_com = complete(infection_model2) + +# ╔═╡ d4e537da-490b-4781-9a76-c89d762849ec +md""" +Then we need to create a new ODE problem. +""" + +# ╔═╡ 4a6f357c-afd7-4b0d-b1d2-15f5c0e4298b +oprob2 = ODEProblem(infection_model2_com, u0, tspan, parms); + +# ╔═╡ e76e77be-9f15-4aa5-8506-cdb32a6ec9b1 +md""" +Finally, the ODE problem can be solved. Notice that you need to make a deepcopy of the ODE problem, because otherwise changes to the parameter $\beta$ will remain after the first call to `solve`. +""" + +# ╔═╡ 37b11fdb-8c16-4ec4-a7ad-e2fdbaf4ea5d +osol2 = solve(deepcopy(oprob2), Tsit5(), saveat=0.5) + +# ╔═╡ 59f4f4dd-f0b5-4354-b53c-4554c878d5a8 +md""" +Now we can plot the results. +""" + +# ╔═╡ 0af84963-ca64-4958-a544-42d445da5a7c +# We can compare the result now to the solution without contact reduction +begin + plot(osol2) + plot!(osol; linestyle=:dash, label=:none, color=:grey, lw=0.5) +end + +# ╔═╡ 18151ab1-1e4e-48d8-be70-4fa4c8a43af1 +md""" +If you want to see the final values of $S$, $I$, $D$ and $R$, type: +""" + +# ╔═╡ 7d7ed974-7c9c-4c30-ab2a-e3028ce702dd +osol2.u[end] + +# ╔═╡ 7375edbc-24a4-4300-bdef-2686cb377cfc +md""" +!!! question + How do we interpret this event? Is the number of deceased and infections reduced? How much? +""" + +# ╔═╡ 6eb368f5-8d43-4fb8-b6eb-c783675d1dd9 +osol2[:D][end]/osol[:D][end] - 1 # The number of deceased is reduced 13% + +# ╔═╡ 247c3c88-7ba7-4db1-ad56-de1758851ade +osol2[:S][end]/osol[:S][end] - 1 # 6 times less infections with contact measures + +# ╔═╡ 7911963b-e663-4fbc-9113-98cc1a091628 +osol2[:R][end]/osol[:R][end] - 1 # 13% less recoveries (due to fewer infections) + +# ╔═╡ cb8a6f77-f08c-4fc8-9445-bd1c17521fcc +md""" +### Example 3 - Continuous Event + +Suppose that when the number of infected individuals reaches $1\,000\,000$, then $999\,000$ of them are promptly put into isolation (or removed from the population). Hence, a $1000$ individuals remain infected at some point. +""" + +# ╔═╡ 5005a09d-a844-4f9d-a058-0d93583d5bab +md""" +Normally in a continuous event the value of one or more species can be changed when a certain condition is met. In our specific case we want the change in the species happening only once! So, if you want that the continuous event "when $I$ reaches $10^6$ then $999\,000$ is subtracted from $I$" happens only once, then we need to include a ficticious new *species* in our *reaction network model*. We will call this ficticious species `thr` (a short for _**thr**eshold_) and we set its default value to `1e6`. +""" + +# ╔═╡ f77d2d75-468f-4746-b81e-0bbbf33fc8d7 +infection_model3 = @reaction_network begin + @species thr(t)=1e6 + α * β, S + I --> 2I + r * m, I --> D + r * (1 - m), I --> R +end + +# ╔═╡ 51f0f4b0-0332-416f-8ade-582fa4ba0257 +species(infection_model3) + +# ╔═╡ 30bcea84-f7ca-4907-9a0f-54934e1731d0 +md""" +We create the condition in the following way. When `thr` is `1e6` then $I$ will change at some point and also `thr` will become `1e9`, so that the condition happens only once. By the way, $I$ will never reach $1\,000\,000\,000$! +""" + +# ╔═╡ 87d9be87-3bc7-4442-a396-fb501355fe8c +condition3 = [infection_model3.I ~ infection_model3.thr] => [infection_model3.I ~ infection_model3.I - 999_000, infection_model3.thr ~ 1e9] + +# ╔═╡ 467834b5-0063-4e91-b446-2828a1d44d78 +md""" +The continuous event needs to be included in our model. +""" + +# ╔═╡ 09ec4a21-c8fd-42fe-ae0c-f3a548c7032c +@named infection_model3_c = ReactionSystem(equations(infection_model3), continuous_events=condition3) + +# ╔═╡ 7b93b776-caff-4608-9ec3-8519b9b95e51 +md""" +After that, we need to *complete* our *reaction network model*. +""" + +# ╔═╡ 96787705-f294-4f3c-9acf-8af7f6e0d579 +infection_model3_c_com = complete(infection_model3_c) + +# ╔═╡ 54d5f92c-de07-4c06-8bc5-e11e2b2c5764 +md""" +Then we need to create a new ODE problem. +""" + +# ╔═╡ e0562699-d065-4ae1-9c39-f9e0bf561fa3 +oprob3 = ODEProblem(infection_model3_c_com, u0, tspan, parms); + +# ╔═╡ 6b3ea888-1238-46ae-9eaf-52e86c76bc1c +md""" +Finally, the ODE problem can be solved. Notice that you need to make a deepcopy of the ODE problem again. +""" + +# ╔═╡ abe11178-7177-418c-98b4-da1afc56842e +osol3 = solve(deepcopy(oprob3), Tsit5(), saveat=0.5) + +# ╔═╡ 23804c2a-d031-4025-92d3-5cdb74f87353 +md""" +Now we can plot the results. +""" + +# ╔═╡ 02173eaa-1178-4383-b57a-03e53ce38af8 +begin + plot(osol3; idxs=[:S, :I, :D, :R]) + plot!(osol; linestyle=:dash, label=:none, color=:grey, lw=0.5) +end + +# ╔═╡ 6d1aa79b-8614-4a77-a327-f7e6962d1944 +md""" +If you want to see the final values of $S$, $I$, $D$, $R$ and `pwc`, type: +""" + +# ╔═╡ d04b8d97-e9d3-4428-a695-bfde4b44a291 +osol3.u[end] + +# ╔═╡ 70fd136e-5ded-4c30-818e-a5de61fbbf86 +md""" +!!! question + How do we interpret this new event? Is this a better measure than contact reduction alone? Would you know how we call such an event? +""" + +# ╔═╡ f6dd15c0-8aa0-424b-8134-45245a077186 +0.4*0.999e6 # Amount of people deceased during isolation (infected) + +# ╔═╡ 4bd15c53-44b7-42ad-b225-79a9d90b38a8 +(osol3[:D][end] + 0.4*0.999e6)/osol[:D][end] - 1 # Deceased are reduced only 1%! + +# ╔═╡ aeeb2fc7-fa7d-4a89-97d4-f2c12373bbdc +osol3[:S][end]/osol[:S][end] - 1 # 56% less infections (wrt. example 1) + +# ╔═╡ c8986056-3b29-4574-a43d-d0e051a4bee9 +(osol3[:R][end] + 0.6*0.999e6)/osol[:R][end] - 1 # 1% less recoveries + +# ╔═╡ f5d23dba-02a9-4d27-8eb1-ff89d2902cdf +md""" +- Answer: +""" + +# ╔═╡ ef982f58-9deb-4ac2-93d8-e7677332d80d +md""" +!!! hint + Isolation alone is not effective to protect the part of the population that's already been infected. The peak of infections is not avoided, only delayed. However, the proportion of the population exposed is much lower thanks to isolation. *Would then a combined set of rules be best in that case?* +""" + +# ╔═╡ Cell order: +# ╠═be326550-25ea-4c5f-ac4a-dd74d12bc89a +# ╠═e6e748a5-b692-4c7c-b39e-ef0fe9e46c35 +# ╠═3cfe381f-5871-4447-9149-fc023c787b42 +# ╟─9ea49d7a-e524-11ee-1b68-9d1d71aaba24 +# ╟─ba4dfc95-b74f-4d36-b34b-5eb2836a5cd6 +# ╟─94440e99-7c0b-4d87-8038-141a2bc5fcb8 +# ╟─83fa478c-ac54-482b-92de-8eb6a01ddfaf +# ╟─d257102b-8480-4ce3-bdba-50995ccbdc26 +# ╟─df9a46ab-56eb-4d3d-a3a8-e1f2da59b32e +# ╟─7dd37224-071b-4ec7-88d5-39fd4482e615 +# ╟─97d34ab6-a1c6-404d-af1f-7da85287757f +# ╟─b5e6cb25-da6f-475a-bc73-041c4c9256d5 +# ╟─529ddac3-bb7a-4cc3-9bbd-ca31f5796e27 +# ╟─f9ca0b86-1447-48c1-88e1-876180d7628a +# ╟─bf2b06d8-884b-4a35-b308-83a2f3d6696f +# ╟─1f405b09-53cd-43e0-b498-70eafef92acb +# ╟─39d80a3f-14d2-49d6-905b-171adf5930a0 +# ╟─44c6cdc0-8610-43bd-b3b3-a7441ee62615 +# ╠═b1bfe3b7-782c-46c9-86db-829942b6a9d0 +# ╟─cd3da548-b24f-426f-8b46-2db70e0a979c +# ╠═7d2f8bf9-3a00-4631-84ff-1a36b6d7e19b +# ╟─4a504e36-8269-4822-8919-75b7f6dcaf6d +# ╟─bb5578b8-40d4-48c9-be87-cfaeadb8c5f9 +# ╟─af6b169d-55c3-4384-9f57-ad629e02bad6 +# ╟─52b2cc8f-1eac-47c1-8d42-09c1b91d1350 +# ╠═5f1d6198-ccdc-4a63-bf98-2032a6d683ad +# ╟─59686253-3e0e-454e-8bc6-dde189ee2197 +# ╠═fdb79a26-fbc5-482a-8cdb-760d18f57a77 +# ╟─2bd02577-7321-46b5-9f42-1484ed86d1d3 +# ╠═64455b26-4374-435f-b71b-8b9600cab263 +# ╟─64a48327-03ec-46a0-9a76-79c10acb6e20 +# ╠═78a22ba2-1e60-40b3-989c-922dcf9ca054 +# ╟─eeb25e50-165b-4e93-8397-5b09fe8e7242 +# ╟─0d39fbbc-4fcb-45eb-b24b-14f2e093c98c +# ╠═ca18795a-d270-493f-807f-a3b8c78aa6d7 +# ╟─3f6f38a3-12ec-4b5b-8ebb-9e48190d3f7e +# ╠═72f7f870-b2fc-4df1-9325-3c587be7e014 +# ╟─da5cfd6e-b57a-4235-b79d-a1c2f148328b +# ╠═a09b3a39-bedc-4180-ae5b-8cec708a64cb +# ╟─7fc8c671-b75c-4487-a013-779ee2422c8b +# ╠═19398992-dca7-441a-a5fd-7f9d0ad30be5 +# ╟─3197244f-655b-4dca-80f3-794b30722551 +# ╟─c4e83ef8-9490-4361-a2a9-5abc45e242be +# ╟─a7a99fde-c1bd-484c-a7b6-a7d8af809ecc +# ╠═35bd9a1a-bb4a-4285-98f0-853b03c95cb7 +# ╠═a25d5925-a254-488b-b782-d29cff4470a2 +# ╠═9a9440fa-d8a3-44bc-8037-4bf1f8af40b0 +# ╠═c6d2dd69-8c61-4a40-894f-664b2d2d14be +# ╟─050a3786-5ce3-44c2-b2c2-94cb640326aa +# ╟─3c253bf3-886d-4e86-82ac-7751d23f342f +# ╟─14756171-9e8e-4cb0-b7af-74c2d649fe9f +# ╠═142e3e48-bf75-4498-ad0e-9f47cb921045 +# ╟─a3599781-a690-4fa3-b483-cd47727935cb +# ╟─8ac90e5c-ce85-4113-bc1b-9dc17cf7e928 +# ╟─0af3c166-46b6-455d-af6b-a72c4d2a5ce4 +# ╠═513c037b-c54c-47fa-b97a-06f69a983386 +# ╟─5672749b-283a-4e8e-8f90-76b5312b30ac +# ╠═01188934-ce0f-4d65-b880-caac4837d796 +# ╟─f3cf01a1-3c65-4a37-bf9a-cd2233cb470b +# ╠═ff8f4c23-5695-48aa-9965-02677103f2c9 +# ╟─b07f09c6-0515-4d27-9ca1-c45427a5988c +# ╠═ab4940b8-b8ec-4835-8d6e-4e57a5e2e464 +# ╟─a8b68546-9401-4815-9f33-cc22b7a0579d +# ╠═89cc8970-693d-4885-b0b5-46110db4ca32 +# ╟─dcdd693d-f2f3-406c-8647-31cdedcb823b +# ╠═0a2ed6a6-1535-4721-a6da-008f62b16942 +# ╟─757d9f1e-5cba-4ae8-9172-e0c2fceb8458 +# ╠═54bedb4d-51c5-4f58-bc4b-50cc469b7b04 +# ╟─764e2f1a-f974-4916-8573-cacba897cf07 +# ╟─2ae76ddb-71f5-49d7-a250-429d6c0138f6 +# ╟─590f1b49-7442-4a71-af8c-8acdea071448 +# ╟─45c1c238-a9f7-4f7b-a0ce-07b5bb4768d4 +# ╟─ae38c663-0ee4-409e-bfca-5f13ed88b67d +# ╠═d44da6c6-c93d-4c61-8125-9eee464c897e +# ╠═00a72697-d36a-41cc-9eec-8e821829ce0e +# ╠═cf39b4cf-9cd0-4755-80db-ca4aea7c1084 +# ╟─b6baafc2-6d5e-43c3-8ef9-845961cdd20b +# ╠═b65e948d-b13a-4021-a545-b7912fd86e94 +# ╠═52901bbf-e47b-4da1-95c4-f0869812398c +# ╟─102b4fbc-23b1-46ed-bb72-124eb88517ce +# ╟─a627e3c0-e29f-4592-9dc6-5cf48f80252d +# ╟─ade413c2-d7d9-4250-8490-75534900a389 +# ╟─58730ac6-d83b-420d-a000-2f50545f0d39 +# ╠═7411474c-fac7-4b7c-8ded-4c2df5956fb0 +# ╟─cf715eac-c2d0-4f4d-a274-61237e994dc5 +# ╟─e6949052-6d12-4414-ac67-cb9435b46290 +# ╠═8752373f-a602-437b-9b3a-2602c8babf87 +# ╟─ee596a6c-29bf-4d18-b687-be943c128aa7 +# ╠═40f849cb-20f9-4bbe-806e-512abd6f3210 +# ╟─d4e537da-490b-4781-9a76-c89d762849ec +# ╠═4a6f357c-afd7-4b0d-b1d2-15f5c0e4298b +# ╟─e76e77be-9f15-4aa5-8506-cdb32a6ec9b1 +# ╠═37b11fdb-8c16-4ec4-a7ad-e2fdbaf4ea5d +# ╟─59f4f4dd-f0b5-4354-b53c-4554c878d5a8 +# ╠═0af84963-ca64-4958-a544-42d445da5a7c +# ╟─18151ab1-1e4e-48d8-be70-4fa4c8a43af1 +# ╠═7d7ed974-7c9c-4c30-ab2a-e3028ce702dd +# ╟─7375edbc-24a4-4300-bdef-2686cb377cfc +# ╠═6eb368f5-8d43-4fb8-b6eb-c783675d1dd9 +# ╠═247c3c88-7ba7-4db1-ad56-de1758851ade +# ╠═7911963b-e663-4fbc-9113-98cc1a091628 +# ╟─cb8a6f77-f08c-4fc8-9445-bd1c17521fcc +# ╟─5005a09d-a844-4f9d-a058-0d93583d5bab +# ╠═f77d2d75-468f-4746-b81e-0bbbf33fc8d7 +# ╠═51f0f4b0-0332-416f-8ade-582fa4ba0257 +# ╟─30bcea84-f7ca-4907-9a0f-54934e1731d0 +# ╠═87d9be87-3bc7-4442-a396-fb501355fe8c +# ╟─467834b5-0063-4e91-b446-2828a1d44d78 +# ╠═09ec4a21-c8fd-42fe-ae0c-f3a548c7032c +# ╟─7b93b776-caff-4608-9ec3-8519b9b95e51 +# ╠═96787705-f294-4f3c-9acf-8af7f6e0d579 +# ╟─54d5f92c-de07-4c06-8bc5-e11e2b2c5764 +# ╠═e0562699-d065-4ae1-9c39-f9e0bf561fa3 +# ╟─6b3ea888-1238-46ae-9eaf-52e86c76bc1c +# ╠═abe11178-7177-418c-98b4-da1afc56842e +# ╟─23804c2a-d031-4025-92d3-5cdb74f87353 +# ╠═02173eaa-1178-4383-b57a-03e53ce38af8 +# ╟─6d1aa79b-8614-4a77-a327-f7e6962d1944 +# ╠═d04b8d97-e9d3-4428-a695-bfde4b44a291 +# ╟─70fd136e-5ded-4c30-818e-a5de61fbbf86 +# ╠═f6dd15c0-8aa0-424b-8134-45245a077186 +# ╠═4bd15c53-44b7-42ad-b225-79a9d90b38a8 +# ╠═aeeb2fc7-fa7d-4a89-97d4-f2c12373bbdc +# ╠═c8986056-3b29-4574-a43d-d0e051a4bee9 +# ╟─f5d23dba-02a9-4d27-8eb1-ff89d2902cdf +# ╟─ef982f58-9deb-4ac2-93d8-e7677332d80d diff --git a/src/exercises/ode_model_fermenter_monod.jl b/src/exercises/ode_model_fermenter_monod.jl new file mode 100644 index 00000000..7822089f --- /dev/null +++ b/src/exercises/ode_model_fermenter_monod.jl @@ -0,0 +1,470 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "11" +#> title = "2. ODE_model_fermenter_monod" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Fermenter with biomass growing on substrate through Monod kinetics" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ e99680dc-73af-40aa-bf57-a06d3a7372be +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 2e58f4ae-f711-11ee-2598-7f3a6f2e2013 +using Markdown, InteractiveUtils + +# ╔═╡ e6bf6f7e-f56d-4ee1-89ec-9224ef4444a6 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 7906cea8-e163-42cf-adbc-7eec7adc5597 +using OrdinaryDiffEq, Catalyst + +# ╔═╡ 7856d878-8586-4cfd-9cf6-d61234450e41 +md" +# Exercise: Fermenter - Monod kinetics +" + +# ╔═╡ 7bbab2e5-0ea9-4682-ad7c-a98a5218c7ca +md""" +![](https://users.ugent.be/~gvhaelew/fig/fermenter_model_2nd.png) +""" + +# ╔═╡ 8500c35e-6bc3-4900-81bf-7705ddd61532 +md""" +In a fermenter reactor biomass grows on substrate. The reactor is fed with a inlet flow rate $Q_{in} \, [L/h],$ which consist of a (manipulable) input concentration of substrate $S_{in} \, [g/L].$ Inside the reactor, biomass, with a concentration of $X \, [g/L]$, is produced through **Monod** kinetics: + +$$\begin{eqnarray*} +S + X \xrightarrow[\quad\quad]{k} (1 + Y) \, X \quad\quad\quad\quad \textrm{with} \quad k = \cfrac{\mu_{max}}{S + K_s} \, . +\end{eqnarray*}$$ +Note that we also make the assumption that the reaction kinetics follow the [rate equation](https://en.wikipedia.org/wiki/Rate_equation), and therefore the reaction's speed is also first order in $S$ and $X$. + +The quantity + +$$\mu = k\,S = \mu_{max} \, \cfrac{S}{S + K_s}$$ + +is called the specific growth rate $[h^{-1}]$. Therein, $\mu_{max}$ is the maximum specific growth rate, and $K_s \, [g/L]$ is the so-called *half-velocity constant* (i.e. the value of $S$ when $\mu/\mu_{max} = 0.5$). Note that in Catalyst you can write this as `mm(S, μmax, Ks)`, where the function `mm` stands for the Michaelis-Menten kinetics, which is equivalent to Monod kinetics. + +Futhermore, $Y \, [gX/gS]$ is the yield coefficient which is defined here by the amount of produced biomass by consumption of one unit of substrate. The reactor is drained with an outlet flow $Q \, [L/h]$, which consist of the current concentrations of substrate $S \, [g/L]$ and biomass $X \, [g/L]$ inside the reactor. The volume $V \, [L]$ of the reactor content is kept constant by setting $Q_{in} = Q$. +""" + +# ╔═╡ f1350528-07a5-4860-ad2d-627588186abc +md""" +Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of substrate $S$ and biomass $X$ with time. Name it `fermenter_monod`. +""" + +# ╔═╡ 331a34f4-89d4-4193-896c-c14ab0bf04e7 +# fermenter_monod = @reaction_network begin +# @species missing +# @parameters missing +# missing # When S and X meet, then Y*X + X are created +# missing # S is created at a rate Q/V*Sin +# missing # S is degraded at a rate Q/V*S +# missing # X is degraded at a rate Q/V*X +# end + +# ╔═╡ 55746566-2d46-4475-851a-02b7fad87a1a +md""" +Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. + +Keep in mind that `mm(S, μmax, Ks)` stands for $\mu_{max} \, \cfrac{S}{S + K_s}$. +""" + +# ╔═╡ ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac +osys = missing + +# ╔═╡ 67117a27-dcea-4b43-b962-9ad9fd07f4f4 +md""" +The parameter values are $\mu_{max} = 0.40$, $K_s = 0.015$, $Y = 0.67$, $Q = 2.0$, $V = 40.0$ and $S_{in} = 0.02\;g/L$. Suppose that at $t=0\;h$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concetration of $0.0005\;g/L$. Simulate the evolution of $S$ and $X$ during $200$ hours. +""" + +# ╔═╡ d13e6e38-037e-4812-85e9-2c18bed360f6 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ 4b556cf0-8fad-434d-be56-dc1848d898ae +u0 = missing + +# ╔═╡ ea55d648-7575-43c3-a385-5f4979996ef2 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ 1365c12e-e662-4858-983b-02ba94cd9f0d +tspan = missing + +# ╔═╡ 3941bd60-a83c-4f72-84b3-28e28cb845d0 +md""" +Initialize a vector `parms` with the parameter values: +""" + +# ╔═╡ d6c1316a-cf96-43d1-854a-f25925cf4a55 +parms = missing + +# ╔═╡ 4926b941-c3b6-4804-b4a4-11e13e5186f2 +md""" +Create the ODE problem and store it in `oprob`: +""" + +# ╔═╡ ab2a9842-6a9c-46bd-812b-db01629d6a1c +oprob = missing; + +# ╔═╡ b6a526bd-6ee5-442b-9fb8-3fbe1e280dd4 +md""" +## Part 1 + +Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol1`: +""" + +# ╔═╡ 1f62e66d-571f-41ca-9f02-f36a8ca10ab9 +osol1 = missing + +# ╔═╡ 37ced6e3-b435-4546-a720-a1ec1af23a65 +md""" +Plot the results: +""" + +# ╔═╡ d609bed4-94cf-4167-80fe-924501a5835c +missing + +# ╔═╡ 3c638fc4-8ebc-4e26-984e-b4513035287e +md""" +Inspect the final values in both the $S$ and $X$ vector.\ +Tip: use something like: `(osol1[...][...], osol1[...][...])` +""" + +# ╔═╡ 25ef069e-5a08-4e85-acea-a5b64e0890f6 +# (osol1[...][...], osol1[...][...]) + +# ╔═╡ d8234337-7516-4817-8ce5-194af694e3e3 +md""" +We will now create a `SteadyStateProblem` to determine the steady state values for $S$ and $X$ under the current conditions (cf. current initial values and current parameter values). +""" + +# ╔═╡ b63fa707-6023-44fc-aac9-25c76e075f90 +md""" +Initialize a vector `u_guess1` with the final values for $S$ and $X$: +""" + +# ╔═╡ 8d96d79f-6ddb-4bba-a6ea-821588e13107 +u_guess1 = [:S => missing, :X => missing] + +# ╔═╡ e8969045-27ac-460e-86a2-7494903534e8 +md""" +Make and solve the SteadyStateProblem. Use `u_guess1` as initial conditions and the parameter values previously defined. The outputs are the steady state values for $S$ and $X$ which we have denoted as `Seq1` and `Xeq1`. +""" + +# ╔═╡ 1777503e-b793-4be2-b80b-b4edcd7041b5 +equil_val_monod = missing + +# ╔═╡ b9b9c5f0-02fa-4544-8865-a07271e8941f +Seq1 = missing + +# ╔═╡ 0ddf9ea1-3f45-48b1-bb9c-5a08df45bd5b +Xeq1 = missing + +# ╔═╡ 1d5a2118-96e9-49b2-9931-5d4b201cb8f5 +md""" +Next, we can just inspect these values: +""" + +# ╔═╡ cb57997e-c3ec-47e0-b9a0-b10aa9f5608d +missing + +# ╔═╡ 7ae28a79-921f-4bda-bdf8-c3122c787f4f +md""" +!!! questions + 1. Explain why $S$ first increases and then decreases while $X$ only increases during the first 50 hours. + 2. What are the steady state values of $S$ and $X$. +""" + +# ╔═╡ 92e868a3-0537-496f-9fbd-8272dca9a6a2 +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ 9bb450c6-5499-42f6-8356-bdc4985b74e7 +md""" +## Part 2 + +Suppose that the substrate inlet concentration $S_{in}$ suddenly increases to $0.022\;g/L$ at $t = 100\;h$. Simulate the evolution of $S$ and $X$. +""" + +# ╔═╡ 0298953a-b90c-41cd-8613-cb47ce752e43 +md""" +Create the *condition* that contains the timepoint for the sudden change in $S_{in}$. Store it in `condition2`: +""" + +# ╔═╡ c85e505d-99c6-4616-b7c1-42c05b4894fc +condition2 = missing + +# ╔═╡ 7c96bead-9f7b-4e84-abb9-9b6651208667 +md""" +Make a new *reaction system* where the discrete event is included. Name it `fermenter_monod2`. +""" + +# ╔═╡ 439dbeef-55b9-4fa4-aef5-fa0bb5a2ccf1 +# @named fermenter_monod2 = missing + +# ╔═╡ 43614c69-3fb5-4bef-b2a2-9805a5545fb8 +md""" +Complete the new *reaction system*. Name it `fermenter_monod2_com`. +""" + +# ╔═╡ 532306b5-2a71-4fcf-93ac-9dc52457a3f9 +fermenter_monod2_com = missing + +# ╔═╡ 9e8f4500-0b6a-47f0-a1f5-a74daea9d117 +md""" +Create the ODE problem and store it in `oprob2`: +""" + +# ╔═╡ 7af72709-2f82-4971-8342-f02943f947c8 +oprob2 = missing; + +# ╔═╡ e019f797-a6ad-4f8f-8f9e-69db00ed3c39 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol2`: +""" + +# ╔═╡ 5f77450b-aa96-41b0-8017-a3d29fd7023a +osol2 = missing + +# ╔═╡ 310a78a5-94ce-4a29-b7a1-37831ce5c64e +md""" +Plot the results: +""" + +# ╔═╡ 85742dc1-24cb-42d1-a70b-70f1be6b6c1e +missing + +# ╔═╡ 7f358845-c9fc-4e77-9882-94506f9338d6 +md""" +Calculate the state state values for $S$ and $X$. +""" + +# ╔═╡ 1102146d-abbe-43ce-9602-c863d1a91071 +md""" +Inspect the final values in both the $S$ and $X$ vector.\ +Tip: use something like: `(osol2[...][...], osol2[...][...])` +""" + +# ╔═╡ 196edf3a-b220-4a54-8137-b136b509617e +# (osol2[...][...], osol2[...][...]) + +# ╔═╡ 3c7bf6f2-4ffd-4678-a930-94cf1322ba9f +md""" +Initialize a vector `u_guess2` with the final values for $S$ and $X$: +""" + +# ╔═╡ 56eaa343-03b6-4cae-868b-e5c36ac66546 +u_guess2 = missing + +# ╔═╡ 4d8c05d8-ee29-4cb8-84cd-4342cb1db289 +md""" +Initialize a vector `parms_mod` with the parameter values. Notice that all parameter values will be the same, **except** the one of $S_{in}$. +""" + +# ╔═╡ f121efc5-4e64-4e82-8672-2765ad85443e +parms_mod = missing + +# ╔═╡ 9fe054e0-cf21-49ba-a777-a8200b34b7dd +md""" +Make and solve the steady state problem. Call the output values `Seq2` and `Xeq2`. +""" + +# ╔═╡ 0b992750-a446-447b-b2a1-26658c11c0bf +eq2 = missing + +# ╔═╡ 7b985909-bc75-4c03-8949-b689e90c9fea +Seq2 = missing + +# ╔═╡ c4417b5c-a7cf-497b-8b7a-2a90ebe428f2 +Xeq2 = missing + +# ╔═╡ 4ed59602-ad9d-4aae-8a21-83dabbfd3846 +md""" +Inspect those values. +""" + +# ╔═╡ 47a63fd8-f805-4c7d-8695-c9ee6550f24f +missing + +# ╔═╡ 231e294c-affe-4024-be03-a795cc28d42a +md""" +!!! questions + 1. Can you clearly see the effect of the increase in $S_{in}$? + 2. Find the steady state values of $S$ and $X$. Is the steady state value of $S$ influenced by the increase of $S_{in}$? Show how you can deduce that from the differential equations. + 3. Can you explain why $X$ increased permanently? +""" + +# ╔═╡ c20abc19-e6ce-4046-a85a-a96657da41c7 +md""" +Answers: +1. missing +2. missing +3. missing +""" + +# ╔═╡ 53980767-a84f-44f2-a878-2a7d57e0e2ae +md""" +## Part 3 + +Suppose that the inlet/outlet flow $Q$ is suddenly doubled at $t = 100\;h$. Simulate the evolution of $S$ and $X$. +""" + +# ╔═╡ 31b64d91-f8ff-413a-9c0c-402fe2215a81 +md""" +Create the *condition* that contains the timepoint for the sudden change in $Q$. Store it in `condition3`: +""" + +# ╔═╡ 6e771231-abf1-41c0-9aa8-ac7220f2a9cd +condition3 = missing + +# ╔═╡ 97b0de4f-c300-44f1-97fc-804d3263d8b5 +md""" +Make a new *reaction system* where the discrete event is included. Name it `fermenter_monod3`. +""" + +# ╔═╡ 837e27f1-a2d8-4d2c-aaa5-e82b6761e4fd +# @named fermenter_monod3 = missing + +# ╔═╡ d8002843-03c6-4fa4-b8e5-b42eac27588c +md""" +Complete the new *reaction system*. Name it `fermenter_monod3_com`. +""" + +# ╔═╡ 508f1dfe-3a92-4d80-b48d-e84a8738f97f +fermenter_monod3_com = missing + +# ╔═╡ 5d4c2573-4e57-455b-bdcc-1cee79b08ce2 +md""" +Create the ODE problem and store it in `oprob3`: +""" + +# ╔═╡ dd388e88-53af-48d3-800e-09b5c182a83b +oprob3 = missing; + +# ╔═╡ 5133d846-e6a6-4b50-9ce1-cb91cf04cbd1 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol3`: +""" + +# ╔═╡ b3bc3348-a524-41e6-9fdb-865055246cd9 +osol3 = missing + +# ╔═╡ 39ef225d-3222-4998-bfd4-5ff88f74a0f9 +md""" +Plot the results: +""" + +# ╔═╡ 76849cf5-170b-452b-acdd-c4017feaad18 +missing + +# ╔═╡ bcdede6d-f259-4f3f-85d4-99d8faf85030 +md""" +!!! questions + Interpret the results. Ask yourself the following questions: + 1. Can you clearly see the effect of doubling of $Q$? + 2. Can you argue, by means of reasoning, why $S$ increases and $X$ decreases? +""" + +# ╔═╡ 0317615c-2663-475b-a2a2-b2886ea07fa4 +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ Cell order: +# ╟─7856d878-8586-4cfd-9cf6-d61234450e41 +# ╠═2e58f4ae-f711-11ee-2598-7f3a6f2e2013 +# ╠═e99680dc-73af-40aa-bf57-a06d3a7372be +# ╠═e6bf6f7e-f56d-4ee1-89ec-9224ef4444a6 +# ╠═7906cea8-e163-42cf-adbc-7eec7adc5597 +# ╟─7bbab2e5-0ea9-4682-ad7c-a98a5218c7ca +# ╟─8500c35e-6bc3-4900-81bf-7705ddd61532 +# ╟─f1350528-07a5-4860-ad2d-627588186abc +# ╠═331a34f4-89d4-4193-896c-c14ab0bf04e7 +# ╟─55746566-2d46-4475-851a-02b7fad87a1a +# ╠═ec9cb3bd-f5ed-4ab0-9b3d-b875692227ac +# ╟─67117a27-dcea-4b43-b962-9ad9fd07f4f4 +# ╟─d13e6e38-037e-4812-85e9-2c18bed360f6 +# ╠═4b556cf0-8fad-434d-be56-dc1848d898ae +# ╟─ea55d648-7575-43c3-a385-5f4979996ef2 +# ╠═1365c12e-e662-4858-983b-02ba94cd9f0d +# ╟─3941bd60-a83c-4f72-84b3-28e28cb845d0 +# ╠═d6c1316a-cf96-43d1-854a-f25925cf4a55 +# ╟─4926b941-c3b6-4804-b4a4-11e13e5186f2 +# ╠═ab2a9842-6a9c-46bd-812b-db01629d6a1c +# ╟─b6a526bd-6ee5-442b-9fb8-3fbe1e280dd4 +# ╠═1f62e66d-571f-41ca-9f02-f36a8ca10ab9 +# ╟─37ced6e3-b435-4546-a720-a1ec1af23a65 +# ╠═d609bed4-94cf-4167-80fe-924501a5835c +# ╟─3c638fc4-8ebc-4e26-984e-b4513035287e +# ╠═25ef069e-5a08-4e85-acea-a5b64e0890f6 +# ╟─d8234337-7516-4817-8ce5-194af694e3e3 +# ╟─b63fa707-6023-44fc-aac9-25c76e075f90 +# ╠═8d96d79f-6ddb-4bba-a6ea-821588e13107 +# ╟─e8969045-27ac-460e-86a2-7494903534e8 +# ╠═1777503e-b793-4be2-b80b-b4edcd7041b5 +# ╠═b9b9c5f0-02fa-4544-8865-a07271e8941f +# ╠═0ddf9ea1-3f45-48b1-bb9c-5a08df45bd5b +# ╟─1d5a2118-96e9-49b2-9931-5d4b201cb8f5 +# ╠═cb57997e-c3ec-47e0-b9a0-b10aa9f5608d +# ╟─7ae28a79-921f-4bda-bdf8-c3122c787f4f +# ╠═92e868a3-0537-496f-9fbd-8272dca9a6a2 +# ╟─9bb450c6-5499-42f6-8356-bdc4985b74e7 +# ╟─0298953a-b90c-41cd-8613-cb47ce752e43 +# ╠═c85e505d-99c6-4616-b7c1-42c05b4894fc +# ╟─7c96bead-9f7b-4e84-abb9-9b6651208667 +# ╠═439dbeef-55b9-4fa4-aef5-fa0bb5a2ccf1 +# ╟─43614c69-3fb5-4bef-b2a2-9805a5545fb8 +# ╠═532306b5-2a71-4fcf-93ac-9dc52457a3f9 +# ╟─9e8f4500-0b6a-47f0-a1f5-a74daea9d117 +# ╠═7af72709-2f82-4971-8342-f02943f947c8 +# ╟─e019f797-a6ad-4f8f-8f9e-69db00ed3c39 +# ╠═5f77450b-aa96-41b0-8017-a3d29fd7023a +# ╟─310a78a5-94ce-4a29-b7a1-37831ce5c64e +# ╠═85742dc1-24cb-42d1-a70b-70f1be6b6c1e +# ╟─7f358845-c9fc-4e77-9882-94506f9338d6 +# ╟─1102146d-abbe-43ce-9602-c863d1a91071 +# ╠═196edf3a-b220-4a54-8137-b136b509617e +# ╟─3c7bf6f2-4ffd-4678-a930-94cf1322ba9f +# ╠═56eaa343-03b6-4cae-868b-e5c36ac66546 +# ╟─4d8c05d8-ee29-4cb8-84cd-4342cb1db289 +# ╠═f121efc5-4e64-4e82-8672-2765ad85443e +# ╟─9fe054e0-cf21-49ba-a777-a8200b34b7dd +# ╠═0b992750-a446-447b-b2a1-26658c11c0bf +# ╠═7b985909-bc75-4c03-8949-b689e90c9fea +# ╠═c4417b5c-a7cf-497b-8b7a-2a90ebe428f2 +# ╟─4ed59602-ad9d-4aae-8a21-83dabbfd3846 +# ╠═47a63fd8-f805-4c7d-8695-c9ee6550f24f +# ╟─231e294c-affe-4024-be03-a795cc28d42a +# ╠═c20abc19-e6ce-4046-a85a-a96657da41c7 +# ╟─53980767-a84f-44f2-a878-2a7d57e0e2ae +# ╟─31b64d91-f8ff-413a-9c0c-402fe2215a81 +# ╠═6e771231-abf1-41c0-9aa8-ac7220f2a9cd +# ╟─97b0de4f-c300-44f1-97fc-804d3263d8b5 +# ╠═837e27f1-a2d8-4d2c-aaa5-e82b6761e4fd +# ╟─d8002843-03c6-4fa4-b8e5-b42eac27588c +# ╠═508f1dfe-3a92-4d80-b48d-e84a8738f97f +# ╟─5d4c2573-4e57-455b-bdcc-1cee79b08ce2 +# ╠═dd388e88-53af-48d3-800e-09b5c182a83b +# ╟─5133d846-e6a6-4b50-9ce1-cb91cf04cbd1 +# ╠═b3bc3348-a524-41e6-9fdb-865055246cd9 +# ╟─39ef225d-3222-4998-bfd4-5ff88f74a0f9 +# ╠═76849cf5-170b-452b-acdd-c4017feaad18 +# ╟─bcdede6d-f259-4f3f-85d4-99d8faf85030 +# ╠═0317615c-2663-475b-a2a2-b2886ea07fa4 diff --git a/src/exercises/ode_model_infection.jl b/src/exercises/ode_model_infection.jl new file mode 100644 index 00000000..7c42b7bc --- /dev/null +++ b/src/exercises/ode_model_infection.jl @@ -0,0 +1,845 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "12" +#> title = "2. ODE_model_infection" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Infection model built as a reaction network" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 989fd8c8-25d9-47b9-ade6-6c7f21a7dceb +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 18a4df05-0349-400d-a29e-b3fa71aa4d88 +using Markdown, InteractiveUtils + +# ╔═╡ e968f6d5-962c-4de8-a9a3-ec116805d5e1 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ e97637b0-446c-44ac-bd08-c56632f9b57f +using OrdinaryDiffEq, Catalyst + +# ╔═╡ c54c7e3c-f3d1-40a5-88f7-bedcab3269ba +md" +# Exercises - infection model +" + +# ╔═╡ 6b7d1263-fa96-47a7-b19c-4a518fd974d0 +md""" +![](https://users.ugent.be/~gvhaelew/fig/infection_model.png) +""" + +# ╔═╡ fab49cb7-c41e-498a-98f6-33b4821ceb90 +md""" +We will work here with the same infection model as in the **Introdution to Catalyst** (revisit the concerned notebook if necessary). We shortly summarize some important aspects of the model and give a condensed version of the solution method and the examples. +""" + +# ╔═╡ 8ff0b57e-acc1-4ebd-a562-a82c9efa7ffc +md""" +The **state variables**: + +| Variable | Unit | Meaning | +|:---------- |:---------- |:------------| +| ``S`` | *persons* | number of susceptible persons | +| ``I`` | *persons* | number of infected persons | +| ``D`` | *persons* | number of deceased persons | +| ``R`` | *persons* | number of recovered persons | +""" + +# ╔═╡ e36b150a-b4fc-476f-bab7-e1162a4b263d +md""" +The **parameters**: + +| Variable | Unit | Meaning | +|:---------- |:---------- |:------------| +| ``\alpha`` | ``\frac{persons}{contact}`` | chances of getting infected after contact | +| ``\beta`` | ``\frac{contact}{persons^2\,day}`` | contact rate | +| ``r`` | ``\frac{1}{day}`` | rate of leaving infection period | +| ``m`` | ``\frac{person}{person}`` | fraction of persons deceasing | +| ``1-m`` | ``\frac{person}{person}`` | fraction of persons becoming resistant | +""" + +# ╔═╡ bdefa657-88bd-43d6-8e54-cc6321a9e720 +md""" +The infection model has three reaction events: + +- **Infection**, where a susceptible persons meets an infected person and also becomes infected. The infection rate is $\alpha \beta$. +- **Decease**, where an infected person dies. The death rate is $m r$. +- **Recovery**, where an infected person recovers. The recovery rate is $(1-m) r$. +""" + +# ╔═╡ afb751b9-39b8-4430-af4b-02f010667518 +md""" +The involved reactions are: + +$$S + I \xrightarrow[]{\alpha \beta} 2I$$ +$$I \xrightarrow[]{mr} D$$ +$$I \xrightarrow[]{(1-m)r} R$$ +""" + +# ╔═╡ a91f1024-4800-4ffe-8bb2-5f6cf3c2bde1 +md""" +## Examples +""" + +# ╔═╡ 47f4a7cc-1a74-4ff0-ae21-2a5e4c495935 +md" +### Implementation of the system +" + +# ╔═╡ 13d94ba6-425e-471b-aa99-2f321891d1f0 +md""" +Implement the *reaction model* in Catalyst: +""" + +# ╔═╡ 3f6080e1-ab43-47f7-a82b-95956bf4cafd +infection_model = @reaction_network begin + α * β, S + I --> 2I + r * m, I --> D + r * (1 - m), I --> R +end + +# ╔═╡ 5d5fae40-a4ed-4908-ba65-f3a16c38ab4f +md""" +The species: +""" + +# ╔═╡ fd347a58-5cad-4073-aadb-176fa54dcdfa +species(infection_model) + +# ╔═╡ bd5bd448-8741-4528-989f-9754b0e55b9b +md""" +Alternatively: +""" + +# ╔═╡ a78b178e-6952-4057-bd8c-6d5b0c5e0518 +@unpack S, I, D, R = infection_model; + +# ╔═╡ cbfe8833-786c-48f7-b849-705006d4c41d +md""" +The parameters: +""" + +# ╔═╡ 96f3dd68-da80-4f07-af2b-e3b9ec05cc0c +parameters(infection_model) + +# ╔═╡ cc4d514a-dfed-4424-9982-8315954b38ff +md" +Convert the *reaction model* if you want to see the symbolic differential equation model: +" + +# ╔═╡ 3aad1f32-7daf-49a5-9dd3-9ddbba1a50de +osys = convert(ODESystem, infection_model) + +# ╔═╡ a432a88a-8db3-4729-b9a3-8ca9322fc81e +md""" +We can get a list of the differential equations, the state variables and the parameters: +""" + +# ╔═╡ 7e268605-8314-4e7b-8c38-a65212e148a9 +equations(osys) + +# ╔═╡ 0707b599-c1ca-459b-b87b-956f8f49564b +unknowns(osys) + +# ╔═╡ 113ff67e-6dc8-45cb-9617-7768e132f6e9 +parameters(osys) + +# ╔═╡ 39e396e1-20fa-4e78-b0ab-99774ce55f0f +md""" +### Simulating the system as an ODE-problem +""" + +# ╔═╡ 4a581cce-9a1b-4516-9cf1-6a41ea6566e7 +md""" +### Setting initial conditions, timespan and parameter values +""" + +# ╔═╡ c5ee2780-c652-4c9b-981b-53e5f91e1766 +u0 = [:S => 9_999_000.0, :I => 1_000.0, :D => 0.0, :R => 0.0] + +# ╔═╡ fc380000-33a0-419d-9226-7a0c136de8c6 +tspan = (0.0, 90.0) + +# ╔═╡ a8756838-93d7-4909-84c2-6d89be4fa711 +parms = [:α => 0.08, :β => 1.0e-6, :r => 0.2, :m => 0.4] + +# ╔═╡ 6d2580f7-f882-449d-a9b8-629ab41e2671 +md""" +### Creating and solving the ODE problem and plotting results +""" + +# ╔═╡ d3f39b29-71f5-4674-a52f-3ba63279fca7 +oprob = ODEProblem(infection_model, u0, tspan, parms) + +# ╔═╡ 6173506b-8f56-4100-a0a0-117c1ee8aa0f +osol = solve(oprob, Tsit5(), saveat=0.5) + +# ╔═╡ e6b17756-a7e9-4f50-98dc-b69f9a54903f +plot(osol) + +# ╔═╡ 006bfc98-3c95-4763-b0ed-f4714ad5bd02 +plot(osol, idxs=[:S, :I]) # only S and I +# plot(osol, idxs=[S, I]) # also possible when S and I are unpacked + +# ╔═╡ 4c5d2e5d-3237-4886-b852-3142fb996dd6 +plot(osol, idxs=(:S, :I), xlab="S", ylab="I") # phase plot I vs S + +# ╔═╡ f9bd32f5-c473-44d4-af9d-4d99032a2d21 +osol.u[end] # final values in the order of defined species + +# ╔═╡ 000495a6-e91d-438e-ab6d-7a5e95a99f27 +md""" +### Example 1 - Influence of $r$ + +Influence of the duration of infection, $1/r$, for average infection periods between $1$ and $10$ days of being contagious ($r$ between $0.1$ and $1.0$, with a step of $0.1$, and default value $0.1$). +""" + +# ╔═╡ d73f8ed8-ac25-4c1c-b834-989a2a929df3 +@bind r Slider(0.1:0.1:1, default=0.1, show_value=true) + +# ╔═╡ 4ca0e1df-7765-4f36-a442-2749ea493426 +parms1 = [:α => 0.08, :β => 1.0e-6, :r => r, :m => 0.4] # r specified by slider + +# ╔═╡ d56b486e-44e5-4db7-9c4e-00db5c1b733f +oprob1 = ODEProblem(infection_model, u0, tspan, parms1); + +# ╔═╡ 298ada89-4581-41c5-a89f-f01ae431c5ec +osol1 = solve(oprob1, Tsit5(), saveat=0.5); + +# ╔═╡ 5b4ca642-0f89-40ab-9a2f-c35fc603a91d +plot(osol1, ylim=(0, 1e7)) + +# ╔═╡ 171208da-69a3-4737-85e0-fae0805bbaa8 +md""" +Change the value of $r$ in the `params1` vector to visualize the effect in the plot. +""" + +# ╔═╡ 98aedcf5-e0de-442f-b19b-c5b3d9e3d475 +md""" +### Example 2 - Discrete event + +Suppose that regulations are such that on day 14, people need to reduce their contacts by 50%. +""" + +# ╔═╡ 462b2786-98d6-40cb-887d-aff6b63881e8 +condition2 = [14.0] => [infection_model.β ~ infection_model.β/2] + +# ╔═╡ efd74885-c25c-42e5-bef5-c9654329c220 +@named infection_model2 = ReactionSystem(equations(infection_model), discrete_events=condition2) + +# ╔═╡ ecf20050-12d3-48e7-84f1-6919a9aab865 +infection_model2_com = complete(infection_model2) + +# ╔═╡ fdea5a92-e115-4dbe-a885-95772857c686 +oprob2 = ODEProblem(infection_model2_com, u0, tspan, parms) + +# ╔═╡ 35d6bce7-9778-4f42-bdcd-271cedd8bad9 +osol2 = solve(deepcopy(oprob2), Tsit5(), saveat=0.5) + +# ╔═╡ 31ceb303-dbb1-4f4d-9082-14ea1d83ab66 +# We can compare the result now to the solution without contact reduction +begin + plot(osol2) + plot!(osol; linestyle=:dash, label=:none, color=:grey, lw=0.5) +end + +# ╔═╡ 14143212-53f1-4753-8c25-e22559cdbccd +osol2.u[end] + +# ╔═╡ 9c668254-3c54-4d95-9e1e-e2bd2d3eb1df +md""" +!!! question + How do we interpret this event? Is the number of deceased and infections reduced? How much? +""" + +# ╔═╡ 130a6ce2-dd8c-4a05-8863-953a77a407aa +osol2[:D][end]/osol[:D][end] - 1 # The number of deceased is reduced 13% + +# ╔═╡ 9abcc2f9-5c24-4840-8a14-fa7a197ffef0 +osol2[:S][end]/osol[:S][end] - 1 # 6 times less infections with contact measures + +# ╔═╡ 1f970a9e-abde-422b-a793-eb51ad2001bc +osol2[:R][end]/osol[:R][end] - 1 # 13% less recoveries (due to fewer infections) + +# ╔═╡ bdf9cfa0-9dc4-4ea5-9024-63813ec30d09 +md""" +### Example 3 - Continuous event + +Suppose that when the number of infected individuals reaches $1\,000\,000$, then $999\,000$ of them are promptly put into isolation (or removed from the population). +""" + +# ╔═╡ cbf0a768-f6ac-43fb-8d05-e7dac51d4cc0 +infection_model3 = @reaction_network begin + @species thr(t)=1e6 + α * β, S + I --> 2I + r * m, I --> D + r * (1 - m), I --> R +end + +# ╔═╡ fee597e0-97cb-40bc-a5b9-166631e8b9f6 +condition3 = [infection_model3.I ~ infection_model3.thr] => [infection_model3.I ~ infection_model3.I - 999_000, infection_model3.thr ~ 1e9] + +# ╔═╡ 51363f3c-7aa9-48ed-808e-d1f7a4aadc0c +@named infection_model3_c = ReactionSystem(equations(infection_model3), continuous_events=condition3) + +# ╔═╡ cd060e93-7a92-4824-b812-080391fbf554 +infection_model3_c_com = complete(infection_model3_c) + +# ╔═╡ a41878a2-2fd3-4a2a-ba85-d2a09c67da33 +oprob3 = ODEProblem(infection_model3_c_com, u0, tspan, parms) + +# ╔═╡ fc2dc11e-7022-4c6f-b7ed-5b077fc21ac5 +osol3 = solve(deepcopy(oprob3), Tsit5(), saveat=0.5) + +# ╔═╡ 2b511237-7739-44e5-b6bd-26a2fdc76bb5 +begin + plot(osol3; idxs=[:S, :I, :D, :R]) + plot!(osol; linestyle=:dash, label=:none, color=:grey, lw=0.5) +end + +# ╔═╡ 3a131f92-aa09-4e8c-98a1-0c4c1c74878a +osol3.u[end] + +# ╔═╡ 96792abd-2b86-4245-aaaf-15c43b4eabb0 +md""" +!!! question + How do we interpret this new event? Is this a better measure than contact reduction alone? Would you know how we call such an event? +""" + +# ╔═╡ 1116b608-e6b9-4e6d-a0eb-1ac58ef9e2f7 +0.4*0.999e6 # Amount of people deceased during isolation (infected) + +# ╔═╡ 4111ae36-ddef-42a8-b8a9-61f9f464a5c0 +(osol3[:D][end] + 0.4*0.999e6)/osol[:D][end] - 1 # Deceased are reduced only 1%! + +# ╔═╡ 6c8a9e97-0a60-4e42-ad7f-78c0dece2b30 +(osol3[:S][end] + 0.999e6)/osol[:S][end] - 1 # Infections have increased 56% (wrt. example 1) + +# ╔═╡ 801a038b-7485-40b6-b7a5-92b0b3c11c0e +(osol3[:R][end] + 0.6*0.999e6)/osol[:R][end] - 1 # 1% less recoveries + +# ╔═╡ 62adbc28-cec2-4da8-9842-2153563497d0 +md""" +- Answer: +""" + +# ╔═╡ 3bc2d36a-6efc-4631-baae-0d3e62bf7e3f +md""" +!!! hint + Isolation alone is not effective to protect the part of the population that's already been infected. The peak of infections is not avoided, only delayed. However, the proportion of the population exposed is much lower thanks to isolation. *Would then a combined set of rules be best in that case?* +""" + +# ╔═╡ ad83a851-e424-4e8f-bae9-38b9283db2fa +md""" +## Exercises +""" + +# ╔═╡ b580ef9b-7d53-4bf0-8511-213920d59ee2 +md""" +### Exercise 1 - Influence of $\alpha$ + +Evaluate the effect of a decreasing risk of infection after contact with an infected person, i.e. $r = 0.2$, $\beta = 10^{-6}$ and $\alpha$ between $8\%$ and $20\%$. + +Use the same initial values and timespan as before. +""" + +# ╔═╡ 090a949f-c18f-4e07-8c94-21a71bb0ceed +md""" +Make a slider for $\alpha$ in the range of $0.08$ and $0.20$ with a step of $0.02$. Take a default value of $0.08$. +""" + +# ╔═╡ 85e81822-2ea6-4d05-b879-1791bff255b9 +missing + +# ╔═╡ a2a109e3-ea74-4e74-91d0-6ea12ddf28d4 +md" +Initialize vector `parms_ex1` with parameter values: +" + +# ╔═╡ 27a4c4f5-0b2b-4d23-9bb0-a1a8ff6cfc4d +parms_ex1 = missing + +# ╔═╡ e0a8a397-4500-47de-8be3-49d3174648b1 +md""" +Create the ODE problem and store it in `oprob_ex1`: +""" + +# ╔═╡ 7955722d-fc35-4cce-8bf2-ee40ee7fbc82 +oprob_ex1 = missing; + +# ╔═╡ 0590bc0b-45d6-4a8e-8078-af057984523f +md""" +Solve the ODE problem and store the solution in `osol_ex1`: +""" + +# ╔═╡ f7b09e96-cf30-4bd0-841b-46621df693ab +osol_ex1 = missing; + +# ╔═╡ e7947165-b244-4ff6-bdf3-61d03aefe696 +md""" +Plot the solutions: +""" + +# ╔═╡ adc0aa15-e0a9-4549-9609-967a9dc78b78 +missing + +# ╔═╡ 7a1effe4-776c-4143-8085-f98a213a2cc3 +md""" +Change the value of $\alpha$ in the `params_ex1` vector to visualize the effect in the plot. +""" + +# ╔═╡ f85bd6be-8984-49f0-a36b-7aa7bb937c54 +md""" +!!! warning "Tip" + You can move the cell containing the slider next to the figure to visualize the changes in $\alpha$. +""" + +# ╔═╡ c14d45b8-3af1-4e05-b10a-081ccd62a34d +md""" +Try to interpret the results yourself. +Ask yourself the following questions: +!!! question + 1. What are the trends in the obtained results? +""" + +# ╔═╡ 04f89fb3-f856-4206-9871-4d92f2816332 +md""" +- Answer: +""" + +# ╔═╡ a39eeec9-d816-4d35-90ae-e602a24bb056 +md""" +!!! question + 2. How can this be explained from the model structure? +""" + +# ╔═╡ fcad8ede-8295-46d6-a637-728b3986a761 +md""" +- Answer: +""" + +# ╔═╡ b3f08d61-187d-4f99-b95f-508ff1b3d52d +md""" +### Exercise 2 - Administration of medicinal products + +Scientists have developed a medicine that heals sick people and makes them immune to +the disease. After administering medication, the infection duration is reduced to two +days. All treated patients heal and acquire immunity to the virus. The model will have +to be extended with two additional parameters. + +- Parameter $b$: the fraction of infected persons undergoing treatment. +- Parameter $h$: the rate at which the infected persons treated are no longer contagious ($day^{-1}$). + +Administering the drug to a fraction of the infected individuals affects two *reactions*: $I \rightarrow D$ and $I \rightarrow R$, with the following assumptions: + +- The fraction of infected persons treated ($b$) has a reduced infection duration. +- The fraction of infected individuals not receiving treatment ($1 − b$) still has the same duration of infection. +- The mortality rate $m$ only affects the group of sick people who were not given any medication. +- All treated individuals recover. +- A fraction of the untreated individuals also heals. + +Check the effect on the epidemic when $0\%$, $25\%$, $50\%$, $75\%$ and $100\%$ of infected individuals are treated with $h = 0.5$. Use the same initial conditions and timespan as before. +""" + +# ╔═╡ 08ba4614-e4fa-4203-9e28-c5c4bd9fcdd5 +md""" +Set-up the new *reaction network/model* and name it `infection_med`: +""" + +# ╔═╡ e4087441-7d0d-4716-b9fa-eef45e9f3b87 +# infection_med = @reaction_network begin # Uncomment and complete the instruction +# α * β, S + I --> 2I +# ..., I --> D +# ..., I --> R +# ..., I --> R +# end + +# ╔═╡ 87b3796d-16c5-4fe9-a32e-d8bf7c754254 +md""" +Convert to an ODE system. Check the differential equations and make sure you understand each term. +""" + +# ╔═╡ 2a66417b-79a2-4909-bd89-5650c49b9411 +osys_ex2 = missing + +# ╔═╡ 35956a99-5729-4bf7-bf09-b2232fc958da +md""" +Set-up parameter values: +""" + +# ╔═╡ cc31567f-631b-41b9-aa2d-3408ca951bcf +parms_ex2 = missing + +# ╔═╡ 82f8c50c-511d-4399-ac87-0890b678c6c1 +md""" +Create the ODE problem and store it in `oprob_ex2`: +""" + +# ╔═╡ ec08adb2-561c-4f17-a0b5-019d7b1f1098 +oprob_ex2 = missing; + +# ╔═╡ c55ec157-6ec8-45db-9a54-0f39aceb9607 +md""" +Solve the ODE problem and store the solution in `osol_ex2`: +""" + +# ╔═╡ 67668918-624e-4adf-be5d-6fdbc62555f6 +osol_ex2 = missing; + +# ╔═╡ 832f984a-176d-4187-8476-5026d29a63d8 +md""" +Plot the solutions: +""" + +# ╔═╡ 5f9a9838-099d-457f-9402-915f42d0cd33 +missing + +# ╔═╡ ef3ca25d-c110-452d-877a-6304ae6cd5a7 +md""" +Change the value of $b$ in the `params_ex2` vector to visualize the effect in the plot. Interpret the obtained plots. +""" + +# ╔═╡ f8dbab24-55aa-466f-a978-5f8db93b4b93 +md""" +Try to answer the following questions: + +!!! question + Why does the peak in the number of infected individuals shift to the right when the value of $b$ increases? +""" + +# ╔═╡ 381d3a54-0e4f-4203-874f-4a4716d481d4 +md""" +- Answer: +""" + +# ╔═╡ dedb1ac1-cb54-416f-a092-12a50080c69f +md""" +Check the number of fatalities: +""" + +# ╔═╡ a912f449-6c9d-4595-863f-a2ba523bad95 +missing # ...% less deaths for b = 0.2 + +# ╔═╡ 089707c3-1df6-4799-a87b-0d4872a0267d +md""" +### Exercise 3 - Adding vaccination to the model + +Scientists have developed a vaccine that makes healthy people immediately immune to +the disease. + +Vaccination affects several differential equations: +- Susceptible individuals are vaccinated at a rate of $v$ (with unit $day^{-1}$). These persons can therefore no longer be infected. +- The vaccinated persons become resistant. + +We are going to use a vaccination rate $v$ so that the number of fatalities is about 10 times smaller after a period of 90 days compared to those in absence of vaccination (cf. Exercise 2). + +The vaccination programme is launched $2$ days after the outbreak of the disease. + +Assume that individuals are still being treated ($b = 0.2$ and $h = 0.5$). Extend the model obtained in the previous exercise for the launch of a vaccination campaign after the outbreak of the disease. + +Find out via trial and error what the minimum vaccination rate need to be so that the number of fatalities is $10$ times smaller after a period of $90$ days compared to those in absence of vaccination (cf. Exercise 2). + +Use the same initial values and timespan as before. +""" + +# ╔═╡ 381a714d-699e-45c6-909a-02689b2a7e6b +md""" +Set-up the new *reaction network/model* and name it `infection_med_vac`: +""" + +# ╔═╡ a1795123-876b-4bd3-ac3a-711367b500d1 +# Uncomment and complete the instruction +# infection_med_vac = @reaction_network begin +# α * β, S + I --> 2I +# ..., I --> D +# ..., I --> R +# ..., I --> R +# ..., ... --> ... +# end + +# ╔═╡ 6b46d116-0da3-428a-b25f-221622dfa7e0 +md""" +Convert to an ODE system. Check the differential equations and make sure you understand each term. +""" + +# ╔═╡ 710fe13c-bd43-4d1e-867e-72330722ac1e +osys_ex3 = missing + +# ╔═╡ 2ee20493-2dc2-4bd4-8ec3-78032f1fcc2d +md""" +Make a slider and bind it to the variable `v`. Use a range $[0.0, 0.1]$, step size $0.001$ and default value of $0.0$. +""" + +# ╔═╡ 2943b515-fb4b-45a2-88dd-d0267ec95b09 +missing + +# ╔═╡ 0274fbd8-c025-4e36-bbb0-f65f21b962c7 +md""" +Set-up parameter values: +""" + +# ╔═╡ 34c4d746-45f7-43c4-a0f0-6e570373a35d +parms_ex3 = missing + +# ╔═╡ 060a5c5b-9834-42e2-98e9-ac23e7403b60 +md""" +Create the ODE problem and store it in `oprob_ex3`: +""" + +# ╔═╡ c61ae8b6-2324-43fa-a5ae-274a919af559 +oprob_ex3 = missing; + +# ╔═╡ 69983b90-b6ef-4732-a4af-a772bb1364e5 +md""" +Solve the ODE problem for (step wise) increasing values of $v$ and store the solution in `osol_ex3_vac`. +""" + +# ╔═╡ 16211bc6-cd97-43c0-8faf-25bb490930b6 +osol_ex3_no_vac = missing; + +# ╔═╡ 461eada9-5f9c-4439-8b65-226382b6d148 +md""" +First, put the value of $b$ in Exercise 2 to $0.2$. Compare the latter with the number of fatalities when no vaccination is/was available (cf. Exercise 2) by setting up a condition (a boolean expression returning either `true` or `false`) here below where the final number of fatalities (with vaccination) divided by 10 is compared with (use larger than or smaller than) the number of fatalities (without vaccination): +""" + +# ╔═╡ 0994d790-0fe8-46ab-bba2-a0a4ea466f64 +missing + +# ╔═╡ 8bb5dafd-6c26-4634-8be4-c2963efba056 +md""" +Once you have found the required value of $v$ launch the vaccination programme $2$ days after the outbreak. Set-up the $2$-day time condition and store it in `condition_ex3`: +""" + +# ╔═╡ 26c77206-fe3c-45c5-a627-f2b346c66be7 +condition_ex3 = missing + +# ╔═╡ 527e55a8-1744-4c40-a412-054bed6d77f0 +md""" +Make a new *reaction system* where the discrete event is included. Name it `infection_med_vac_c`. +""" + +# ╔═╡ ceae9947-ed0f-4975-a944-cc938cf22dde +# @named infection_med_vac_c = missing + +# ╔═╡ 6e767e36-f54e-4472-881a-ab04ad9c9d09 +md""" +Complete the new *reaction system*. Name it `infection_med_vac_c_com`. +""" + +# ╔═╡ 870c9d65-2229-40b7-9959-59afecb4f3bf +infection_med_vac_c_com = missing + +# ╔═╡ 93ca3970-bebd-4192-bc62-6ca08d58a6c2 +md""" +Create the ODE problem and store it in `oprob_ex3_c`: +""" + +# ╔═╡ 7e4114d6-932a-4def-9bb5-b534072ea513 +oprob_ex3_c = missing + +# ╔═╡ 1bf8bc58-6848-4783-a614-7ac11646de92 +md""" +Solve the ODE problem. Make a deepcopy and use `Tsit5()` and `saveat=0.5`. Store the solution in `osol_ex3`. +""" + +# ╔═╡ 8814c42b-16c7-466a-8ad8-58ee1c0921b2 +osol_ex3 = missing; + +# ╔═╡ 25158ea5-1930-4fee-aab4-490b475d8635 +md""" +Plot the solutions: +""" + +# ╔═╡ ed9472a0-09c9-4d1c-8f1d-1b7d87b97640 +missing + +# ╔═╡ 6e213afb-fd4b-4ab9-8986-a319bee6be0c +md""" +Check the number of fatalities now and compare to the case without vaccination. +""" + +# ╔═╡ 3af06996-bd43-4783-a5e0-3c11c23ca280 +missing + +# ╔═╡ 93400573-ad1f-408d-b514-854998e7836a +md""" +!!! question + What can you say about the rate of infection in the case of no vaccination? How would you measure this in the plot? +""" + +# ╔═╡ 008cc499-fce9-43e7-b969-2419b847d24c +md""" +- Answer: +""" + +# ╔═╡ 14c3b2f1-14b4-415c-85c6-922361423826 +md""" +!!! hint + The rate of infection has to do with how many people get infected during a certain period... +""" + +# ╔═╡ Cell order: +# ╟─c54c7e3c-f3d1-40a5-88f7-bedcab3269ba +# ╠═18a4df05-0349-400d-a29e-b3fa71aa4d88 +# ╠═989fd8c8-25d9-47b9-ade6-6c7f21a7dceb +# ╠═e968f6d5-962c-4de8-a9a3-ec116805d5e1 +# ╠═e97637b0-446c-44ac-bd08-c56632f9b57f +# ╟─6b7d1263-fa96-47a7-b19c-4a518fd974d0 +# ╟─fab49cb7-c41e-498a-98f6-33b4821ceb90 +# ╟─8ff0b57e-acc1-4ebd-a562-a82c9efa7ffc +# ╟─e36b150a-b4fc-476f-bab7-e1162a4b263d +# ╟─bdefa657-88bd-43d6-8e54-cc6321a9e720 +# ╟─afb751b9-39b8-4430-af4b-02f010667518 +# ╟─a91f1024-4800-4ffe-8bb2-5f6cf3c2bde1 +# ╟─47f4a7cc-1a74-4ff0-ae21-2a5e4c495935 +# ╟─13d94ba6-425e-471b-aa99-2f321891d1f0 +# ╠═3f6080e1-ab43-47f7-a82b-95956bf4cafd +# ╟─5d5fae40-a4ed-4908-ba65-f3a16c38ab4f +# ╠═fd347a58-5cad-4073-aadb-176fa54dcdfa +# ╟─bd5bd448-8741-4528-989f-9754b0e55b9b +# ╠═a78b178e-6952-4057-bd8c-6d5b0c5e0518 +# ╟─cbfe8833-786c-48f7-b849-705006d4c41d +# ╠═96f3dd68-da80-4f07-af2b-e3b9ec05cc0c +# ╟─cc4d514a-dfed-4424-9982-8315954b38ff +# ╠═3aad1f32-7daf-49a5-9dd3-9ddbba1a50de +# ╟─a432a88a-8db3-4729-b9a3-8ca9322fc81e +# ╠═7e268605-8314-4e7b-8c38-a65212e148a9 +# ╠═0707b599-c1ca-459b-b87b-956f8f49564b +# ╠═113ff67e-6dc8-45cb-9617-7768e132f6e9 +# ╟─39e396e1-20fa-4e78-b0ab-99774ce55f0f +# ╟─4a581cce-9a1b-4516-9cf1-6a41ea6566e7 +# ╠═c5ee2780-c652-4c9b-981b-53e5f91e1766 +# ╠═fc380000-33a0-419d-9226-7a0c136de8c6 +# ╠═a8756838-93d7-4909-84c2-6d89be4fa711 +# ╟─6d2580f7-f882-449d-a9b8-629ab41e2671 +# ╠═d3f39b29-71f5-4674-a52f-3ba63279fca7 +# ╠═6173506b-8f56-4100-a0a0-117c1ee8aa0f +# ╠═e6b17756-a7e9-4f50-98dc-b69f9a54903f +# ╠═006bfc98-3c95-4763-b0ed-f4714ad5bd02 +# ╠═4c5d2e5d-3237-4886-b852-3142fb996dd6 +# ╠═f9bd32f5-c473-44d4-af9d-4d99032a2d21 +# ╟─000495a6-e91d-438e-ab6d-7a5e95a99f27 +# ╠═d73f8ed8-ac25-4c1c-b834-989a2a929df3 +# ╠═4ca0e1df-7765-4f36-a442-2749ea493426 +# ╠═d56b486e-44e5-4db7-9c4e-00db5c1b733f +# ╠═298ada89-4581-41c5-a89f-f01ae431c5ec +# ╠═5b4ca642-0f89-40ab-9a2f-c35fc603a91d +# ╟─171208da-69a3-4737-85e0-fae0805bbaa8 +# ╟─98aedcf5-e0de-442f-b19b-c5b3d9e3d475 +# ╠═462b2786-98d6-40cb-887d-aff6b63881e8 +# ╠═efd74885-c25c-42e5-bef5-c9654329c220 +# ╠═ecf20050-12d3-48e7-84f1-6919a9aab865 +# ╠═fdea5a92-e115-4dbe-a885-95772857c686 +# ╠═35d6bce7-9778-4f42-bdcd-271cedd8bad9 +# ╠═31ceb303-dbb1-4f4d-9082-14ea1d83ab66 +# ╠═14143212-53f1-4753-8c25-e22559cdbccd +# ╟─9c668254-3c54-4d95-9e1e-e2bd2d3eb1df +# ╠═130a6ce2-dd8c-4a05-8863-953a77a407aa +# ╠═9abcc2f9-5c24-4840-8a14-fa7a197ffef0 +# ╠═1f970a9e-abde-422b-a793-eb51ad2001bc +# ╟─bdf9cfa0-9dc4-4ea5-9024-63813ec30d09 +# ╠═cbf0a768-f6ac-43fb-8d05-e7dac51d4cc0 +# ╠═fee597e0-97cb-40bc-a5b9-166631e8b9f6 +# ╠═51363f3c-7aa9-48ed-808e-d1f7a4aadc0c +# ╠═cd060e93-7a92-4824-b812-080391fbf554 +# ╠═a41878a2-2fd3-4a2a-ba85-d2a09c67da33 +# ╠═fc2dc11e-7022-4c6f-b7ed-5b077fc21ac5 +# ╠═2b511237-7739-44e5-b6bd-26a2fdc76bb5 +# ╠═3a131f92-aa09-4e8c-98a1-0c4c1c74878a +# ╟─96792abd-2b86-4245-aaaf-15c43b4eabb0 +# ╠═1116b608-e6b9-4e6d-a0eb-1ac58ef9e2f7 +# ╠═4111ae36-ddef-42a8-b8a9-61f9f464a5c0 +# ╠═6c8a9e97-0a60-4e42-ad7f-78c0dece2b30 +# ╠═801a038b-7485-40b6-b7a5-92b0b3c11c0e +# ╠═62adbc28-cec2-4da8-9842-2153563497d0 +# ╟─3bc2d36a-6efc-4631-baae-0d3e62bf7e3f +# ╟─ad83a851-e424-4e8f-bae9-38b9283db2fa +# ╟─b580ef9b-7d53-4bf0-8511-213920d59ee2 +# ╟─090a949f-c18f-4e07-8c94-21a71bb0ceed +# ╠═85e81822-2ea6-4d05-b879-1791bff255b9 +# ╟─a2a109e3-ea74-4e74-91d0-6ea12ddf28d4 +# ╠═27a4c4f5-0b2b-4d23-9bb0-a1a8ff6cfc4d +# ╟─e0a8a397-4500-47de-8be3-49d3174648b1 +# ╠═7955722d-fc35-4cce-8bf2-ee40ee7fbc82 +# ╟─0590bc0b-45d6-4a8e-8078-af057984523f +# ╠═f7b09e96-cf30-4bd0-841b-46621df693ab +# ╟─e7947165-b244-4ff6-bdf3-61d03aefe696 +# ╠═adc0aa15-e0a9-4549-9609-967a9dc78b78 +# ╟─7a1effe4-776c-4143-8085-f98a213a2cc3 +# ╟─f85bd6be-8984-49f0-a36b-7aa7bb937c54 +# ╟─c14d45b8-3af1-4e05-b10a-081ccd62a34d +# ╠═04f89fb3-f856-4206-9871-4d92f2816332 +# ╟─a39eeec9-d816-4d35-90ae-e602a24bb056 +# ╠═fcad8ede-8295-46d6-a637-728b3986a761 +# ╟─b3f08d61-187d-4f99-b95f-508ff1b3d52d +# ╟─08ba4614-e4fa-4203-9e28-c5c4bd9fcdd5 +# ╠═e4087441-7d0d-4716-b9fa-eef45e9f3b87 +# ╟─87b3796d-16c5-4fe9-a32e-d8bf7c754254 +# ╠═2a66417b-79a2-4909-bd89-5650c49b9411 +# ╟─35956a99-5729-4bf7-bf09-b2232fc958da +# ╠═cc31567f-631b-41b9-aa2d-3408ca951bcf +# ╟─82f8c50c-511d-4399-ac87-0890b678c6c1 +# ╠═ec08adb2-561c-4f17-a0b5-019d7b1f1098 +# ╟─c55ec157-6ec8-45db-9a54-0f39aceb9607 +# ╠═67668918-624e-4adf-be5d-6fdbc62555f6 +# ╟─832f984a-176d-4187-8476-5026d29a63d8 +# ╠═5f9a9838-099d-457f-9402-915f42d0cd33 +# ╟─ef3ca25d-c110-452d-877a-6304ae6cd5a7 +# ╟─f8dbab24-55aa-466f-a978-5f8db93b4b93 +# ╠═381d3a54-0e4f-4203-874f-4a4716d481d4 +# ╟─dedb1ac1-cb54-416f-a092-12a50080c69f +# ╠═a912f449-6c9d-4595-863f-a2ba523bad95 +# ╟─089707c3-1df6-4799-a87b-0d4872a0267d +# ╟─381a714d-699e-45c6-909a-02689b2a7e6b +# ╠═a1795123-876b-4bd3-ac3a-711367b500d1 +# ╟─6b46d116-0da3-428a-b25f-221622dfa7e0 +# ╠═710fe13c-bd43-4d1e-867e-72330722ac1e +# ╟─2ee20493-2dc2-4bd4-8ec3-78032f1fcc2d +# ╠═2943b515-fb4b-45a2-88dd-d0267ec95b09 +# ╟─0274fbd8-c025-4e36-bbb0-f65f21b962c7 +# ╠═34c4d746-45f7-43c4-a0f0-6e570373a35d +# ╟─060a5c5b-9834-42e2-98e9-ac23e7403b60 +# ╠═c61ae8b6-2324-43fa-a5ae-274a919af559 +# ╟─69983b90-b6ef-4732-a4af-a772bb1364e5 +# ╠═16211bc6-cd97-43c0-8faf-25bb490930b6 +# ╟─461eada9-5f9c-4439-8b65-226382b6d148 +# ╠═0994d790-0fe8-46ab-bba2-a0a4ea466f64 +# ╟─8bb5dafd-6c26-4634-8be4-c2963efba056 +# ╠═26c77206-fe3c-45c5-a627-f2b346c66be7 +# ╟─527e55a8-1744-4c40-a412-054bed6d77f0 +# ╠═ceae9947-ed0f-4975-a944-cc938cf22dde +# ╟─6e767e36-f54e-4472-881a-ab04ad9c9d09 +# ╠═870c9d65-2229-40b7-9959-59afecb4f3bf +# ╟─93ca3970-bebd-4192-bc62-6ca08d58a6c2 +# ╠═7e4114d6-932a-4def-9bb5-b534072ea513 +# ╟─1bf8bc58-6848-4783-a614-7ac11646de92 +# ╠═8814c42b-16c7-466a-8ad8-58ee1c0921b2 +# ╟─25158ea5-1930-4fee-aab4-490b475d8635 +# ╠═ed9472a0-09c9-4d1c-8f1d-1b7d87b97640 +# ╟─6e213afb-fd4b-4ab9-8986-a319bee6be0c +# ╠═3af06996-bd43-4783-a5e0-3c11c23ca280 +# ╟─93400573-ad1f-408d-b514-854998e7836a +# ╠═008cc499-fce9-43e7-b969-2419b847d24c +# ╟─14c3b2f1-14b4-415c-85c6-922361423826 diff --git a/src/exercises/ode_model_mtk_intro.jl b/src/exercises/ode_model_mtk_intro.jl index 882e581d..5accbc15 100644 --- a/src/exercises/ode_model_mtk_intro.jl +++ b/src/exercises/ode_model_mtk_intro.jl @@ -12,7 +12,9 @@ using Markdown using InteractiveUtils # ╔═╡ 1c46508e-2354-11f0-08db-d373e43929bd -using Pkg; Pkg.activate("..") +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") # ╔═╡ 2d125aad-334c-4210-a921-1acacefc5cfa using StatsPlots, PlutoUI; TableOfContents() From f0a7c7378c4932c2e1ab30e4522a19f6c3508105 Mon Sep 17 00:00:00 2001 From: jarjarbinks02 Date: Thu, 13 Aug 2026 14:12:30 +0200 Subject: [PATCH 3/5] adding practical 3 --- src/exercises/dje_model_bike_sharing.jl | 338 ++++++++++++++++ src/exercises/dje_model_catalyst_intro.jl | 372 ++++++++++++++++++ src/exercises/dje_model_festival_toilet.jl | 283 +++++++++++++ src/exercises/sde_model_aging_mtk.jl | 358 +++++++++++++++++ .../sde_model_fermenter_secondorder_mtk.jl | 281 +++++++++++++ src/exercises/sde_model_heston_mtk_intro.jl | 364 +++++++++++++++++ 6 files changed, 1996 insertions(+) create mode 100644 src/exercises/dje_model_bike_sharing.jl create mode 100644 src/exercises/dje_model_catalyst_intro.jl create mode 100644 src/exercises/dje_model_festival_toilet.jl create mode 100644 src/exercises/sde_model_aging_mtk.jl create mode 100644 src/exercises/sde_model_fermenter_secondorder_mtk.jl create mode 100644 src/exercises/sde_model_heston_mtk_intro.jl diff --git a/src/exercises/dje_model_bike_sharing.jl b/src/exercises/dje_model_bike_sharing.jl new file mode 100644 index 00000000..093a5ebc --- /dev/null +++ b/src/exercises/dje_model_bike_sharing.jl @@ -0,0 +1,338 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "21" +#> title = "3. DJE_model_bike_sharing" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Discrete jump model of a simple bike sharing system" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 309035dd-5653-48a6-a53d-817e743279fa +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 6b342f14-e7d5-11ef-1ea0-77ceb0d78f32 +using Markdown, InteractiveUtils + +# ╔═╡ 71140c81-af29-4857-8020-4f94c8bd64b3 +using Catalyst, JumpProcesses, StatsPlots, StatsBase + +# ╔═╡ 284f5847-9c15-41f3-a595-1e12a22df69f +using PlutoUI; TableOfContents() + +# ╔═╡ 1f975552-b0b8-4830-8dcc-214574d4fc38 +md""" +# Exercise: Modeling a simple Bike Sharing System +""" + +# ╔═╡ a0da1455-bf8c-45d2-893a-f9b5c279cb59 +# https://www.rete8.it/wp-content/uploads/2016/04/ciclostazione-777x437.jpg +md""" +![Bike sharing station](https://www.rete8.it/wp-content/uploads/2016/04/ciclostazione-777x437.jpg) +""" + +# ╔═╡ d2f32eab-0b35-4794-9219-5bcbb4c069c5 +md""" +Imagine a bike sharing system for students traveling between Olin College and Wellesley College, which are about three miles apart in eastern Massachusetts. Suppose the system contains 12 bikes and two bike racks, one at Olin and one at Wellesley, each with the capacity to hold 12 bikes. As students arrive, check out a bike, and ride to the other campus, the number of bikes in each location changes. +Initially there are `10` bikes at Olin and, hence, `2` bikes at Wellesley. For this simple model, we will also assume that the changes in the number of bikes at both locations is instantaneous. The rate at which a bike is moved from Olin to Wellesley is denoted as $p_1$ ($\#bikes\;min^{-1}$); the rate at which a bike is moved from Wellesley to Olin is denoted as $p_2$ ($\#bikes\;min^{-1}$). Both processes are zeroth-order and we want to see the evolution of bikes during $1\,h = 60\,min$. + +This is a discreet and stochastic problem and you need to solve it with SSA. +""" + +# ╔═╡ 016842c9-9479-4061-a27e-9dc006121f23 +md""" +Create a *reaction network object* model for the aforementioned problem in order to simulate the evolution of the number of bikes at Olin ($O$) and Wellesley ($W$) with time. Name it `bike_sharing`. + +In order to make sure that $O$ does not become negative, you can use either `ifelse(O>0, 1, 0)` or `>(O, 0)` as a multiplication factor to the rate `p₁`. A similar multiplication factor must be applied to the other rate `p₂`. +""" + +# ╔═╡ 6c97bf81-ef32-45a4-aa7c-c8c26ba2d2c3 +# bike_sharing = @reaction_network begin +# @species missing +# missing +# missing +# end + +# ╔═╡ 7227a95a-ba0c-44dc-b0b8-18d6bbf362e8 +md""" +!!! tip "Tip" + Subscripts 1, 2, etc, can be visualized by typing, after the letter, a **backslash** followed by an **underscore** and then the **TAB** key. For example `p\_1` followed by the TAB key will result in `p₁`. +""" + +# ╔═╡ 9c7ab7fb-7380-41a3-85ea-714478ade218 +md""" +Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. +""" + +# ╔═╡ 1536fe23-0f8d-4b86-98d2-076248b35954 +missing + +# ╔═╡ 613d7769-cc9d-471e-b3e7-4266fc0e4677 +md""" +## Part 1: Simulation for different p₁ values +""" + +# ╔═╡ 6614bdde-fed0-45aa-b7ca-a21ca72dd181 +md""" +Simulate the evolution of $O$ and $W$ for values of `p₁` in the range `[0.0, 1.0]` with a stepsize of `0.1` using a slider. +""" + +# ╔═╡ e6e2ff5c-38eb-4ba3-b430-c9031483a0a5 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ ab6af765-1cde-4da8-bbc1-a5fab391db54 +u0 = missing + +# ╔═╡ 378878a0-5c09-4eb0-ac43-1031014ff12a +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ 3ae98e83-7beb-4597-89be-80c813d4349b +tspan = missing #floats! + +# ╔═╡ 988f79c0-9c7b-4752-a7f2-d4473ad73ce6 +md""" +Create a slider for the variable `p₁` in the range of `0.0` and `1.0` with a step of `0.1`. Take a default value of `0.0`. +""" + +# ╔═╡ 0d8f53f8-0a14-4ac6-bd0c-2190d4db0909 +# @bind missing + +# ╔═╡ 08d43ac8-a973-4d7b-baf7-4c37e54cfe24 +md" +Initialize vector `parms` with parameter values, `p₁` is the slider value and assign a constant value of `0.3` to `p₂`. +" + +# ╔═╡ e20e4dd8-bdbb-4005-af68-6bf7e4ec130e +parms = missing + +# ╔═╡ 238e1120-34af-4d57-8efa-aa80ab28a874 +md""" +Create a DiscreteProblem and store it in `dprob`: +""" + +# ╔═╡ d4c45709-70c9-4ba0-8fb8-6b600473723d +dprob = missing + +# ╔═╡ d06fb076-76e4-4248-a940-96804ea68833 +md""" +Create a JumpProblem and store it in `jdprob`. Use the simulation method `Direct()`.""" + +# ╔═╡ 7644adf4-d992-48b1-b40a-12fdf30f6cb5 +jdprob = missing + +# ╔═╡ 74708270-b1ec-48c7-af32-3b970b92c706 +md""" +Solve the problem and store it in `jdsol`. +""" + +# ╔═╡ 2b00df5d-994e-47a1-8068-c93ce3f1a618 +jdsol = missing + +# ╔═╡ 9d06c31e-3525-4889-a1de-3fe02413c7d8 +md""" +Plot the solution. Limit the plot to `(0, 12)` for the vertical axis. +""" + +# ╔═╡ 9a90f800-3669-4831-b50b-c5405bbb9a03 +missing + +# ╔═╡ a554fd16-aa3d-48ca-8de6-5582725c27d8 +md""" +Analyse the results. See what happens when you: +- run the notebook cell with the `solve` function repeatly +- change the value of `p₁` using the slider +""" + +# ╔═╡ d6872046-b5ef-4c2d-a9bb-2418f57f715d +md""" +!!! question "Question" + From what value of `p₁` do you start to get empty bike racks at Olin? +""" + +# ╔═╡ 747e20c4-b06b-4e78-a09a-55053cf42bf4 +md"- Answer: missing" + +# ╔═╡ ce18866b-5cb8-4966-81c7-683fa65823ff +md""" +## Part 2: Mean zero counts at Olin +""" + +# ╔═╡ dcad848a-c178-4ce5-82d0-8a11aabb3b3c +md""" +We now want to have an idea of the mean zero counts at Olin for `p₁` values in the range `[0.0, 1.0]`. +""" + +# ╔═╡ 92181028-60fc-4830-afba-2380ac91455d +md""" +You can inspect the actual number of bike values at Olin by using `jdsol[:O]`: +""" + +# ╔═╡ f8942b10-773a-4b22-baad-8004fba8bd34 +missing + +# ╔═╡ 43d41284-053d-4dfe-8d5b-96be70c0495c +md""" +If you want to have a `true` boolean value on positions where the vector value is zero (and `false` on non-zero values), then you would compare `jdsol[:O]` element wise with `0`. In Julia, if you want to do element wise operations with/on vectors, you always need to place a dot (`.`) in front of the operator, like for example `.==`. + +Compare in that way `jdsol[:O]` with `0`: +""" + +# ╔═╡ a999ae2a-7567-41e7-9c0c-e94fad6f5d46 +missing + +# ╔═╡ 9ebb5b44-04d7-4b89-acdb-e40a245703d2 +md""" +Furthermore, if you want the count the number of `true` values in the latter (hence, the zero element values), you can simply use the function `count(...)`. Count the number of zeros: +""" + +# ╔═╡ 049de8d5-b221-452b-b2c4-9bc1e0c17f48 +missing + +# ╔═╡ a73a2853-1f48-4179-9771-083794d3f137 +md""" +Using the aforementioned way to count zeros in a vector, we will now count the zeros for a range of $p$ values. Because of the stochastic behaviour of the system, for each $p$ values we will count the zeros for a $1000$ simulations and then storing only the average value. + +To introduce a new value for $p_1$ you can take a deepcopy of the problem and remake the problem like this: +- `jdprob_re = remake(deepcopy(jdprob); p=[:p₁=>p_val])` +and then solving the problem and store it in `jdsol_re`. + +In the layout below, `mean_zero_counts` while contain the final mean values of the averaged numbers of zeros from a `1000` simulations using a specific $p$ value, `zero_counts_p_val` will contain the actual number of zeros for a `1000` simulations using a specific $p$ value. + +Use the layout below to fill in `mean_zero_counts`. +""" + +# ╔═╡ b82da9cc-ccba-45c4-92c4-2c3b2c190688 +md""" +!!! warning "Important note" + + The SSA solver only saves the state when something changes (for example, when a bike arrives or leaves). It does **not** automatically store values in between events, so it does not explicitly keep track of how long the system stays in the same state. + + If we want to estimate how long there were zero bikes, we need information at regular time intervals. We can do this by setting `saveat = 0.1`. This forces the solver to record the state every 0.1 time units, thereby approximating the time that there are 0 bikes at the campus. + + Because the timepoints at which a state changes are random, we will still have a small error due to the number of bikes changing in between our chosen time intervals. Choosing a small time interval will help reduce this error. +""" + +# ╔═╡ 682e9120-0e1c-4dfa-9ec6-66bb0a3f4374 +# begin +# p_values = 0.0:0.1:1.0 # different p-values +# mean_zero_counts = [] # vector to store the corresponding mean zero values +# for p_val in p_values # p_val will be each of the p_values +# zero_counts_p_val = [] # vector to store the zeros for the 1000 simulations +# for i = missing # do a 1000 simulation +# # take a deepcopy and remake the problem for the specific p-value +# jdprob_re = missing +# # solve the problem +# jdsol_re = missing; +# # append the number of zeros to zero_counts_p_val +# missing +# end +# # append the mean number of zeros to mean_zero_counts +# missing +# end +# end + +# ╔═╡ 705d3fcb-20b6-4481-a304-1d3ccd623674 +md""" +Have a look at the mean zero counts by typing `mean_zero_counts`: +""" + +# ╔═╡ 5968317a-6c07-4655-8137-6702656bb3b4 +missing + +# ╔═╡ ff9370d8-3395-4382-9f51-afa11748319e +md""" +Plot the mean zero counts as a function of the $p$-values. +""" + +# ╔═╡ 48be49d0-0b60-44f3-8152-1ca917a4232e +missing + +# ╔═╡ d6452915-bdf0-48f0-8c7d-3df83c7bce72 +md""" +!!! question "Questions" + 1. From what value of $p$ do the empty number of bike racks at Olin clearly begin to rise? + 2. Reflect on this, does this make sense? Hint: change the value of $p_2$ and observe what happens. +""" + +# ╔═╡ 4d73e614-9360-4e13-b7a4-ff7713989bf8 +md""" +Answers: +1. missing +2. missing +""" + +# ╔═╡ Cell order: +# ╟─1f975552-b0b8-4830-8dcc-214574d4fc38 +# ╠═6b342f14-e7d5-11ef-1ea0-77ceb0d78f32 +# ╠═309035dd-5653-48a6-a53d-817e743279fa +# ╠═71140c81-af29-4857-8020-4f94c8bd64b3 +# ╠═284f5847-9c15-41f3-a595-1e12a22df69f +# ╟─a0da1455-bf8c-45d2-893a-f9b5c279cb59 +# ╟─d2f32eab-0b35-4794-9219-5bcbb4c069c5 +# ╟─016842c9-9479-4061-a27e-9dc006121f23 +# ╠═6c97bf81-ef32-45a4-aa7c-c8c26ba2d2c3 +# ╟─7227a95a-ba0c-44dc-b0b8-18d6bbf362e8 +# ╟─9c7ab7fb-7380-41a3-85ea-714478ade218 +# ╠═1536fe23-0f8d-4b86-98d2-076248b35954 +# ╟─613d7769-cc9d-471e-b3e7-4266fc0e4677 +# ╟─6614bdde-fed0-45aa-b7ca-a21ca72dd181 +# ╟─e6e2ff5c-38eb-4ba3-b430-c9031483a0a5 +# ╠═ab6af765-1cde-4da8-bbc1-a5fab391db54 +# ╟─378878a0-5c09-4eb0-ac43-1031014ff12a +# ╠═3ae98e83-7beb-4597-89be-80c813d4349b +# ╟─988f79c0-9c7b-4752-a7f2-d4473ad73ce6 +# ╠═0d8f53f8-0a14-4ac6-bd0c-2190d4db0909 +# ╟─08d43ac8-a973-4d7b-baf7-4c37e54cfe24 +# ╠═e20e4dd8-bdbb-4005-af68-6bf7e4ec130e +# ╟─238e1120-34af-4d57-8efa-aa80ab28a874 +# ╠═d4c45709-70c9-4ba0-8fb8-6b600473723d +# ╟─d06fb076-76e4-4248-a940-96804ea68833 +# ╠═7644adf4-d992-48b1-b40a-12fdf30f6cb5 +# ╟─74708270-b1ec-48c7-af32-3b970b92c706 +# ╠═2b00df5d-994e-47a1-8068-c93ce3f1a618 +# ╟─9d06c31e-3525-4889-a1de-3fe02413c7d8 +# ╠═9a90f800-3669-4831-b50b-c5405bbb9a03 +# ╟─a554fd16-aa3d-48ca-8de6-5582725c27d8 +# ╟─d6872046-b5ef-4c2d-a9bb-2418f57f715d +# ╠═747e20c4-b06b-4e78-a09a-55053cf42bf4 +# ╟─ce18866b-5cb8-4966-81c7-683fa65823ff +# ╟─dcad848a-c178-4ce5-82d0-8a11aabb3b3c +# ╟─92181028-60fc-4830-afba-2380ac91455d +# ╠═f8942b10-773a-4b22-baad-8004fba8bd34 +# ╟─43d41284-053d-4dfe-8d5b-96be70c0495c +# ╠═a999ae2a-7567-41e7-9c0c-e94fad6f5d46 +# ╟─9ebb5b44-04d7-4b89-acdb-e40a245703d2 +# ╠═049de8d5-b221-452b-b2c4-9bc1e0c17f48 +# ╟─a73a2853-1f48-4179-9771-083794d3f137 +# ╟─b82da9cc-ccba-45c4-92c4-2c3b2c190688 +# ╠═682e9120-0e1c-4dfa-9ec6-66bb0a3f4374 +# ╟─705d3fcb-20b6-4481-a304-1d3ccd623674 +# ╠═5968317a-6c07-4655-8137-6702656bb3b4 +# ╟─ff9370d8-3395-4382-9f51-afa11748319e +# ╠═48be49d0-0b60-44f3-8152-1ca917a4232e +# ╟─d6452915-bdf0-48f0-8c7d-3df83c7bce72 +# ╠═4d73e614-9360-4e13-b7a4-ff7713989bf8 diff --git a/src/exercises/dje_model_catalyst_intro.jl b/src/exercises/dje_model_catalyst_intro.jl new file mode 100644 index 00000000..26dfe609 --- /dev/null +++ b/src/exercises/dje_model_catalyst_intro.jl @@ -0,0 +1,372 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "20" +#> title = "3. DJE_model_Catalyst_intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Introduction to solving discrete jump problems with Catalyst" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ a2582acb-7d17-43ab-b883-d766b1a2c984 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ e5f8c320-eda0-11ee-37d0-458bdbd94f15 +using Markdown, InteractiveUtils + +# ╔═╡ 4e03b93e-f63e-466c-9941-d66e62306010 +using PlutoUI; TableOfContents() + +# ╔═╡ 9e8fd818-a14f-41cf-b2fd-a7425141b283 +using Catalyst + +# ╔═╡ 0b3921c9-6d6e-4c52-8c21-d883ed493028 +using JumpProcesses, StatsPlots + +# ╔═╡ 62b185be-e327-4ef3-af39-819732d107bf +md""" +# Introduction: Solving discrete jump problems with Catalyst +""" + +# ╔═╡ f5f32d5d-0c13-4865-8024-ca47208c9b8e +md""" +In this practical, we will revisit the infection model which we solved last week using Catalyst.jl. Last week we assumed that the number of people is a continuous variable, while in practice this is not the case (ever +seen 46.3 persons). In this session, we will instead use a Stochastic Simulation Algorithm (SSA), which is better suited for models with discrete variables, such as the number of infected individuals. +""" + + +# ╔═╡ e1583a47-9171-4db2-a6e9-d4889ee294c7 +md""" +For the sake of clarity we restate the describtion of the previous infection model. + +It is important to model the outbreak of infectious diseases in order to devise appropriate measures to avoid global epidemics. In this exercise we consider an isolated group of people in which a viral disease is spreading. An infection model (similar to the SIR-model but slightly extended) will be used for this purpose. We are interested in the evolution of the number of susceptible ($S$), infected ($I$), deceased ($D$) and resistant ($R$) persons.\ +We make the following assumptions: +1. Transmission of the disease from an infected person to a susceptible person takes place through direct contact. The chance of any two inhabitants of the group coming into contact with each other is $\beta$, and the probability of infection after contact between an infected and a susceptible person is $\alpha$. +2. Note that the above assumption implicitly states that the probability of two neighbours coming into contact with each other is as high as the probability of two people living at two extremes of the territory coming into contact with each other. +3. A pereson leaves the infection period at a rate $r$ (hence, a person is contagious for an average of $1/r$ days. Without appropriate medication, a fraction $m$ of infected people die and a fraction $(1-m)$ of infected people acquire immunity after healing. +4. We assume that no one crosses the territory borders. +""" + +# ╔═╡ 723c2c53-7f75-4f2d-8608-11ef0ef274d9 +md""" +| Variable | Unit | Meaning | +|:---------- |:---------- |:------------| +| ``S`` | *persons* | number of susceptible persons | +| ``I`` | *persons* | number of infected persons | +| ``D`` | *persons* | number of deceased persons | +| ``R`` | *persons* | number of resistant persons | +""" + +# ╔═╡ 0148d340-a072-49c0-9b3c-29249c21a334 +md""" +| Variable | Unit | Meaning | +|:---------- |:---------- |:------------| +| ``\alpha`` | ``\frac{persons}{contact}`` | chances of getting infected after contact | +| ``\beta`` | ``\frac{contact}{persons^2\,day}`` | contact rate | +| ``r`` | ``\frac{1}{day}`` | rate of leaving infection period | +| ``m`` | ``\frac{person}{person}`` | fraction of persons deceasing | +| ``1-m`` | ``\frac{person}{person}`` | fraction of persons becoming resistant | +""" + +# ╔═╡ 66d75717-3bcd-4e73-a8de-ed3879efb509 +md""" +Hence, the infection rate is ``\alpha \beta``. This means that a susceptible person meets an infected person: ``S+I``, this will result in ``2I`` at a rate ``\alpha \beta``. Futhermore, an infected person ``I`` will either become a deceased person ``D`` at a rate ``m r`` or become a resistant person ``R`` at rate ``(1-m) r`` +""" + +# ╔═╡ e884ea42-87ac-4ccc-bf19-937837a2645d +md""" +Our infection model has three reaction events: + +- Infection, where a susceptible persons meets an infected persons and also becomes infected. +- Deceasing, where an infected person die. +- Recovery, where an infected person recovers. +""" + +# ╔═╡ 10c93224-ac8d-4512-be8f-6961b885712f +md""" +Each reaction is also associated with a specific rate: + +- ``\alpha \beta``, the infection rate. +- ``m r``, the death rate. +- ``(1-m) r``, the recovery rate. +""" + +# ╔═╡ 7064d3d4-7ccb-4af2-a1ed-64430c50651f +md""" +Hence, the following *infection reactions* are: + +$$S + I \xrightarrow[]{\alpha \beta} 2I$$ +$$I \xrightarrow[]{mr} D$$ +$$I \xrightarrow[]{(1-m)r} R$$ +""" + +# ╔═╡ 4dd77999-2ed0-415e-b044-1af1de1b4ae3 +md""" +We are going to implement this system of *reactions* using Catalyst. +""" + +# ╔═╡ d888c734-044b-424d-b895-f8bc738346ed +md""" +## Implementation of the system + +First we create a *reaction network object*, that we have named `infection_model`, that implements the aforementioned *reactions*. +""" + +# ╔═╡ c792559f-db9b-4d9a-8e79-c7e8f82b4603 +infection_model = @reaction_network begin + α * β, S + I --> 2I + r * m, I --> D + r * (1 - m), I --> R +end + +# ╔═╡ 2085fd23-28fc-4b68-a3e7-590c50f2c9c6 +md""" +You can get a list of the different *reaction* **species** with the command `species` +""" + +# ╔═╡ f3e1feb4-d36a-4095-8e25-df02c73078e9 +species(infection_model) + +# ╔═╡ 22145b7a-5a55-4db1-9061-ad51e907490f +md""" +The *reaction model* can be converted to a symbolic differential equation model via +""" + +# ╔═╡ 7f15968c-dadc-4860-8071-9b072673e414 +osys = convert(ODESystem, infection_model) + +# ╔═╡ f9feffe6-c7d6-4962-ada5-85a51664ff2a +md""" +You can get a list of the differential equations with the command `equations`: +""" + +# ╔═╡ a0ee4837-074a-4587-a9d6-b8892a6c99f4 +equations(osys) + +# ╔═╡ 1d6f5b69-ab47-4344-985f-3e9c9430fddd +md""" +To get a list of the state variables, you can use the command `unknowns`: +""" + +# ╔═╡ 6bc9574c-0f04-45c0-a457-6601f1333c0e +unknowns(osys) + +# ╔═╡ b3584f8c-50f4-4800-96aa-776cfc2b8db3 +md""" +To get a list of the parameters, you can use the command `parameters`: +""" + +# ╔═╡ 98fa7987-b60b-4748-a3e8-38259bb0cd8c +parameters(osys) + +# ╔═╡ d94a467a-3194-4573-a3e0-27a265147e66 +md""" +## Simulating the system as a (Discrete) Jump problem +""" + +# ╔═╡ 78e55389-3431-41de-9f4a-b6b45cb8988b +md""" +Instead of simulating our model with the species defined as decimal numbers, we will simulate the individual reaction events through the so-called **Gillespie algorithm**. This algorithm is a so-called **Stochastic Simulation Algorithm** (SSA).\ +The Gillespie algorithm is a computational method used to simulate **discrete** and **stochastic** (random) processes. The algorithm models the changes in a system over time by considering individual events and their probabilities, this allows to understand how random fluctuations affect the system's behavior. +""" + +# ╔═╡ 9bed9d84-600a-44f0-87a1-0997d547791f +md""" +To illustrate the simulation based on the Gillespie-algorithm, we will use the same infection model as before, but considering much less individuals. Hence, we will use different initial conditions, parameter values and timespan as with the ODE problem. +""" + +# ╔═╡ a2349455-5850-4950-bb10-221ae813b26f +md""" +Assume in this example that there are $50$ people on the territory, and that initially 1 person is infected. Hence, $I_0 = 1$, $S_0 = 50-I_0 = 49$, $D_0 = 0$ and $R_0 = 0$.\ +Furthermore, we take the following values for the parameters: $\alpha = 0.15\;person/contact$, $\beta = 0.1\;contact/(person^2\,day)$, $r = 0.2\;day^{-1}$ (i.e. a person is contagious for an average of $5\;days$) and $m=0.6$.\ +Finally, we want to run our simulation from day $0$ till day $60$. +""" + +# ╔═╡ 60b150fc-b9e5-426d-8d48-95efc907692a +md""" +### Setting initial conditions + +The vector holding the initial conditions for $S$, $I$, $D$ and $R$ is: +""" + +# ╔═╡ bca8459b-8f05-4e4e-92f1-43659ef652ba +u0 = [:S => 49, :I => 1, :D => 0, :R => 0] + +# ╔═╡ ad8215f6-676e-4352-b0c9-8e9701da3bc6 +md""" +### Setting parameter values + +The vector holding the parameter values for $\alpha$, $\beta$, $r$ and $m$ is: +""" + +# ╔═╡ da136f6e-7605-4f1f-81bc-f0e81ed7f528 +parms = [:α => 0.15, :β => 0.1, :r => 0.2, :m => 0.6] + +# ╔═╡ cef064bf-2168-4904-b753-a012a9c9f070 +md""" +### Setting the timespan +""" + +# ╔═╡ fa2dde71-cea4-4b31-894e-432f9e3e7786 +md""" +!!! note + When working with JumpProblems always use floating-point values when defining the time span. Practically this means that you write `tspan = (0.0, 60.0)` instead of `tspan = (0, 60)`, indicating to Julia that time points should be represented at floating points. Using integers may lead to errors for JumpProblems when the solver encounters non-integer + time points (e.g. 1.6seconds). +""" + +# ╔═╡ 69b38ef3-d99e-4f4d-8c53-79e20d0094f0 +tspan = (0.0, 60.0) + +# ╔═╡ 57567d6d-6303-4ad8-b171-f904b594b3fe +md""" +### Creating an DiscreteProblem +""" + +# ╔═╡ 92c77720-5120-4f55-8cf6-168ec8553638 +md""" +Unlike the previous approach with ODEProblem (denoting a deterministic ordinary differential equation), we wish to simulate our model as a jump process (where each reaction event denotes a single jump in the state of the system). We do this by first creating a **DiscreteProblem**, and then using this as an input to a **JumpProblem**. +""" + +# ╔═╡ 65e8b392-4a4c-4e09-a95e-a633a42e4bee +md""" +We create a DiscreteProblem by calling the `DiscreteProblem` function. Applying this function ensures that the problem is approached at a level of individual infections (reactions). Hence, the variable values will be integers. *Note that the order in which the input (the model name, the initial condition, the timespan, and the parameter values) is provided to* `DiscreteProblem` *matters!* Here, we save our DiscreteProblem in the `dprob` variable. +""" + +# ╔═╡ e2f215e7-8d20-4e72-9e0b-cc0ce150e55b +dprob = DiscreteProblem(infection_model, u0, tspan, parms); + +# ╔═╡ d061c4e2-8fbb-4ac5-95cf-c37d2916e669 +md""" +Next, we create a so-called JumpProblem by calling the `JumpProblem` function. Applying this function ensures that the infections (reactions) will happen stochastically. *Note again that the order in which the input (the model name, the DiscreteProblem variable, the simulation method) is provided to* `JumpProblem` *matters!* The simulation method is denoted by the option `Direct()`, which we recommend for now. +""" + +# ╔═╡ b462661a-3908-48e7-b162-f3b78d37231f +jprob = JumpProblem(infection_model, dprob, Direct()) + +# ╔═╡ bb1538ec-99e4-4ad0-8aaa-d552c6291c6d +md""" +### Solving the DiscreteProblem +""" + +# ╔═╡ 89c75cee-2488-4e9c-a470-b2111dcfc871 +md""" +Finally, we can simulate our model using the solve function, and plot the solution using the `plot` function. +""" + +# ╔═╡ 8fbb4942-888c-4798-b972-7860e695d5ba +dsol = solve(jprob) +# dsol = solve(jprob, SSAStepper()) # also possible + +# ╔═╡ 5cfe9878-cbc2-4852-a757-ad8247d1f3d6 +md""" +Note that at the different time points the variables values in the solution are integer numbers and reflect the number of persons in either state ($S$, $I$, $D$ and $R$).\ +Futhermore, note that executing the `solve` command at different occasions will result in other solutions because of the stochastic character of the applied method. +""" + +# ╔═╡ ed8dce8b-d83b-40bc-b1eb-1ab369611cc1 +md""" +Finally, we can plot the solution through the plot function. +""" + +# ╔═╡ f855160a-2f04-4d11-97de-688113067c1c +plot(dsol) + +# ╔═╡ 719af6d7-3c98-499e-b789-a6de5af2a027 +md""" +Below is a piece of code that solves the problem a $1000$ times and stores the time values at which the number of infected persons becomes zero. +""" + +# ╔═╡ bcd1e77b-0869-4b36-9309-598b8c771d80 +begin + times = [] # make empty vector + while length(times) < 1000 # while statement + dsol2 = solve(jprob) # solve the problem + j = findfirst(dsol2[:I] .== 0) # find index of first 0 + if j != nothing # if index is a valid index + append!(times, dsol2.t[j]) # append time to vector times + end + end +end + +# ╔═╡ e97ca371-9ee5-4419-afcd-2a35ed7426d1 +md""" +The vector `times` is now filled with time values at which the number of infected persons becomes zero. +""" + +# ╔═╡ a0e70668-6c4e-4395-b1e4-925952a52dea +times + +# ╔═╡ 764c7739-faad-4cf3-a64c-8ce3cae64074 +md""" +With this vector we make a histogram so that you can have an idea of the distribution when the infected persons becomes zero. +""" + +# ╔═╡ 1971359b-df64-49e5-9011-c992da211c74 +histogram(times, bins=range(0, 60, length=61)) +# histogram(times, bins=range(0, 60, length=61), normalize=:pdf) + +# ╔═╡ Cell order: +# ╟─62b185be-e327-4ef3-af39-819732d107bf +# ╠═e5f8c320-eda0-11ee-37d0-458bdbd94f15 +# ╠═a2582acb-7d17-43ab-b883-d766b1a2c984 +# ╠═4e03b93e-f63e-466c-9941-d66e62306010 +# ╠═9e8fd818-a14f-41cf-b2fd-a7425141b283 +# ╠═0b3921c9-6d6e-4c52-8c21-d883ed493028 +# ╟─f5f32d5d-0c13-4865-8024-ca47208c9b8e +# ╟─e1583a47-9171-4db2-a6e9-d4889ee294c7 +# ╟─723c2c53-7f75-4f2d-8608-11ef0ef274d9 +# ╟─0148d340-a072-49c0-9b3c-29249c21a334 +# ╟─66d75717-3bcd-4e73-a8de-ed3879efb509 +# ╟─e884ea42-87ac-4ccc-bf19-937837a2645d +# ╟─10c93224-ac8d-4512-be8f-6961b885712f +# ╟─7064d3d4-7ccb-4af2-a1ed-64430c50651f +# ╟─4dd77999-2ed0-415e-b044-1af1de1b4ae3 +# ╟─d888c734-044b-424d-b895-f8bc738346ed +# ╠═c792559f-db9b-4d9a-8e79-c7e8f82b4603 +# ╟─2085fd23-28fc-4b68-a3e7-590c50f2c9c6 +# ╠═f3e1feb4-d36a-4095-8e25-df02c73078e9 +# ╟─22145b7a-5a55-4db1-9061-ad51e907490f +# ╠═7f15968c-dadc-4860-8071-9b072673e414 +# ╟─f9feffe6-c7d6-4962-ada5-85a51664ff2a +# ╠═a0ee4837-074a-4587-a9d6-b8892a6c99f4 +# ╟─1d6f5b69-ab47-4344-985f-3e9c9430fddd +# ╠═6bc9574c-0f04-45c0-a457-6601f1333c0e +# ╟─b3584f8c-50f4-4800-96aa-776cfc2b8db3 +# ╠═98fa7987-b60b-4748-a3e8-38259bb0cd8c +# ╟─d94a467a-3194-4573-a3e0-27a265147e66 +# ╟─78e55389-3431-41de-9f4a-b6b45cb8988b +# ╟─9bed9d84-600a-44f0-87a1-0997d547791f +# ╟─a2349455-5850-4950-bb10-221ae813b26f +# ╟─60b150fc-b9e5-426d-8d48-95efc907692a +# ╠═bca8459b-8f05-4e4e-92f1-43659ef652ba +# ╟─ad8215f6-676e-4352-b0c9-8e9701da3bc6 +# ╠═da136f6e-7605-4f1f-81bc-f0e81ed7f528 +# ╟─cef064bf-2168-4904-b753-a012a9c9f070 +# ╟─fa2dde71-cea4-4b31-894e-432f9e3e7786 +# ╠═69b38ef3-d99e-4f4d-8c53-79e20d0094f0 +# ╟─57567d6d-6303-4ad8-b171-f904b594b3fe +# ╟─92c77720-5120-4f55-8cf6-168ec8553638 +# ╟─65e8b392-4a4c-4e09-a95e-a633a42e4bee +# ╠═e2f215e7-8d20-4e72-9e0b-cc0ce150e55b +# ╟─d061c4e2-8fbb-4ac5-95cf-c37d2916e669 +# ╠═b462661a-3908-48e7-b162-f3b78d37231f +# ╟─bb1538ec-99e4-4ad0-8aaa-d552c6291c6d +# ╟─89c75cee-2488-4e9c-a470-b2111dcfc871 +# ╠═8fbb4942-888c-4798-b972-7860e695d5ba +# ╟─5cfe9878-cbc2-4852-a757-ad8247d1f3d6 +# ╟─ed8dce8b-d83b-40bc-b1eb-1ab369611cc1 +# ╠═f855160a-2f04-4d11-97de-688113067c1c +# ╟─719af6d7-3c98-499e-b789-a6de5af2a027 +# ╠═bcd1e77b-0869-4b36-9309-598b8c771d80 +# ╟─e97ca371-9ee5-4419-afcd-2a35ed7426d1 +# ╠═a0e70668-6c4e-4395-b1e4-925952a52dea +# ╟─764c7739-faad-4cf3-a64c-8ce3cae64074 +# ╠═1971359b-df64-49e5-9011-c992da211c74 diff --git a/src/exercises/dje_model_festival_toilet.jl b/src/exercises/dje_model_festival_toilet.jl new file mode 100644 index 00000000..d2404b4e --- /dev/null +++ b/src/exercises/dje_model_festival_toilet.jl @@ -0,0 +1,283 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "22" +#> title = "3. DJE_model_festival_toilet" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Discrete jump model of a festival toilet queue" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ a782b415-6f43-4cf2-a5d4-cff3aa31b187 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ e84131d2-3ad2-11f0-02b8-2ffac00498cf +using Catalyst, StatsPlots, JumpProcesses + +# ╔═╡ 0ab79c4e-0206-47e1-b057-3f84e4f66a17 +using PlutoUI; TableOfContents() + +# ╔═╡ 99c690a3-c1ed-4b41-8427-8b8b04fdcd0f +md""" +# Exercise: Modelling a Festival Toilet +""" + +# ╔═╡ c7d127f5-d8b1-4f75-90db-262d70d21482 +# https://www.linda.nl/lindanl-assets/uploads/2024/06/03140318/festivaltoilet-1800x1013.png +md""" +![Festival toiletten](https://www.linda.nl/lindanl-assets/uploads/2024/06/03140318/festivaltoilet-1800x1013.png) +""" + +# ╔═╡ c8d03b21-b064-42e2-90b1-a7981b17a4a6 +md""" +A good festival provides sufficient food and drink. This also means that there will be a lot of discharge. As a student worker, you are responsible for determining the toilet capacity by means of a simulation. + +The festival has ten mobile unisex toilets and only one queue. Given that people are discrete and unpredictable, a Jump model seems appropriate. + +These are the stocks in your model: +- $L(t)$: the number of people in the queue (wacht_**L**_ijn) (initially zero); +- $V(t)$: the number of free (_**V**_rije) toilets; +- $B(t)$: the number of occupied (_**B**_ezette) toilets (initially zero). + +You simulate minute by minute from 0 to 360 minutes (corresponding to 6 p.m. to 12 p.m.). You consider the following flows/processes: +- People are constantly joining the queue (zero-order process), at an average rate of 1.5 per minute. +- When a cubicle is free, one person from the queue enters and the cubicle is occupied. Only one person per cubicle is allowed. This happens quickly, as the kinetics have already been established. +- An occupied cubicle is occupied for an average of five minutes, then it is free again (and the person goes to do something else). + +Use the following symbols for the parameters: +- `kₐ`: rate at which people arrive in the queue; +- `kₒ`: rate at which someone from the queue will occupy a free cubicle; +- `kₑ`: rate at which an occupied cubicle becomes available. +""" + +# ╔═╡ 15c5c1e3-72d9-4beb-b637-f126400e8da1 +md""" +The time interval used is: +""" + +# ╔═╡ a30d6812-8337-4361-a16e-ee1bb86b2cb1 +tspan = missing #use floats + +# ╔═╡ 2bc030aa-4baa-4e75-90b7-c9fa1e7818ef +md""" +## Part 1: Simulating $L$, $V$ and $B$. +""" + +# ╔═╡ f1e9eecb-7068-48fe-a0f2-bed45d41f673 +md""" +Complete the model, create the (discrete) jump problem, solve it, and create a graph with the simulation. +""" + +# ╔═╡ a9830054-02ee-44f6-a94c-5722959124d7 +# toilet = @reaction_network begin +# @species L(t)=missing V(t)=missing B(t)=missing +# @parameters kₐ=missing kₒ=10 kₑ=missing +# missing +# kₒ, L + V --> B +# missing +# end + +# ╔═╡ 9540af46-06b1-4c55-a541-47813d5d24d9 +missing + +# ╔═╡ 89c78b4a-e180-4ec3-a304-1873a39aeb8b +md""" +Create the (discrete) jump problem: +""" + +# ╔═╡ a2e9c72a-a4a4-4633-851e-878440c82e36 +dprob = missing; + +# ╔═╡ b9bddaa3-4293-40a0-8b5b-a277d4fb9f5f +jprob = missing; + +# ╔═╡ 08b146ff-fe8a-4780-939a-7917b0a0d08c +md""" +Solve the problem: +""" + +# ╔═╡ 26c05ffa-c3c7-4b12-9fb9-7caaceeb9835 +sol1 = missing; + +# ╔═╡ 67f078fc-c3c2-4e3e-a29a-3200db58e4a3 +md""" +Make the graph containing $L$, $V$ and $B$: +""" + +# ╔═╡ be53c5e4-ac38-4fda-a19c-c02dd2be5989 +missing + +# ╔═╡ 0d1124a4-a2d2-4774-add8-4db92142154a +md""" +## Part 2: Maximum Length of the Queue +""" + +# ╔═╡ cfaa00e1-4420-46f0-9ee9-03ae32a7a82b +md""" +Calculate the maximum length of the queue (wacht_**L**_ijn). Run your model `1000` times and save the maximum length each time in a vector `line_maxima` (via a `for`-loop). Create a histogram of those maximum lengths. +""" + +# ╔═╡ dbd252b3-f8c9-4d9d-906f-463738291b3c +md""" +Calculate the maximum length of the queue $L$ using the solution `sol1` from Part 1: +""" + +# ╔═╡ 5f4f76f6-03b2-4224-9969-83ef5be6ad12 +missing + +# ╔═╡ 2da3f077-1a67-4937-92db-e1baa7642903 +md""" +Run your model 12 times and each time save the maximum length (via a `for`-loop) in a vector `line_maxima`. +""" + +# ╔═╡ d80e6788-84cb-479f-9e0c-4e9affd86905 +# begin +# line_maxima = [] +# for i = 1:missing +# sol2 = missing +# missing +# end +# end + +# ╔═╡ 97c72483-8cba-4317-a9ab-2761e153a4ac +# line_maxima + +# ╔═╡ 4b0afbed-7325-48ec-b6d4-3bce0f7d5855 +md""" +Make the histogram: +""" + +# ╔═╡ 6abe6849-5bc0-474c-abbb-4ac45374aab8 +missing + +# ╔═╡ f3380a9b-7970-4084-b9c9-a6d0a473cf70 +md""" +## Part 3: Introducing a Discrete Event +""" + +# ╔═╡ 0b8bc24d-8e3d-432c-930e-96cc64a6bae4 +md""" +Between 8 p.m. and 9 p.m. is a period that many people use to relieve themselves. During that period, an average of three people arrive per minute (and then returns to normal). Implement this with an event, simulate and create a plot. +""" + +# ╔═╡ a808a52c-aef3-4f97-ad97-e6728a1d82db +md""" +Create the conditions, add the condition to the model, and recreate the (discrete) jump problem. +""" + +# ╔═╡ 5d83a715-fc3c-46f0-80b8-6125d11403a2 +rush_event = missing + +# ╔═╡ 9d6ead94-0ac4-4a22-b091-551463151328 +# @named toilet_with_rush = missing; + +# ╔═╡ aa63d527-f671-4de2-beb6-16736579e18a +dprob3 = missing; + +# ╔═╡ b72661ec-3b48-481f-94a2-bdcad402323c +jprob3 = missing; + +# ╔═╡ 58ac9de1-8b5b-459b-aebc-77145bdf9901 +md""" +Los het probleem op: +""" + +# ╔═╡ a0a9c5ce-af7d-470f-98d7-dfb93ecf12e5 +sol3 = missing; + +# ╔═╡ 6a5126ee-23bc-4642-961f-a6ddb1320505 +md""" +Create a graph showing $L$, $V$ and $B$: +""" + +# ╔═╡ c8dae895-2315-4593-b975-3f3e24bc21ec +missing + +# ╔═╡ a6fe82d9-7627-495b-878b-bc074897def1 +md""" +## Part 4: Public Urination Events +""" + +# ╔═╡ fccff7b3-8429-4494-a3c8-c6ae692eaac3 +md""" +When the queue becomes too long (if there are more than 15 people in the queue), people start urinating in public (on average 1 per minute). These people disappear from the queue. Based on your model from 1), create a new model that simulates this. Create a new variable $W(t)$ (initially zero) that keeps track of the total number of public urination events (_**W**_ildplasevents). Plot the total number of public urination events over time.\ +Hint: `>(L, 15)` is 1 when `L` is greater than 15, and zero in other cases. Alternatively `ifelse(L > 15, 1, 0)` is also 1 when `L` is greater than 15, and zero in other cases. +""" + +# ╔═╡ 26c6c5e5-a7f8-4265-9423-c69b91b3d9e1 +# toilet_wp = @reaction_network begin +# @species L(t)=missing V(t)=missing B(t)=missing W(t)=missing +# @parameters kₐ=missing kₒ=10 kₑ=missing +# missing +# kₒ, L + V --> B +# missing +# missing +# end + +# ╔═╡ ee055acd-cd86-4e02-bf38-bf5779bd4f95 +dprob_wp = missing; + +# ╔═╡ b290acfd-52b1-45c4-aaaa-54ee200c5539 +jprob_wp = missing; + +# ╔═╡ 172782af-f395-4c23-b340-080a3ec90a20 +sol_wp = missing; # voer enkele keren uit! + +# ╔═╡ 0dc015e8-31d9-4e9d-950c-ac94d9ec523f +missing + +# ╔═╡ Cell order: +# ╟─99c690a3-c1ed-4b41-8427-8b8b04fdcd0f +# ╟─c7d127f5-d8b1-4f75-90db-262d70d21482 +# ╠═a782b415-6f43-4cf2-a5d4-cff3aa31b187 +# ╠═e84131d2-3ad2-11f0-02b8-2ffac00498cf +# ╠═0ab79c4e-0206-47e1-b057-3f84e4f66a17 +# ╟─c8d03b21-b064-42e2-90b1-a7981b17a4a6 +# ╟─15c5c1e3-72d9-4beb-b637-f126400e8da1 +# ╠═a30d6812-8337-4361-a16e-ee1bb86b2cb1 +# ╟─2bc030aa-4baa-4e75-90b7-c9fa1e7818ef +# ╟─f1e9eecb-7068-48fe-a0f2-bed45d41f673 +# ╠═a9830054-02ee-44f6-a94c-5722959124d7 +# ╠═9540af46-06b1-4c55-a541-47813d5d24d9 +# ╟─89c78b4a-e180-4ec3-a304-1873a39aeb8b +# ╠═a2e9c72a-a4a4-4633-851e-878440c82e36 +# ╠═b9bddaa3-4293-40a0-8b5b-a277d4fb9f5f +# ╟─08b146ff-fe8a-4780-939a-7917b0a0d08c +# ╠═26c05ffa-c3c7-4b12-9fb9-7caaceeb9835 +# ╟─67f078fc-c3c2-4e3e-a29a-3200db58e4a3 +# ╠═be53c5e4-ac38-4fda-a19c-c02dd2be5989 +# ╟─0d1124a4-a2d2-4774-add8-4db92142154a +# ╟─cfaa00e1-4420-46f0-9ee9-03ae32a7a82b +# ╟─dbd252b3-f8c9-4d9d-906f-463738291b3c +# ╠═5f4f76f6-03b2-4224-9969-83ef5be6ad12 +# ╟─2da3f077-1a67-4937-92db-e1baa7642903 +# ╠═d80e6788-84cb-479f-9e0c-4e9affd86905 +# ╠═97c72483-8cba-4317-a9ab-2761e153a4ac +# ╟─4b0afbed-7325-48ec-b6d4-3bce0f7d5855 +# ╠═6abe6849-5bc0-474c-abbb-4ac45374aab8 +# ╟─f3380a9b-7970-4084-b9c9-a6d0a473cf70 +# ╟─0b8bc24d-8e3d-432c-930e-96cc64a6bae4 +# ╟─a808a52c-aef3-4f97-ad97-e6728a1d82db +# ╠═5d83a715-fc3c-46f0-80b8-6125d11403a2 +# ╠═9d6ead94-0ac4-4a22-b091-551463151328 +# ╠═aa63d527-f671-4de2-beb6-16736579e18a +# ╠═b72661ec-3b48-481f-94a2-bdcad402323c +# ╟─58ac9de1-8b5b-459b-aebc-77145bdf9901 +# ╠═a0a9c5ce-af7d-470f-98d7-dfb93ecf12e5 +# ╟─6a5126ee-23bc-4642-961f-a6ddb1320505 +# ╠═c8dae895-2315-4593-b975-3f3e24bc21ec +# ╟─a6fe82d9-7627-495b-878b-bc074897def1 +# ╟─fccff7b3-8429-4494-a3c8-c6ae692eaac3 +# ╠═26c6c5e5-a7f8-4265-9423-c69b91b3d9e1 +# ╠═ee055acd-cd86-4e02-bf38-bf5779bd4f95 +# ╠═b290acfd-52b1-45c4-aaaa-54ee200c5539 +# ╠═172782af-f395-4c23-b340-080a3ec90a20 +# ╠═0dc015e8-31d9-4e9d-950c-ac94d9ec523f diff --git a/src/exercises/sde_model_aging_mtk.jl b/src/exercises/sde_model_aging_mtk.jl new file mode 100644 index 00000000..3ea77a93 --- /dev/null +++ b/src/exercises/sde_model_aging_mtk.jl @@ -0,0 +1,358 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "18" +#> title = "3. SDE_model_aging" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Aging with saturated repair, modelled as an SDE" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 85b6ee78-2f7b-4f83-908b-c48fade56fcb +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 0c457482-95fe-11ef-0873-65164704c7a6 +using Markdown, InteractiveUtils + +# ╔═╡ 1d0a8fb2-dab9-44ae-a230-f3e92cf3cd6a +using ModelingToolkit, StochasticDiffEq + +# ╔═╡ 7a9fa5bc-7bb3-436e-b796-b946c855fe0a +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 22e0eda2-a922-48bb-9b6f-ec0c6dfeb524 +using StatsPlots, PlutoUI, StatsBase; TableOfContents() + +# ╔═╡ a176adcc-bde0-4a25-aa80-33379e05e63f +md""" +# Exercise: Aging and saturated repair +""" + +# ╔═╡ 387a7268-8b9d-4e23-b033-d9b697400fed +md""" +![Aging](https://www.biotechniques.com/wp-content/uploads/2024/11/aging-800x344.png) +""" + +# ╔═╡ 818ba2af-83dd-4ddb-93e8-d0549ddd806b +md""" +Aging is ultimately correlated with damaged cells. These damaged cells are called **senescent cells**. Senescent cells are cells that eventually stop multiplying but don't die off when they should. They instead remain and secrete factors that cause **chronic inflammation** and **reduce regeneration, leading to disease and decline**. Let $X$ denote the number of senescent cells - or the **damage** - in a human body. Research shows that they are **produced** at a **rate proportional to age**. Fortunately, in living organisms, these senescent cells are removed by so-called **natural-killer cells**. However, like many biological processen, this biological process of removing senescent cells is **saturated**. + +Hence, the model that we could adopt in order to predict the number of senescent cells (or damaged cells) $X$ in a human body, has two features: + +1. **production of damage that rises linearly with age**, and +2. the **saturating removal of damage**. + +A possible model is the following differential equation: + +$$\cfrac{dX}{dt} = \mu t - \beta \cfrac{X}{X + \kappa} + \cfrac{dW}{dt}$$ + +Lets denote the amount of senescent cells as $X$ in trillions [$tn$]. The term $\mu t$ stands for the procution of senescent cells, and the term $- \beta \cfrac{X}{X + \kappa}$ for the removal of senescent cells. The time $t$ is in years [$y$]. The coefficient $\mu$ [$tn/y^2$] is a proportionality factor for the production, $\beta$ [$tn/y$] is the removal rate coefficient and $\cfrac{X}{X + \kappa}$ [$-$] is the corresponding saturation factor, with $\kappa$ [$tn$] the amount of $X$ at which they inhibit half of their own removal rate. $W$ is a Wiener process. + +If this model was all there was, then all individuals would age at the same rate and die at the same age. The model does not explain why genetically identical organisms could differ in the number of senenscent cells. Therefore, we will introduce noise in the model by treating it as a **Stochastic Differential Equation** (SDE) model, where noise will be added to both, production and removal processes. +""" + +# ╔═╡ d917e75d-eefb-415f-ba45-2f1d7c62d63c +md""" +## Implementation of the system +""" + +# ╔═╡ 7bda135b-2e0a-425f-b30c-e5a09dbb8a4a +md""" +Implement the above ODE using MTK. + +Take a default initial value $X(t=0)=$`0.0` for the *species* `X`, and default values of `μ=0.00558`, `β=0.4364`, `κ=1.116` for the *parameters* in the model. Use a noise scaling parameter `n=0.2`. +""" + +# ╔═╡ 36c678de-56ea-437f-b667-064149075fa1 +md""" +Define the only variable $X$ with `@variables`. +""" + +# ╔═╡ 72d60bcc-7d78-45e8-90ca-306bb429aede +missing + +# ╔═╡ e560a876-57af-42b7-a16a-8f18676fe8e6 +md""" +Define the parameters with `@parameters`. +""" + +# ╔═╡ 27783ea2-c055-4ee6-a5cc-dbc5b4f516f7 +missing + +# ╔═╡ e881a30a-d622-468e-9be9-69523e5c5ae6 +md""" +Instantiate a source `B` of Brownian noise (a Wiener process by default) with `@brownian`. +""" + +# ╔═╡ 8ad575bd-abc1-41b5-8fe3-e28a5c227ef5 +missing + +# ╔═╡ ef9af0de-b394-4a59-81e8-569716481862 +md""" +Set up the model equation with the noise `n*B` added as a term in the equation and put it in a vector/array. +""" + +# ╔═╡ 858cb14b-ae3d-4b71-94b1-ff7d66a532b9 +eq_scs = missing + +# ╔═╡ 9dc5cfed-fa3d-4637-b1da-37fae3429565 +md""" +Define the expression for the diffusion/noise term and put it in a vector/array. +""" + +# ╔═╡ 705d5c4f-6a7b-4067-80ec-8fb5ccaedb18 +md""" +Build a system of equations with `@mtkbuild` and the function `System`. Provide the following arguments to `System`: the model equation and `t`. Name the system `sys_scs`. +""" + +# ╔═╡ 4e5b490e-67a8-40de-a49f-9f88dcb4b558 +missing + +# ╔═╡ 0524da26-35cb-4846-b7ef-f5842ae5db74 +md""" +## Setting initial condition, time span and parameters. +""" + +# ╔═╡ 98c1f94e-aea5-40c5-bee7-8df8794fedb3 +md""" +Initialize a vector `u0` with the default initial condition, set the timespan for the simulation (we will simulate from `0.0` $y$ to `120.0` $y$), and initialize a vector `parms` with the default parameter values. In that way, later, you can change the initial condition and the parameter values if you want to try other values. +""" + +# ╔═╡ 80db134e-bc57-41b0-8e1b-2ac6bff0c806 +u0 = missing + +# ╔═╡ ab8bef21-8c4f-4962-b9cb-e87ac1db049a +tspan = missing + +# ╔═╡ ab302108-e9a7-4941-a762-c0dc109c9b1d +parms = missing + +# ╔═╡ 3934ba8f-49c9-4f0c-8373-c8010f397d66 +md""" +## Simulating the system as an SDE problem +""" + +# ╔═╡ 45fceec8-a641-43ed-9a17-72b795e09297 +md""" +Create the SDE problem. +""" + +# ╔═╡ 7d164cdf-fd63-4c85-b168-3279d6d658eb +sprob_scs = missing + +# ╔═╡ 9f9a616a-4353-4f9a-8da5-effe4d214ed0 +md""" +Solve the SDE problem using `EM()`as solver and time step `dt=0.1`. +""" + +# ╔═╡ e4b9393d-d33c-4e16-814e-347b35435a82 +ssol_scs = missing + +# ╔═╡ 91881bc3-a1f0-48f7-82e8-7d7acea2f4de +md""" +Plot the solutions. Use the option `ylim=(0, 6)` in order to limit the range of $X$. +""" + +# ╔═╡ 7128ae66-c576-435b-8e33-4221f469ba1c +missing + +# ╔═╡ 1bce56da-0c35-41df-adbd-b5dee116be11 +md""" +Execute the cell, where the SDE problem is being solved, a few times and watch the (stochastic) changes in the solutions. +""" + +# ╔═╡ 7ca7c177-40b3-42c0-a57d-2f7eb874abdf +md""" +## Simulating the system as an EnsembleProblem. +""" + +# ╔═╡ 33debdb3-2008-4055-a778-600bf7409361 +md""" +In order to see to have an idea of the extend of the stochastic effect on the solutions, we can make a so-called *EnsembleProblem*. This allows us to plot many possible solutions in one plot. +""" + +# ╔═╡ 85c7adcd-5a69-4900-849e-6222568bf68f +md""" +Create an `EnsembleProblem` based on `sprob`. +""" + +# ╔═╡ 1f25730d-d93e-462a-ac02-f83381633849 +esprob_scs = missing + +# ╔═╡ 7d531f11-1a64-4b43-aa4f-04d282a615bd +md""" +Solve the ensemble problem. Use `EM()` as solver, take a time step `dt=0.1`, use the option `trajectories=100`. +""" +#= +Solve the ensemble problem. Use `EM()` as solver, take a time step `dt=0.1`, use the options `save_everystep=true`, and `trajectories=100`. +=# + +# ╔═╡ 5aaf1395-9b7c-451f-b1a3-44e5ba3c4d6a +essol_scs = missing + +# ╔═╡ c048528f-58a2-415d-a338-fc87302367b8 +md""" +Plot the solutions. Use the option `ylim=(0, 6)` in order to limit the range of $X$. +""" + +# ╔═╡ 147adea6-e7ed-4f7b-b753-127e41500109 +missing + +# ╔═╡ 1db274cd-3b01-4f45-8aa0-ffe6878a1dd1 +md""" +## Distribution of ages at 5 trillion senescent cells +""" + +# ╔═╡ c024dd3a-8fa8-4dbe-9669-0f338416a6af +md""" +Set up a histogram that shows the distribution of ages once the 5 trillion senescent cells are present in the body. +""" + +# ╔═╡ 3fe09628-cbfb-4e56-85e8-b97285b32e87 +md""" +!!! hints + - The number of senescent cells of the `i`-th trajoctory can be accessed with: `essol.u[i][:X]`. + - The index of the first element in the `i`-th trajectory that is greater than 5 can be found with: `findfirst(>(5), essol.u[i][:X])`. + - An index is a valid index when it is not `nothing`. + - The time at index position `j` can be accessed with `essol.u[i].t[j]` + - Appending an element, e.g., `x` to an array `times` can be done as follow: `append!(times, x)` +""" + +# ╔═╡ 0f901112-e504-4b7d-b7ec-3a80153903ca +# begin +# times = missing # make empty vector +# for i = missing # for loop from 1 to 100, default step is 1 +# # find index of first element that is greater than 5 +# j = missing +# if missing # if index is a valid index +# missing # append time to vector times +# end +# end +# end + +# ╔═╡ 65edc40f-430f-4d1a-9062-17bf8e1d7d59 +md""" +Make a histogram with the array `times`. Use `bins=range(0, 120, length=121)` or `bins=0:120`. +""" + +# ╔═╡ 17a51f9e-0adf-4802-8d13-c43eb7801bc7 +missing + +# ╔═╡ dad744a4-0fd2-406d-a600-429ad7619efd +md""" +Check the mean. +""" + +# ╔═╡ a41d2ab4-ffad-4b27-9811-ab57c51526cb +missing + +# ╔═╡ 98ca03e1-9c4e-40db-9dd7-68f1898c1ef7 +md""" +Check the standard deviation. +""" + +# ╔═╡ 7ac54e2a-9c8e-4743-a1a0-38490b04f759 +missing + +# ╔═╡ 0f9118f5-93f4-42f8-a0cf-754ce9ce9dbe +md""" +Check the minimum value. +""" + +# ╔═╡ a07565f7-31d9-4c50-8345-5777adb8a77a +missing + +# ╔═╡ 6f4253ce-e686-4061-ae13-9b35065464d9 +md""" +Check the maximum value. +""" + +# ╔═╡ 70628044-aaa5-46d6-a2df-deabbd8f2df8 +missing + +# ╔═╡ 23178795-d7fb-4a23-9649-a1ee9d1706d6 +md""" +!!! question + Interpret the results. Ask yourself the following question: + 1. Suppose that $5$ trillion senescent cells is about the maximum a human body can bear. What is the (approximate) corresponding range of ages? + 2. What is the effect of halving the damage rate $\mu$? + 3. What is the effect of doubling the damage removal rate $\beta$? + 4. What is the effect of halving the noise? +""" + +# ╔═╡ a4f5a40e-c4ec-45ef-b44b-b64b71717c25 +md""" +Answers: +1. missing +2. missing +3. missing +4. missing +""" + +# ╔═╡ Cell order: +# ╟─a176adcc-bde0-4a25-aa80-33379e05e63f +# ╠═0c457482-95fe-11ef-0873-65164704c7a6 +# ╠═85b6ee78-2f7b-4f83-908b-c48fade56fcb +# ╠═1d0a8fb2-dab9-44ae-a230-f3e92cf3cd6a +# ╠═7a9fa5bc-7bb3-436e-b796-b946c855fe0a +# ╠═22e0eda2-a922-48bb-9b6f-ec0c6dfeb524 +# ╟─387a7268-8b9d-4e23-b033-d9b697400fed +# ╟─818ba2af-83dd-4ddb-93e8-d0549ddd806b +# ╟─d917e75d-eefb-415f-ba45-2f1d7c62d63c +# ╟─7bda135b-2e0a-425f-b30c-e5a09dbb8a4a +# ╟─36c678de-56ea-437f-b667-064149075fa1 +# ╠═72d60bcc-7d78-45e8-90ca-306bb429aede +# ╟─e560a876-57af-42b7-a16a-8f18676fe8e6 +# ╠═27783ea2-c055-4ee6-a5cc-dbc5b4f516f7 +# ╟─e881a30a-d622-468e-9be9-69523e5c5ae6 +# ╠═8ad575bd-abc1-41b5-8fe3-e28a5c227ef5 +# ╟─ef9af0de-b394-4a59-81e8-569716481862 +# ╠═858cb14b-ae3d-4b71-94b1-ff7d66a532b9 +# ╟─9dc5cfed-fa3d-4637-b1da-37fae3429565 +# ╟─705d5c4f-6a7b-4067-80ec-8fb5ccaedb18 +# ╠═4e5b490e-67a8-40de-a49f-9f88dcb4b558 +# ╟─0524da26-35cb-4846-b7ef-f5842ae5db74 +# ╟─98c1f94e-aea5-40c5-bee7-8df8794fedb3 +# ╠═80db134e-bc57-41b0-8e1b-2ac6bff0c806 +# ╠═ab8bef21-8c4f-4962-b9cb-e87ac1db049a +# ╠═ab302108-e9a7-4941-a762-c0dc109c9b1d +# ╟─3934ba8f-49c9-4f0c-8373-c8010f397d66 +# ╟─45fceec8-a641-43ed-9a17-72b795e09297 +# ╠═7d164cdf-fd63-4c85-b168-3279d6d658eb +# ╟─9f9a616a-4353-4f9a-8da5-effe4d214ed0 +# ╠═e4b9393d-d33c-4e16-814e-347b35435a82 +# ╟─91881bc3-a1f0-48f7-82e8-7d7acea2f4de +# ╠═7128ae66-c576-435b-8e33-4221f469ba1c +# ╟─1bce56da-0c35-41df-adbd-b5dee116be11 +# ╟─7ca7c177-40b3-42c0-a57d-2f7eb874abdf +# ╟─33debdb3-2008-4055-a778-600bf7409361 +# ╟─85c7adcd-5a69-4900-849e-6222568bf68f +# ╠═1f25730d-d93e-462a-ac02-f83381633849 +# ╟─7d531f11-1a64-4b43-aa4f-04d282a615bd +# ╠═5aaf1395-9b7c-451f-b1a3-44e5ba3c4d6a +# ╟─c048528f-58a2-415d-a338-fc87302367b8 +# ╠═147adea6-e7ed-4f7b-b753-127e41500109 +# ╟─1db274cd-3b01-4f45-8aa0-ffe6878a1dd1 +# ╟─c024dd3a-8fa8-4dbe-9669-0f338416a6af +# ╟─3fe09628-cbfb-4e56-85e8-b97285b32e87 +# ╠═0f901112-e504-4b7d-b7ec-3a80153903ca +# ╟─65edc40f-430f-4d1a-9062-17bf8e1d7d59 +# ╠═17a51f9e-0adf-4802-8d13-c43eb7801bc7 +# ╟─dad744a4-0fd2-406d-a600-429ad7619efd +# ╠═a41d2ab4-ffad-4b27-9811-ab57c51526cb +# ╟─98ca03e1-9c4e-40db-9dd7-68f1898c1ef7 +# ╠═7ac54e2a-9c8e-4743-a1a0-38490b04f759 +# ╟─0f9118f5-93f4-42f8-a0cf-754ce9ce9dbe +# ╠═a07565f7-31d9-4c50-8345-5777adb8a77a +# ╟─6f4253ce-e686-4061-ae13-9b35065464d9 +# ╠═70628044-aaa5-46d6-a2df-deabbd8f2df8 +# ╟─23178795-d7fb-4a23-9649-a1ee9d1706d6 +# ╠═a4f5a40e-c4ec-45ef-b44b-b64b71717c25 diff --git a/src/exercises/sde_model_fermenter_secondorder_mtk.jl b/src/exercises/sde_model_fermenter_secondorder_mtk.jl new file mode 100644 index 00000000..f5b3a70a --- /dev/null +++ b/src/exercises/sde_model_fermenter_secondorder_mtk.jl @@ -0,0 +1,281 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "19" +#> title = "3. SDE_model_fermenter_secondorder" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Fermenter with second-order kinetics, modelled as an SDE" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 8152f632-af15-4164-a8ff-07c33a9a49b3 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ eb142900-1d94-11ef-12ed-6951b45f1817 +using Markdown, InteractiveUtils + +# ╔═╡ e317c2cc-c41a-4489-b06b-7f07a517e20b +using ModelingToolkit, StochasticDiffEq + +# ╔═╡ 2e91e92c-5cd2-4018-8a26-9f0f9e01a175 +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ e04f782d-67da-4e21-a3bf-d2ddff4bba0b +using StatsPlots, StatsBase + +# ╔═╡ 3552fc05-a4dc-4242-91b3-3eb6fb2ea389 +using PlutoUI; TableOfContents() + +# ╔═╡ a28e7ddf-76e9-4628-888c-e1d838da75ce +md""" +# Exercise: Fermenter - 2nd order kinetics - SDE +""" + +# ╔═╡ 959d6307-a30d-4ae7-970d-b2c7584c2c8f +md""" +In a fermenter reactor biomass grows on substrate. The reactor is fed with a inlet flow rate $Q_{in}$ [$L/h$], which consist of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. Inside the reactor, biomass, with a concentration of $X$ [$g/L$], is produced through second-order kinetics resulting in the following system of ODEs: + +$$\begin{eqnarray*} +\cfrac{dS}{dt} &= \cfrac{Q}{V}\,\left(S_{in} - S\right) - k\,X\,S \\ +\cfrac{dX}{dt} &= -\cfrac{Q}{V}\,X + Y\,k\,X\,S +\end{eqnarray*}$$ + +with $k$ [$L\,gS^{-1}h^{-1}$] the reaction rate constant, and $Y$ [$gX/gS$] the yield coefficient which is defined here by the amount of produced biomass by consumption of one unit of substrate. Futhermore, the reactor is drained with an outlet flow $Q$ [$L/h$], which consist of the current concentrations of substrate $S$ [$g/L$] and biomass $X$ [$g/L$] inside the reactor. The volume $V$ [$L$] of the reactor content is kept constant by setting $Q_{in} = Q$. +""" + +# ╔═╡ 6da59e21-6451-4f82-9387-63b90e88de00 +md""" +## Implementation of the system +""" + +# ╔═╡ 98858b9c-d4f9-451f-a7b9-fcaa012ee28e +md""" +Create a MTK model for the aforementioned problem in order to simulate the evolution of substrate $S$ and biomass $X$ with time as a Stochastic Differential Equation (SDE) problem with a noisy input concentration of the substrate $S_{in}$. +""" + +# ╔═╡ f7941ddf-f1e7-41d1-9764-b644dfed68c0 +md""" +The parameter values are `k=0.2`, `Y=0.76`, `Q=2.0`, `V=40.0` and `Sin=2.2`. Suppose that at $t=0\;h$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concetration of `0.1`$\;g/L$. Simulate the evolution of $S$ and $X$ during `120.0` hours. +""" + +# ╔═╡ 1dea7082-8562-4423-a03e-a8df45f8d57f +md""" +Define the variables `S` and `X` with `@variables`. You can also add the default initial conditions. +""" + +# ╔═╡ c48a1118-817f-4c7f-ae7f-dd0460299036 +missing + +# ╔═╡ 5cd22cdf-708d-4597-8c20-5851af527e86 +md""" +Define the parameters of the system with `@parameters`. Add a noise scaling parameter `n=0.5`. +""" + +# ╔═╡ 5128e6f4-a9d4-4a03-a93d-949f1b21c150 +missing + +# ╔═╡ ea307a00-169c-417e-937a-aa00397f518a +md""" +Instantiate a source `B` of Brownian noise (a Wiener process by default) with `@brownian`. +""" + +# ╔═╡ e8ea7103-684f-45e6-84e0-da763cab1386 +missing + +# ╔═╡ 1090d70a-a23b-40ce-9ab9-6a316c455286 +md""" +Set up the model equations with the noise `n*B` added to `Sin` and put them in a vector/array. Name the array `eqns_ferm`. +""" + +# ╔═╡ 07e86c4d-9634-4304-bba3-3caed3fcda07 +eqns_ferm = missing + +# ╔═╡ 6b65a3a8-fde7-4586-98d6-3f262ec4e946 +md""" +Build a system of equations with `@mtkbuild` and the function `System`. Provide the following arguments to `System`: the model equations and `t`. Name the system `sys_ferm`. +""" + +# ╔═╡ 003fb585-74c2-4479-8f52-faa336d05b4c +missing + +# ╔═╡ 8351a3a0-bbae-4f5c-9655-ded253b798ff +md""" +## Setting initial condition, time span and parameters. +""" + +# ╔═╡ 7be20227-fb5e-4dc2-aa1e-1e94db474a69 +md""" +Initialize a vector `u0` with the default initial condition, set the timespan for the simulation (we will simulate from `0.0` $y$ to `120.0` $y$), and initialize a vector `parms` with the default parameter values. In that way, later, you can change the initial condition and the parameter values if you want to try other values. +""" + +# ╔═╡ ef3f7a41-61ab-4449-8ffc-784cf1e5cbe6 +u0 = missing + +# ╔═╡ 839a624b-ca62-4c57-9511-207b626ce864 +tspan = missing + +# ╔═╡ a4d28c40-e315-4bb9-87a5-2b45dd633e5f +parms = missing + +# ╔═╡ 0545417b-e3f2-42a7-aa30-15c3bca9ed9c +md""" +## Simulating the system as an SDE problem +""" + +# ╔═╡ b2973e86-d89f-476a-8c41-de25a9e5c69d +md""" +Create the SDE problem and store it in `sprob_ferm`. +""" + +# ╔═╡ aeddc31e-9de2-4792-a2d8-59a14dfc8173 +sprob_ferm = missing + +# ╔═╡ 3a981326-2031-4c63-ad31-c44ddd7a88d5 +md""" +Solve the SDE problem. Use `EM()` with `dt=0.1`. Store the solution in `ssol_ferm`: +""" + +# ╔═╡ 6a69c369-4743-48c1-aed9-4f0ccb095707 +ssol_ferm = missing + +# ╔═╡ 6b6d2229-913b-41a2-8101-00e9fef0945a +md""" +Plot the results with the option `ylim=(0.0, 2.0)`: +""" + +# ╔═╡ 593a0e0a-c4d8-4b12-b38b-15b47705f8a7 +missing + +# ╔═╡ 177acfd0-eed1-4a1d-925d-5b65e06c35bf +md""" +## Simulating the system as an EnsembleProblem. +""" + +# ╔═╡ 08746e97-d794-4261-9ba6-9002cf17e4c1 +md""" +Create an `EnsembleProblem` in order to visualize a multiple solutions. Store it in `esprob_ferm`. +""" + +# ╔═╡ 3d07836a-60b1-4584-a89a-3d8bbc72b8cd +esprob_ferm = missing + +# ╔═╡ 98c4ee2b-20ef-43a1-b420-7644d568810b +md""" +Solve the `EnsembleProblem` using the same solver (and time step) as before, for $100$ trajectories. Store the solution in `essol_ferm`. +""" + +# ╔═╡ 683fe575-887b-4bd1-8960-10c04f68354d +essol_ferm = missing + +# ╔═╡ 26746cab-d3e7-4a01-bbbd-9fcb49ef652f +md""" +Plot the results. Use as option again `ylim=(0.0,2.0)` and also `linealpha=0.5` (or `la=0.5`) to modify the line boldness. +""" + +# ╔═╡ 4fcc1d0a-9d30-4056-b8a3-3d802edc42e5 +missing + +# ╔═╡ b7580824-8bea-4fd4-88c7-6d8c1eb3f00d +md""" +Check out the end value of $X$ in the first simulation with `essol_ferm.u[1][:X][end]`. +""" + +# ╔═╡ 28da57db-ff57-4ade-887c-a132a616abd4 +missing + +# ╔═╡ 440c0d44-60c4-444f-b6b0-b53c6d9abb4f +md""" +Create an array/vector with all the end values of $X$. Use therefore a `for`-loop and the `append!` function. Put all values in `Xeq_values`. +""" + +# ╔═╡ 2b61187f-12f4-4b80-b927-f6fb587b486a +# begin +# Xeq_values = missing +# for i=missing +# missing +# end +# end + +# ╔═╡ 705e51c1-2cb9-4162-b356-9364715bbfa8 +md""" +Plot a histogram in the range `[1.0, 1.8]` with a bin size of `0.01` (cf. `bins=1.0:0.01:1.8`). +""" + +# ╔═╡ 89f2a414-ba53-43aa-bf61-1a7fbe252986 +missing + +# ╔═╡ 2d6202ca-fb4a-4b44-847f-07abe120e618 +md""" +Calculate the mean. +""" + +# ╔═╡ ac5a78ad-6575-4c0b-9f9e-a632b648552a +missing + +# ╔═╡ 638304a0-1036-4c54-a40b-4cb8baf826c6 +md""" +Calculate the standard deviation. +""" + +# ╔═╡ fffe5c34-f0e0-400e-bb0f-44f9d9595919 +missing + +# ╔═╡ Cell order: +# ╟─a28e7ddf-76e9-4628-888c-e1d838da75ce +# ╠═eb142900-1d94-11ef-12ed-6951b45f1817 +# ╠═8152f632-af15-4164-a8ff-07c33a9a49b3 +# ╠═e317c2cc-c41a-4489-b06b-7f07a517e20b +# ╠═2e91e92c-5cd2-4018-8a26-9f0f9e01a175 +# ╠═e04f782d-67da-4e21-a3bf-d2ddff4bba0b +# ╠═3552fc05-a4dc-4242-91b3-3eb6fb2ea389 +# ╟─959d6307-a30d-4ae7-970d-b2c7584c2c8f +# ╟─6da59e21-6451-4f82-9387-63b90e88de00 +# ╟─98858b9c-d4f9-451f-a7b9-fcaa012ee28e +# ╟─f7941ddf-f1e7-41d1-9764-b644dfed68c0 +# ╟─1dea7082-8562-4423-a03e-a8df45f8d57f +# ╠═c48a1118-817f-4c7f-ae7f-dd0460299036 +# ╟─5cd22cdf-708d-4597-8c20-5851af527e86 +# ╠═5128e6f4-a9d4-4a03-a93d-949f1b21c150 +# ╟─ea307a00-169c-417e-937a-aa00397f518a +# ╠═e8ea7103-684f-45e6-84e0-da763cab1386 +# ╟─1090d70a-a23b-40ce-9ab9-6a316c455286 +# ╠═07e86c4d-9634-4304-bba3-3caed3fcda07 +# ╟─6b65a3a8-fde7-4586-98d6-3f262ec4e946 +# ╠═003fb585-74c2-4479-8f52-faa336d05b4c +# ╟─8351a3a0-bbae-4f5c-9655-ded253b798ff +# ╟─7be20227-fb5e-4dc2-aa1e-1e94db474a69 +# ╠═ef3f7a41-61ab-4449-8ffc-784cf1e5cbe6 +# ╠═839a624b-ca62-4c57-9511-207b626ce864 +# ╠═a4d28c40-e315-4bb9-87a5-2b45dd633e5f +# ╟─0545417b-e3f2-42a7-aa30-15c3bca9ed9c +# ╟─b2973e86-d89f-476a-8c41-de25a9e5c69d +# ╠═aeddc31e-9de2-4792-a2d8-59a14dfc8173 +# ╟─3a981326-2031-4c63-ad31-c44ddd7a88d5 +# ╠═6a69c369-4743-48c1-aed9-4f0ccb095707 +# ╟─6b6d2229-913b-41a2-8101-00e9fef0945a +# ╠═593a0e0a-c4d8-4b12-b38b-15b47705f8a7 +# ╟─177acfd0-eed1-4a1d-925d-5b65e06c35bf +# ╟─08746e97-d794-4261-9ba6-9002cf17e4c1 +# ╠═3d07836a-60b1-4584-a89a-3d8bbc72b8cd +# ╟─98c4ee2b-20ef-43a1-b420-7644d568810b +# ╠═683fe575-887b-4bd1-8960-10c04f68354d +# ╟─26746cab-d3e7-4a01-bbbd-9fcb49ef652f +# ╠═4fcc1d0a-9d30-4056-b8a3-3d802edc42e5 +# ╟─b7580824-8bea-4fd4-88c7-6d8c1eb3f00d +# ╠═28da57db-ff57-4ade-887c-a132a616abd4 +# ╟─440c0d44-60c4-444f-b6b0-b53c6d9abb4f +# ╠═2b61187f-12f4-4b80-b927-f6fb587b486a +# ╟─705e51c1-2cb9-4162-b356-9364715bbfa8 +# ╠═89f2a414-ba53-43aa-bf61-1a7fbe252986 +# ╟─2d6202ca-fb4a-4b44-847f-07abe120e618 +# ╠═ac5a78ad-6575-4c0b-9f9e-a632b648552a +# ╟─638304a0-1036-4c54-a40b-4cb8baf826c6 +# ╠═fffe5c34-f0e0-400e-bb0f-44f9d9595919 diff --git a/src/exercises/sde_model_heston_mtk_intro.jl b/src/exercises/sde_model_heston_mtk_intro.jl new file mode 100644 index 00000000..19c9fe34 --- /dev/null +++ b/src/exercises/sde_model_heston_mtk_intro.jl @@ -0,0 +1,364 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "17" +#> title = "3. SDE_model_Heston_intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Introduction to solving SDE problems with ModelingToolkit" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 7e26d33b-2401-4e11-a457-bde8316df2f0 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 058d1250-30a2-11f0-05e1-4b134c2349ec +using Markdown, InteractiveUtils + +# ╔═╡ c070007f-b4b8-4edc-8d04-700bf7e323de +using StatsPlots, StatsBase + +# ╔═╡ 7e450e64-a8d6-47e9-9c2a-8bf8a4e2e71e +using ModelingToolkit, StochasticDiffEq + +# ╔═╡ d1185b3f-5fa3-4d84-9a7f-c105b7c456ac +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 6846bf57-0737-4ffd-87cf-955ecb4bd4de +using PlutoUI; TableOfContents() + +# ╔═╡ fd3b7b7e-b573-4c14-afe1-a9a9083f39fe +md""" +# Introduction: Solving SDE problems with ModelingToolkit +""" + +# ╔═╡ ba1450a1-5061-4352-994f-a308c053c3ce +# https://www.goldavenue.com/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fyrlg7o79%2Fproduction%2F2974fbf9471a13b1a69294cfd81839266c779303-2560x1638.jpg&w=750&q=75 +md""" +![Stock and volatility](https://www.goldavenue.com/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fyrlg7o79%2Fproduction%2F2974fbf9471a13b1a69294cfd81839266c779303-2560x1638.jpg&w=750&q=75) +""" + +# ╔═╡ e2ba0bc5-7147-462a-8eb9-76bf7145a51e +md""" +**Stochastic Differential Equations** (SDEs) are mathematical equations used to model +systems influenced by random noise. They extend **Ordinary Differential Equations** (ODEs) by incorporating terms that represent **stochastic processes**, typically in the form of a Wiener process or Brownian motion. SDEs are widely used in various fields, such as physics, biology, finance, and engineering, to describe the evolution of systems under uncertainty or with inherent randomness. +""" + +# ╔═╡ 00b7ef23-1cfd-4923-918a-4d62de6ef17d +md""" +We will illustrate the concepts of SDE problems using a simplified **Heston model**.\ +""" + +# ╔═╡ 9352137e-5210-41fe-b6fe-f5e3c51d7eeb +md""" +In finance, the Heston model, named after Steven L. Heston, is a mathematical model that describes the evolution of the volatility of an underlying stock. It is a stochastic volatility model: such a model assumes that the volatility of the stock is not constant, nor even deterministic, but follows a random process. + +- *Stock*: the goods or merchandise or asset (something having value) +- *Volatility*: a tendency to change quickly and unpredictably +""" + +# ╔═╡ 843c9131-4eb3-4cdd-9136-a99171173b36 +md""" +Our simplified Heston model assumes that $S$, the price of the stock, is determined by a stochastic process: + +$$\cfrac{dS}{dt} = \mu\,S\,dt + \sqrt{V}\,S\,\cfrac{dW}{dt}$$ + +where the volatility $\sqrt{V}$ is given by a Cox-Ingersoll-Ross (CIR) process: + +$$\cfrac{dV}{dt} = \kappa\,(\theta - V)\,dt - \sigma\,\sqrt{V}\,\cfrac{dW}{dt}$$ + +and $W$ is a Wiener process (i.e., continuous random walk). The value $V$, being the square of the volatility, is called the instantaneous *variance*. +""" + +# ╔═╡ 93584181-33dc-4733-b548-945f5b80aaab +md""" +There are five parameters: +- $\mu$: the expected return (drift of the stock) +- $\kappa$: the rate at which the variance of the price reverts to long-term mean $\theta$ +- $\theta$: long-term mean of the variance of the price. +- $\sigma$: the volatility of the volatility +""" + +# ╔═╡ 60c709a9-4943-4eee-991b-95b4209500de +md""" +The values of the parameters are summerized in the following table: + +| Parameter | Value | +|:---------- |:---------- | +| `μ` | 0.03 | +| `κ` | 0.90 | +| `θ` | 0.04 | +| `σ` | 0.08 | + +We will use `100.0` and `0.04` as initial values for `S` and `V` respectively. +""" + +# ╔═╡ 877ceee2-9e53-4ca9-9c75-8cb6252e6fef +md""" +## Implementation of the system +""" + +# ╔═╡ 8d6c3bf9-35cf-48b9-8ce6-7ef711b6fe03 +md""" +We will create a MTK model for the aforementioned problem in order to simulate the evolution of the price of the stock $S$ with time as a Stochastic Differential Equation (SDE) problem with the noisy factors in the equations above. +""" + +# ╔═╡ 3ff63798-3337-4764-87a9-fe2dd037232c +md""" +We define the variables `S` and `V` with `@variables` and we can immediately add default initial conditions. +""" + +# ╔═╡ cdaf2c50-1a58-4f04-ad7d-cf7b0bf39f96 +@variables S(t)=100.0 V(t)=0.04 + +# ╔═╡ 762847df-5bba-4f95-96aa-d30fc8852daa +md""" +We define the parameters `μ`, `κ`, `θ` and `σ` with `@parameters` and we can immediately add their default values. In addition we add the scaling parameter `n` and we set it to `0.5`. We this parameter you can literally scale the noise. +""" + +# ╔═╡ 944f859c-24fb-4f1e-a5a1-2ee248e99965 +@parameters μ=0.03 κ=0.90 θ=0.04 σ=0.08 n=0.5 + +# ╔═╡ ff673d4f-11b1-4e52-a63e-19e007e03b5f +md""" +Next, we instantiate a source `B` of Brownian noise (a Wiener process by default) with `@brownian`. +""" + +# ╔═╡ 48b0b8d1-a569-48c6-b2a8-10de2273752f +@brownian B + +# ╔═╡ f890b80d-61eb-4eb7-89f2-2f733fd00c33 +md""" +Now we set up the model equations including the noise terms `sqrt(V)*S*n*B` and `-σ*sqrt(V)*n*B`, and put them in a vector/array. We have named the array `eqs_spsv`. +""" + +# ╔═╡ 1bc1cdba-43c6-4455-8bc7-2751f5538021 +eqs_spsv = [ + D(S) ~ μ*S + sqrt(V)*S*n*B, + D(V) ~ κ*(θ - V) - σ*sqrt(V)*n*B +] + +# ╔═╡ f5b1f97d-9409-4d92-8a6c-2ecfc3400340 +md""" +Now we are ready to build a system of equations with `@mtkbuild` and the function `System`. We provide the following arguments to `System`: the model equations and `t`. We have named our system `sys_spsv`. +""" + +# ╔═╡ 68ca9da8-a309-4fe0-a754-61305f8e70a9 +@mtkbuild sys_spsv = System(eqs_spsv, t) + +# ╔═╡ 55bbe791-089f-4b01-acfa-c2fd6a18edf0 +md""" +## Simulating the system as an SDE problem +""" + +# ╔═╡ 4baab57b-ca86-46e4-b70a-ff0c4ff91500 +md""" +If you want to modify the default initial conditions or parameter values, you can define new vectors for them. For the sake of completeness, we will define new vectors `u0` and `parms` but using the same default values as were set before. +""" + +# ╔═╡ 915df4bd-6ab3-4267-80e2-d4613d7d46f7 +md""" +### Setting initial conditions +""" + +# ╔═╡ 4dd3594d-033a-479f-b3b6-48458d7f7a21 +u0 = [S=>100.0, V=>0.04] + +# ╔═╡ 76080f37-ddb2-404d-ac40-246e4eff94f1 +md""" +### Setting the timespan +""" + +# ╔═╡ 1f02b734-2519-44bc-b304-924fce19c735 +md""" +We will simulate the price of the stock in the time interval `(0.0, 5.0)`. +""" + +# ╔═╡ fa33322d-e9f3-4065-99c0-c356ac871d19 +tspan = (0.0, 5.0) + +# ╔═╡ 4315f33a-27f3-41fc-9ef1-8645d69c0452 +md""" +### Setting parameter values +""" + +# ╔═╡ c97ab6e7-bc7b-41d4-b8ad-94c1fb9ec64c +parms = [μ=>0.03, κ=>0.90, θ=>0.04, σ=>0.08, n=>0.5] + +# ╔═╡ 44aa6e33-9517-442b-97a9-db2b15f8f32a +md""" +### Creating an SDEProblem +""" + +# ╔═╡ 301599b6-bb60-49a2-90ec-1bd33adc3748 +md""" +We will create the SDE problem using the function `SDEProblem`. As with the function `ODEProblem` you need to provide the same kind of arguments: the name of the MTK system, the vector of initial values, the time span and the vector of parameters. Since the initial conditions and the parameter values were given as default (cf. when we defined them with `@variables` and `@parameters`), we could just put empty vectors (cf. `[]`) if we like. We have named our SDE problem `sprob_spsv`. +""" + +# ╔═╡ fd3faa3e-f530-46a8-901d-3d32e2aad5e4 +sprob_spsv = SDEProblem(sys_spsv, u0, tspan, parms) # using values in u0 and parms +# sprob_spsv = SDEProblem(sys_spsv, [], tspan, []) # using the default values + +# ╔═╡ 128438ca-57e4-4424-9cb7-44b282f67ac7 +md""" +### Solving the SDEProblem + +There are many solving methods available for solving SDE problems. You can find a [list of methods here](https://docs.sciml.ai/DiffEqDocs/stable/solvers/sde_solve/#Full-List-of-Methods). We will simply use the first one in this list, namely `EM()`, with the time step option `dt=0.01` that will introduce some randomness at every time step. We have stored the solution in `ssol_spsv`. +""" + +# ╔═╡ db240bb7-5011-4112-b55e-e05b7ccb769d +ssol_spsv = solve(sprob_spsv, EM(), dt=0.01) + +# ╔═╡ ec980078-64d8-490b-9606-64f38fd3e4ef +md""" +We will only plot the variable `S` using the function `plot` with the option `idxs=[S]`. In our simplified model, the graph of `V` will be exactly the negative of `S` but on a much smaller scale. +""" + +# ╔═╡ c85137a4-edc1-40b0-ac90-02125dc5106a +plot(ssol_spsv; idxs=[S]) + +# ╔═╡ 9b32d5cb-f05b-4775-b3f7-6d286e13fcb5 +md""" +## Simulating the system as an EnsembleProblem. + +In practice, modeling and simulation are used to draw conclusions or gain insights into a system. Unlike ordinary differential equations (ODEs), simulations based on stochastic differential equations (SDEs) +produce different outcomes each time they are run due to their inherent randomness. +Besides, we are often interested in the edge cases: what are the chances of losing more than 20% of my budget or what are the chances of doubling it? +In order to see to have an idea of the extend of the stochastic effect on the solutions, +we can create a so-called *EnsembleProblem*. This allows us to plot many possible solutions in one plot and gain insights in the general behaviour of the system. +In order to create an *EnsembleProblem*, you need to create an *SDEProblem* first. Since we already have our *SDEProblem* called `sprob_spsv`, we can readily create an *EnsembleProblem* from this. All you need to do is call the function `EnsembleProblem` with the name of the SDE problem object (cf. `sprob_spsv`) as argument. +""" + +# ╔═╡ 53629d40-01df-4bfd-bb29-05506cc23a39 +esprob_spsv = EnsembleProblem(sprob_spsv) + +# ╔═╡ decfa049-d033-4049-868e-fa61669e2024 +md""" +### Solving the EnsembleProblem + +Solving the ensemble problem can be done with our, yet familiar, function `solve` as we did when solving the SDE problem, but now we need to provide how many trajectories (simulations) you want to make. If you want an ensemble of 100 simulations, you can put `trajectories=100`. +""" + +# ╔═╡ e4fdecc6-39a8-41c2-ac5c-2c9401393a4a +essol_spsv = solve(esprob_spsv, EM(), dt=0.01, trajectories=100) + +# ╔═╡ da73ed53-cd93-4b03-a14d-ba06a52275e5 +md""" +We can now plot all simulations of `S` simply as before with the function `plot`. +""" + +# ╔═╡ d419be32-54e4-4262-9656-c6690ed1cef1 +plot(essol_spsv; idxs=[S]) + +# ╔═╡ d85efd34-e78e-4e0e-9989-dd0b849bbe5e +md""" +If you want to inspect the end value of $S$ of the 32th simulation, you can do as follow: +""" + +# ╔═╡ 2d3d3fcd-fa00-4cf2-a0ba-14a9b412195a +essol_spsv[32][S][end] + +# ╔═╡ 528e0012-c275-4d88-9fce-8624af804750 +md""" +In order to have an idea of the spread of the end value of S after 5 units of time for all simulations, we will store all the end values in a vector `S_end_vals` using a `for`-loop as illustrated in the following code-cell: +""" + +# ╔═╡ a487e6a0-bb20-4ff6-9e38-242c49078f13 +begin + S_end_vals = [] # define an empty vector + for i = 1:length(essol_spsv) # loop index-wise over the end values + # append the end value of S of the i-th simulation to S_end_vals + append!(S_end_vals, essol_spsv[i][S][end]) + end +end + +# ╔═╡ 4e6dab85-b377-4989-8ad7-80fc45ee78aa +md""" +Next, we will plot a histogram using the end values. Hereby, we will group our data in `bins` of size 5 in the range 0 to 200 (cf. the option `bins=0:5:200`). +""" + +# ╔═╡ b1eaa791-0213-44ab-9df9-e6cee2f9ef2d +histogram(S_end_vals; bins=0:5:200) + +# ╔═╡ b54452ac-3792-425b-886a-0ce218498d9b +md""" +You can determine the mean value of all end values of S in the following way: +""" + +# ╔═╡ 95a28316-028e-431a-920c-e5ffe28bc8a9 +mean(S_end_vals) + +# ╔═╡ 078cd51d-bea4-4408-a35b-255928d39e32 +md""" +You can determine the standard deviation of all end values of S in the following way: +""" + +# ╔═╡ c5bf45b5-9c5f-4f19-96b7-afde835dde19 +std(S_end_vals) + +# ╔═╡ Cell order: +# ╟─fd3b7b7e-b573-4c14-afe1-a9a9083f39fe +# ╠═058d1250-30a2-11f0-05e1-4b134c2349ec +# ╠═7e26d33b-2401-4e11-a457-bde8316df2f0 +# ╠═c070007f-b4b8-4edc-8d04-700bf7e323de +# ╠═7e450e64-a8d6-47e9-9c2a-8bf8a4e2e71e +# ╠═d1185b3f-5fa3-4d84-9a7f-c105b7c456ac +# ╠═6846bf57-0737-4ffd-87cf-955ecb4bd4de +# ╟─ba1450a1-5061-4352-994f-a308c053c3ce +# ╟─e2ba0bc5-7147-462a-8eb9-76bf7145a51e +# ╟─00b7ef23-1cfd-4923-918a-4d62de6ef17d +# ╟─9352137e-5210-41fe-b6fe-f5e3c51d7eeb +# ╟─843c9131-4eb3-4cdd-9136-a99171173b36 +# ╟─93584181-33dc-4733-b548-945f5b80aaab +# ╟─60c709a9-4943-4eee-991b-95b4209500de +# ╟─877ceee2-9e53-4ca9-9c75-8cb6252e6fef +# ╟─8d6c3bf9-35cf-48b9-8ce6-7ef711b6fe03 +# ╟─3ff63798-3337-4764-87a9-fe2dd037232c +# ╠═cdaf2c50-1a58-4f04-ad7d-cf7b0bf39f96 +# ╟─762847df-5bba-4f95-96aa-d30fc8852daa +# ╠═944f859c-24fb-4f1e-a5a1-2ee248e99965 +# ╟─ff673d4f-11b1-4e52-a63e-19e007e03b5f +# ╠═48b0b8d1-a569-48c6-b2a8-10de2273752f +# ╟─f890b80d-61eb-4eb7-89f2-2f733fd00c33 +# ╠═1bc1cdba-43c6-4455-8bc7-2751f5538021 +# ╟─f5b1f97d-9409-4d92-8a6c-2ecfc3400340 +# ╠═68ca9da8-a309-4fe0-a754-61305f8e70a9 +# ╟─55bbe791-089f-4b01-acfa-c2fd6a18edf0 +# ╟─4baab57b-ca86-46e4-b70a-ff0c4ff91500 +# ╟─915df4bd-6ab3-4267-80e2-d4613d7d46f7 +# ╠═4dd3594d-033a-479f-b3b6-48458d7f7a21 +# ╟─76080f37-ddb2-404d-ac40-246e4eff94f1 +# ╟─1f02b734-2519-44bc-b304-924fce19c735 +# ╠═fa33322d-e9f3-4065-99c0-c356ac871d19 +# ╟─4315f33a-27f3-41fc-9ef1-8645d69c0452 +# ╠═c97ab6e7-bc7b-41d4-b8ad-94c1fb9ec64c +# ╟─44aa6e33-9517-442b-97a9-db2b15f8f32a +# ╟─301599b6-bb60-49a2-90ec-1bd33adc3748 +# ╠═fd3faa3e-f530-46a8-901d-3d32e2aad5e4 +# ╟─128438ca-57e4-4424-9cb7-44b282f67ac7 +# ╠═db240bb7-5011-4112-b55e-e05b7ccb769d +# ╟─ec980078-64d8-490b-9606-64f38fd3e4ef +# ╠═c85137a4-edc1-40b0-ac90-02125dc5106a +# ╟─9b32d5cb-f05b-4775-b3f7-6d286e13fcb5 +# ╠═53629d40-01df-4bfd-bb29-05506cc23a39 +# ╟─decfa049-d033-4049-868e-fa61669e2024 +# ╠═e4fdecc6-39a8-41c2-ac5c-2c9401393a4a +# ╟─da73ed53-cd93-4b03-a14d-ba06a52275e5 +# ╠═d419be32-54e4-4262-9656-c6690ed1cef1 +# ╟─d85efd34-e78e-4e0e-9989-dd0b849bbe5e +# ╠═2d3d3fcd-fa00-4cf2-a0ba-14a9b412195a +# ╟─528e0012-c275-4d88-9fce-8624af804750 +# ╠═a487e6a0-bb20-4ff6-9e38-242c49078f13 +# ╟─4e6dab85-b377-4989-8ad7-80fc45ee78aa +# ╠═b1eaa791-0213-44ab-9df9-e6cee2f9ef2d +# ╟─b54452ac-3792-425b-886a-0ce218498d9b +# ╠═95a28316-028e-431a-920c-e5ffe28bc8a9 +# ╟─078cd51d-bea4-4408-a35b-255928d39e32 +# ╠═c5bf45b5-9c5f-4f19-96b7-afde835dde19 From 02d408fc0eb29f4b6350565fee5f3d7cc5cbccb4 Mon Sep 17 00:00:00 2001 From: jarjarbinks02 Date: Thu, 13 Aug 2026 14:15:17 +0200 Subject: [PATCH 4/5] adding practical 4 --- src/exercises/probmod_1-intro.jl | 535 ++++++++++++++++++++++++++++ src/exercises/probmod_2-basics.jl | 332 +++++++++++++++++ src/exercises/probmod_3-advanced.jl | 228 ++++++++++++ src/exercises/probmod_4-review.jl | 77 ++++ 4 files changed, 1172 insertions(+) create mode 100644 src/exercises/probmod_1-intro.jl create mode 100644 src/exercises/probmod_2-basics.jl create mode 100644 src/exercises/probmod_3-advanced.jl create mode 100644 src/exercises/probmod_4-review.jl diff --git a/src/exercises/probmod_1-intro.jl b/src/exercises/probmod_1-intro.jl new file mode 100644 index 00000000..28295e81 --- /dev/null +++ b/src/exercises/probmod_1-intro.jl @@ -0,0 +1,535 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "23" +#> title = "4. ProbMod intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Introduction to the sampling practicals" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 768f7b5e-f6df-11f0-a8ab-2920af629627 +# ╠═╡ show_logs = false +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 0ede7402-6b92-456c-aaf9-89e1b272d63d +using Turing, StatsPlots + +# ╔═╡ 59ce4733-40b6-4102-892c-13b7f46d9d11 +using PlutoUI; TableOfContents() + +# ╔═╡ 866cdda4-1863-4a8b-95fc-2e65849f6346 +md"# Sampling notebook #1: Intro" + +# ╔═╡ e4b9d0c5-776a-4fa0-ad95-3ab632e22609 +md""" +This notebook will guide you through the basics of sampling in Julia. + +To start off, let's load the required packages. +""" + +# ╔═╡ 139b8552-bb44-4547-9c05-f5d435683e31 +md"## Problem description" + +# ╔═╡ 1785baee-b26b-4b2a-a3de-38efea3eb7b5 +md""" +Most students of our beautiful campus Coupure had to endure the bitter task of cycling through the rain. What's worse, even when the rain has stopped, one is not safe from the wetness: get too close behind a fellow cyclist and their back wheel will pelt your face with dirty water droplets. In order to prevent this tragic fate, we must find how far we must stay away from the cyclist in front of us to keep our faces safe. We will do this by simulating the trajectories of the water droplets and **estimating the probability that they will coincide with our face**. +""" + +# ╔═╡ 2adece60-5f0d-408f-82eb-51bf047a6d0f +md""" +![Problem illustration](https://i.imgur.com/7TDkD08.png) +> **Figure 1**: An illustration of the problem. +""" + +# ╔═╡ e1ad094e-d10d-4cca-9b83-f653e02f4d58 +md"## The model" + +# ╔═╡ 034660af-152f-4850-a1d8-7e624ba3b8da +md""" +We consider a simple model for a droplet's motion: it is launched into the air by the back wheel giving it an initial velocity, and then falls down due to gravity. We neglect other factors such as wind resistance. +""" + +# ╔═╡ 09875352-5f8c-4c89-a978-b704bf1ad6c4 +md""" +After simulating the droplet's motion, we need to check whether it hits our face. We'll assume it's $1.5$m to $1.7$m above the ground and at some horizontal distance $x_f$ away from the cyclist in front of us. We then get hit if the droplet's altitude is in the range [1.5, 1.7] when it has travelled a distance $x_f$. +""" + +# ╔═╡ 03f4935b-ac2b-4f73-9cee-ea04f700df83 +md"### Mathematical description" + +# ╔═╡ 543cea22-f592-41ef-a6d2-7d35063c387f +md""" +We can describe the horizontal position $x$ and vertical position $y$ through time $t$ using simple equations from [the physics of projectile motion](https://en.wikipedia.org/wiki/Projectile_motion#Trajectory_in_vacuum) (see also **Figure 2** for an illustration): + +```math +\begin{align} +x(t) &= \mathrm{cos}(α) \, v_0 \, t \, , +\\ y(t) &= \mathrm{sin}(α) \, v_0 \, t - \frac{g}{2} \, t^2 \, , +\end{align} +``` + +where the parameters are: +- $v_0$: the initial velocity. +- $\alpha$: the launching angle. +- $g = 9.8$: the gravitational acceleration. +""" + +# ╔═╡ ee9cd05c-7fa5-4412-b3b4-32b0a1774c87 +md""" +Checking for droplet collision then comes down to finding the time $t_f$ the droplet has travelled a distance $x_f$ +```math +t_f = \frac{x_f}{\mathrm{cos}(\alpha \, v_0)} \, , +``` +evaluating it's y-coordinate there +```math +y_f = \mathrm{sin}(α) \, v_0 \, t_f - \frac{g}{2} \, t_f^2 \, , +``` +and checking if it's in the range of our face +```math +y_f ∈ [1.5, 1.7] \, . +``` +""" + +# ╔═╡ af5a6f7d-b017-4cb6-b54a-a0defbfe7cdf +md""" +![Model illustration](https://i.imgur.com/fMyFGn4.png) +> **Figure 2**: An illustration of the droplet launching model. Note: one vector represents a velocity while the other represents an acceleration! +""" + +# ╔═╡ e8821ac2-baa3-4674-868c-3a5b8593cc95 +md"### The variables" + +# ╔═╡ 4811e1af-9ba4-49b3-a3a1-5d8ff8d5af92 +md""" +To reiterate, we have three variables in our model: +1. The initial droplet velocity $v_0$. +1. The droplet launching angle $\alpha$. +1. The horizontal position of our face $x_f$. + +The first two of these are **stochastic variables**: they don't have a single, exact value, but can rather take a range of different values following some underlying distribution. This can be either because we simply don't know the real value, or there really are random fluctuations on the value of the variable. For our model, we need to specify what these distributions are. We will assume for now we magically know the distributions of these variables, though they are usually inferred from data as we will see next practical. +""" + +# ╔═╡ 01156ef7-b194-4372-8bc8-de9fad5e12f1 +md"For the initial droplet velocity $v_0$, we will assume a normal distribution with mean 5 [m/s] and a standard deviation of 1:" + +# ╔═╡ f30290bd-9d31-4939-b5d6-f89f066775d2 +plot( + Normal(5, 1), + xlabel = "v0 [m/s]", # set x label + ylabel = "Probability density", # set y label + legend = false, # remove legend from figure + title = "Initial velocity prior" # set title +) + +# ╔═╡ dab23eba-3eb1-434b-aef5-e08730e35114 +md"For the launching angle $\alpha$, we will assume a symmetric **triangular distribution** going from $0$ radians to $\pi/2$ radians:" + +# ╔═╡ 299e402c-634a-4b18-b867-489bafaf4564 +plot( + TriangularDist(0, pi/2), + xlabel = "α [rad]", # set x label + ylabel = "Probability density", # set y label + legend = false, # remove legend from figure + title = "Launching angle prior" # set title +) + +# ╔═╡ 314ba0ec-015f-488d-ac63-780b1dbf5bc2 +md"The third is an **input variable**: when cycling, we can choose how far away we stay from the cyclist in front of us. The x-position of our face $x_f$ is therefore an input to the model." + +# ╔═╡ 2878b32a-9e54-40b0-b151-031b95ad161b +md""" +!!! note + As you can see, to visualize a distribution you can simply call the `plot` function on the distribution (and set extra keyword arguments as desired). +""" + +# ╔═╡ c070f3c9-dd80-49f9-b279-c77f46316116 +md"## Defining the model" + +# ╔═╡ 84a08354-7bf3-4783-9285-a7e8850e798b +md""" +Turing models are defined as Julia functions preceded by the `@model` macro. +Inside of them, you can define random variables with the `var ~ Distribution(params)` syntax, aside from doing the usual programming stuff. +""" + +# ╔═╡ 8f94567c-b87b-4eb4-8441-c6434da393a8 +md"First, we define the constant parameter `g` outside of our model, so we can use it later on for plotting:" + +# ╔═╡ 07c7645a-2b56-4089-90c9-736d7794a109 +const g = 9.8 + +# ╔═╡ 43582e68-1289-4d5a-8895-dc06ec2b9b5b +md""" +!!! note + Using a global variable (a variable not defined inside a function) inside of a function, such as our Turing model, will slow down your code. We can fix this by declaring that the value of this global variable is constant with `const`. But don't worry: this is no programming course, so it's no problem if you forget this on your exam. +""" + +# ╔═╡ 40f4edce-13c4-4063-98f4-23fd948c58dd +md""" +Our model can be defined as follows: +""" + +# ╔═╡ f29fd729-0f05-49b5-9280-cf88edf02faf +# note: x_f is an input variable, so we add it as an input to the model +@model function splash(x_f) + # define the initial velocity + v0 ~ Normal(5, 1) + # define the launching angle + α ~ TriangularDist(0, pi/2) + + # get the time it takes the droplet to travel the distance x_f + t_f = x_f / (cos(α) * v0) + # get the droplet's altitude at this time + y_f = sin(α)*v0*t_f - g/2 * t_f^2 + # to respect the ground, set altitude to 0 if it would be negative + y_f = max(0, y_f) + + return y_f +end + +# ╔═╡ 22144610-0c37-4501-b14c-83895d8b0eae +md"This function does not yet know the value of the input variable $x_f$, and is therefore an incomplete model. Calling it with a value for all its inputs will return a complete Turing model. Let's set it to 1 for now." + +# ╔═╡ 92d8ccc8-cd5e-4d0b-a8bf-4be301784a18 +splash_model = splash(1) + +# ╔═╡ 6e1d6cfb-47ed-4c0f-bc5f-c327c58ae128 +md""" +!!! note + Some models don't have any input variables. In this case, you still need to call it to create a "completed" model, though you will simply call it with no input arguments. +""" + +# ╔═╡ 4e2bf07a-5f7c-4f33-b7e7-b481a6f8cef7 +md"## Sampling the model" + +# ╔═╡ cda45ee9-612e-4b2e-aca7-5da34a23dd02 +md""" +Now that we have defined our model, we can use it to answer our question. We will estimate the probability a droplet hits our face from a distance $x_f$ by sampling a large number of different trajectories and checking in which fraction the droplet hits us. +""" + +# ╔═╡ cbad0f13-aa5e-4e74-bf51-482437ff7ad3 +md"### Generating samples" + +# ╔═╡ c00cfd87-e68b-4c13-a4a4-2b4fdded49f8 +md"We sample different sets of values for the model's parameters using the `sample` function:" + +# ╔═╡ aea98d63-9277-4c07-9db5-9c94d940ef84 +chain = sample( + splash_model, # the Turing model we sample + Prior(), # the sampling algorithm - we will always use `Prior()` this practical + 500 # the amount of samples +); + +# ╔═╡ 7e67c4bc-e146-484a-8284-50127c3fbea1 +chain + +# ╔═╡ f648fd3a-057d-4e73-9331-e87c60c014a7 +md""" +!!! note + The `logprior` column gives the log prior probability of a sample. For example, for a sample with $v_0 = 6.1$ and $\alpha = 0.9$, this column will equal $\mathrm{log}(P(v_0 = 6.1) + P(\alpha = 0.9))$, where $P(v_0 = 6.1)$ equals the probability density of its prior distribution, a Normal(5, 1), at $x = 6.1$ (and analogous for $\alpha$). +""" + +# ╔═╡ 1efb24f2-b988-437b-a7b7-2b3b7746dc07 +md"### Getting the model output" + +# ╔═╡ 46f17c07-9aec-4fee-8590-4eeb5a13b93e +md"To answer our question, we are interested in the `y_f` variable returned by the model. We can extract the output variable from our chain using the `generated_quantities` function:" + +# ╔═╡ 071fc405-7a33-4dab-a69a-1e68a0fb35ad +y_f_samples = generated_quantities(splash_model, chain) + +# ╔═╡ 20e99145-e80b-4259-9bca-f7e14c2d355e +md"It's often useful to visualize the distribution of our variable of interest. We can use the `histogram` function for this:" + +# ╔═╡ 45d44cf5-2289-4947-8d4e-1dcddb5447de +histogram(y_f_samples, bins = 15) + +# ╔═╡ 8d179748-40aa-4e88-b277-9234fe938251 +md"Checking whether the droplet altitude matches our face's can be done with a simple inequality operation:" + +# ╔═╡ 633a2b4c-379e-4e18-bbcd-b450930196a5 +face_hit_samples = [1.5 <= y_f <= 1.7 for y_f in y_f_samples] + +# ╔═╡ ad8a6ea2-30ba-4104-824f-12a7ea8e9718 +md"Finally, we can estimate the probability of our face being hit by taking the mean amount of times we got hit in our sample." + +# ╔═╡ e1f9d1e9-8a0c-4b28-ab67-a89e3287b50f +mean(face_hit_samples) + +# ╔═╡ 1c9e2de1-21a0-4d4d-918d-9e2f0fc4d912 +md"""And that's the answer to our question! If we're 1m behind the cyclist in front of us, we can expect to get hit by about ~1% of the droplets they launch (exact number depends on the random samples). You can manually change the value of $x_f$ to investigate for other distances, or scroll down to the [Essentials](#Essentials) section for a handy slider.""" + +# ╔═╡ bf5642f4-09d3-491d-8eb9-47fcdb67c977 +md""" +!!! note + Do you like your code sleek? You can also calculate this probability from your samples with just one line: `mean(1.5 .<= y_f_samples .<= 1.7)` +""" + +# ╔═╡ 3067af6e-df69-48a0-a024-a6e219dcfc7c +md"### Getting the stochastic variables" + +# ╔═╡ cbc04c5f-aa93-4340-a61e-1583d20d86b1 +md"Often, it's enough to get a sample of the variable of interest we `return` in our Turing model. Sometimes, however, it can be useful to also get sampled values for the stochastic variables used to calculate the variable of interest. In this case, we could make a nice plot of the droplet's trajectories if we have all the sampled values of $v_0$ and $\alpha$." + +# ╔═╡ d36784b8-9f3b-4434-88fe-03b75b94754e +md"Getting the values of a model's stochastic variable can be done simply by indexing the chain with the name of that variable as a `String` or `Symbol` (whichever you prefer):" + +# ╔═╡ dad51c0a-b93f-4637-b095-244fd5513838 +begin + v0_samples = chain["v0"] + α_samples = chain[:α] +end + +# ╔═╡ 9093b274-d329-4e4c-a374-d3bc625fa2a6 +md""" +To plot the trajectories, we want an equation for $y$ in function of $x$. We can do this by substituting $x(t)$ into the equation for $y(t)$ to acquire the following: +""" + +# ╔═╡ 0f5fbf8e-3e65-4212-9068-1d9feb09a484 +md""" +```math +y(x) = \frac{\mathrm{sin}(α)}{\mathrm{cos}(α)} \, x - \frac{g}{2} \, \left( \frac{x}{\mathrm{cos}(α) \, v_0} \right)^2 +``` +""" + +# ╔═╡ c9df5202-37f1-4e93-ab94-546ac7bb9444 +md""" +!!! note + You can also use this equation to find $y(x_f)$ in the Turing model. +""" + +# ╔═╡ 4faf9589-a815-4223-92f9-34264fb22401 +md"First we define our trajectory function in function of $x$ and our stochastic variables." + +# ╔═╡ 577df19a-7da2-40b1-b1d4-38c3aec16ae7 +y(x, α, v0) = sin(α)/cos(α)*x - g/2*(x / (cos(α) * v0))^2 + +# ╔═╡ 18f6c707-4369-4360-94a7-ee4a7f4e57d2 +md"Then we get the specific trajectory for every sample of our chain by filling in the stochastic variables with the values we sampled." + +# ╔═╡ 5d0bee7a-8fd0-44ff-adb5-6068477aaf21 +trajectories = [ + # note: every trajectory in our vector is a function of x! + # so we will write an anonymous function x -> function(x,...,...) + # to plot the trajectory in function of x + x -> y(x, α_samples[i], v0_samples[i]) + for i in 1:length(v0_samples) +]; + +# ╔═╡ 00572e1a-d1e6-4c84-b1b2-b53404087dd8 +md"Finally, we plot all sampled trajectories." + +# ╔═╡ f550ed7a-1bba-40b5-9338-cdeb0520aae9 +begin + plot(trajectories, xlims = (0, 3), ylims = (0, 2), label = false, + color = :blue, alpha = 0.3 + ) + plot!([1, 1], [1.5, 1.7], linewidth = 5, color = :orange, label = "face") +end + +# ╔═╡ 3b465fc0-3a9d-4e67-8d4a-a14b3cd61f4e +md""" +!!! note + We set a lot of keyword arguments of our plot to make the figure pretty here, but don't panic: this is still no programming course, so we will always clearly provide which one(s) you need if you need to plot something. If you ever do want to look for one yourself, you can find them on the function's help page (`?plot` or the `🔍 Live docs` in the bottom right corner). +""" + +# ╔═╡ 1de58e6a-4ee2-4d67-b97d-e63bbb9ed057 +md"## Sampling alternative" + +# ╔═╡ 83a8b9b6-72da-4aff-8364-37846b527a2f +md"It's also possible to skip the chain construction entirely and simply call your model as a function to get a sample of the model output. Note that it's not possible to get the corresponding values of the stochastic variables using this approach, so only use this method if you **only** care about the model output." + +# ╔═╡ 771b9cb1-d809-46de-9380-2db73ccd014d +# get a sample of the output 500 times +y_f_samples_alternative = [splash_model() for _ in 1:500] + +# ╔═╡ 0f1cccd8-937d-4fc2-8657-c9967db483b8 +histogram(y_f_samples_alternative, bins = 15) # yup, that's the same distribution + +# ╔═╡ 7806eca0-a2ad-4910-8d6e-edc50073b37b +md"## For-loops for many variables" + +# ╔═╡ 64ebc3ce-83ca-4cee-ab93-cddf51138543 +md""" +Sometimes you need to define a large number of stochastic variables following the same distribution. Let's say, for example, you want to model the total mass of water being splashed on your face assuming that 10 droplets will hit you, each having a mass that is exponentially distributed with a mean of 30 mg. + +This can be done using a **for-loop** as follows: +""" + +# ╔═╡ bdf3c18c-b40b-4fb4-81e7-fe9d821cc166 +@model function splash_mass() + droplet_masses = zeros(10) # instantiate vector of 10 zeros + for i in 1:length(droplet_masses) + droplet_masses[i] ~ Exponential(30) # each element of the vector follows an Exponential distribution + end + total_mass = sum(droplet_masses) + + return total_mass +end + +# ╔═╡ 2cbe2750-38c1-4e7f-8d03-9bed7c16861a +mass_model = splash_mass(); + +# ╔═╡ ad8b48c5-f892-49b3-8f19-64d93424485c +mass_chain = sample(mass_model, Prior(), 1000); + +# ╔═╡ 8507f084-eff5-4351-800d-8eba7ca673bb +total_mass_samples = generated_quantities(mass_model, mass_chain) + +# ╔═╡ befb8f05-6ab7-4594-9cf1-71df47c1df03 +md"## Essentials" + +# ╔═╡ 52af5de0-b516-4947-84f7-dde63e4c513e +md""" +The most essential code for the first practical is reiterated here without long explanations to provide an easy reference for making the practical exercises. Additionally, $x_f$ has been assigned to a slider so you can more easily explore at what distance your face is safe. + +Side note: the code is wrapped in a let block so Pluto won't complain about the same variable names being used again. +""" + +# ╔═╡ 2ceede7e-8f0c-420c-bde2-f16b726cd204 +md""" +!!! note + If you enjoy playing with the $x_f$ slider, the code will run much faster if you comment out the problem definition in the code below (select the code and press `CTRL+/`). This will make it reuse the (same) model defined previously and saves the time to run the model generation. +""" + +# ╔═╡ fbabbc25-6924-4584-9236-3707ab03e859 +@bind x_f Slider(0:0.1:5, default = 1.0, show_value = true) + +# ╔═╡ 9a7926ac-615a-422f-b306-e21c56b8ae1c +# ╠═╡ show_logs = false +let + # model definition (comment out for speed) + @model function splash(x_f) + v0 ~ Normal(5, 1) + α ~ TriangularDist(0, pi/2) + + t_f = x_f / (cos(α) * v0) + y_f = sin(α)*v0*t_f - g/2 * t_f^2 + y_f = max(0, y_f) + return y_f + end + + # get samples + splash_model = splash(x_f) + chain = sample(splash_model, Prior(), 500); + + y_f_samples = generated_quantities(splash_model, chain) + v0_samples = chain["v0"] + α_samples = chain[:α] + + # calculate interesting things + prob_face_hit = mean([1.5 <= y_f <= 1.7 for y_f in y_f_samples]) + + y(x, α, v0) = sin(α)/cos(α)*x - g/2*(x / (cos(α) * v0))^2 + trajectories = [ + x -> y(x, α_samples[i], v0_samples[i]) + for i in 1:length(v0_samples) + ] + plot(trajectories, xlims = (0, 5), ylims = (0, 2), + legend = false, color = :blue, alpha = 0.3, + title = "Probability of getting hit ≈ $(round(100*prob_face_hit, digits = 3))%" + ) + annotate!(x_f, 1.6, text("☺", 30)) +end + +# ╔═╡ Cell order: +# ╟─866cdda4-1863-4a8b-95fc-2e65849f6346 +# ╟─e4b9d0c5-776a-4fa0-ad95-3ab632e22609 +# ╠═768f7b5e-f6df-11f0-a8ab-2920af629627 +# ╠═0ede7402-6b92-456c-aaf9-89e1b272d63d +# ╠═59ce4733-40b6-4102-892c-13b7f46d9d11 +# ╟─139b8552-bb44-4547-9c05-f5d435683e31 +# ╟─1785baee-b26b-4b2a-a3de-38efea3eb7b5 +# ╟─2adece60-5f0d-408f-82eb-51bf047a6d0f +# ╟─e1ad094e-d10d-4cca-9b83-f653e02f4d58 +# ╟─034660af-152f-4850-a1d8-7e624ba3b8da +# ╟─09875352-5f8c-4c89-a978-b704bf1ad6c4 +# ╟─03f4935b-ac2b-4f73-9cee-ea04f700df83 +# ╟─543cea22-f592-41ef-a6d2-7d35063c387f +# ╟─ee9cd05c-7fa5-4412-b3b4-32b0a1774c87 +# ╟─af5a6f7d-b017-4cb6-b54a-a0defbfe7cdf +# ╟─e8821ac2-baa3-4674-868c-3a5b8593cc95 +# ╟─4811e1af-9ba4-49b3-a3a1-5d8ff8d5af92 +# ╟─01156ef7-b194-4372-8bc8-de9fad5e12f1 +# ╠═f30290bd-9d31-4939-b5d6-f89f066775d2 +# ╟─dab23eba-3eb1-434b-aef5-e08730e35114 +# ╠═299e402c-634a-4b18-b867-489bafaf4564 +# ╟─314ba0ec-015f-488d-ac63-780b1dbf5bc2 +# ╟─2878b32a-9e54-40b0-b151-031b95ad161b +# ╟─c070f3c9-dd80-49f9-b279-c77f46316116 +# ╟─84a08354-7bf3-4783-9285-a7e8850e798b +# ╟─8f94567c-b87b-4eb4-8441-c6434da393a8 +# ╠═07c7645a-2b56-4089-90c9-736d7794a109 +# ╟─43582e68-1289-4d5a-8895-dc06ec2b9b5b +# ╟─40f4edce-13c4-4063-98f4-23fd948c58dd +# ╠═f29fd729-0f05-49b5-9280-cf88edf02faf +# ╟─22144610-0c37-4501-b14c-83895d8b0eae +# ╠═92d8ccc8-cd5e-4d0b-a8bf-4be301784a18 +# ╟─6e1d6cfb-47ed-4c0f-bc5f-c327c58ae128 +# ╟─4e2bf07a-5f7c-4f33-b7e7-b481a6f8cef7 +# ╟─cda45ee9-612e-4b2e-aca7-5da34a23dd02 +# ╟─cbad0f13-aa5e-4e74-bf51-482437ff7ad3 +# ╟─c00cfd87-e68b-4c13-a4a4-2b4fdded49f8 +# ╠═aea98d63-9277-4c07-9db5-9c94d940ef84 +# ╠═7e67c4bc-e146-484a-8284-50127c3fbea1 +# ╟─f648fd3a-057d-4e73-9331-e87c60c014a7 +# ╟─1efb24f2-b988-437b-a7b7-2b3b7746dc07 +# ╟─46f17c07-9aec-4fee-8590-4eeb5a13b93e +# ╠═071fc405-7a33-4dab-a69a-1e68a0fb35ad +# ╟─20e99145-e80b-4259-9bca-f7e14c2d355e +# ╠═45d44cf5-2289-4947-8d4e-1dcddb5447de +# ╟─8d179748-40aa-4e88-b277-9234fe938251 +# ╠═633a2b4c-379e-4e18-bbcd-b450930196a5 +# ╟─ad8a6ea2-30ba-4104-824f-12a7ea8e9718 +# ╠═e1f9d1e9-8a0c-4b28-ab67-a89e3287b50f +# ╟─1c9e2de1-21a0-4d4d-918d-9e2f0fc4d912 +# ╠═bf5642f4-09d3-491d-8eb9-47fcdb67c977 +# ╟─3067af6e-df69-48a0-a024-a6e219dcfc7c +# ╟─cbc04c5f-aa93-4340-a61e-1583d20d86b1 +# ╟─d36784b8-9f3b-4434-88fe-03b75b94754e +# ╠═dad51c0a-b93f-4637-b095-244fd5513838 +# ╟─9093b274-d329-4e4c-a374-d3bc625fa2a6 +# ╟─0f5fbf8e-3e65-4212-9068-1d9feb09a484 +# ╟─c9df5202-37f1-4e93-ab94-546ac7bb9444 +# ╟─4faf9589-a815-4223-92f9-34264fb22401 +# ╠═577df19a-7da2-40b1-b1d4-38c3aec16ae7 +# ╟─18f6c707-4369-4360-94a7-ee4a7f4e57d2 +# ╠═5d0bee7a-8fd0-44ff-adb5-6068477aaf21 +# ╟─00572e1a-d1e6-4c84-b1b2-b53404087dd8 +# ╠═f550ed7a-1bba-40b5-9338-cdeb0520aae9 +# ╟─3b465fc0-3a9d-4e67-8d4a-a14b3cd61f4e +# ╟─1de58e6a-4ee2-4d67-b97d-e63bbb9ed057 +# ╟─83a8b9b6-72da-4aff-8364-37846b527a2f +# ╠═771b9cb1-d809-46de-9380-2db73ccd014d +# ╠═0f1cccd8-937d-4fc2-8657-c9967db483b8 +# ╟─7806eca0-a2ad-4910-8d6e-edc50073b37b +# ╟─64ebc3ce-83ca-4cee-ab93-cddf51138543 +# ╠═bdf3c18c-b40b-4fb4-81e7-fe9d821cc166 +# ╠═2cbe2750-38c1-4e7f-8d03-9bed7c16861a +# ╠═ad8b48c5-f892-49b3-8f19-64d93424485c +# ╠═8507f084-eff5-4351-800d-8eba7ca673bb +# ╟─befb8f05-6ab7-4594-9cf1-71df47c1df03 +# ╟─52af5de0-b516-4947-84f7-dde63e4c513e +# ╟─2ceede7e-8f0c-420c-bde2-f16b726cd204 +# ╠═fbabbc25-6924-4584-9236-3707ab03e859 +# ╠═9a7926ac-615a-422f-b306-e21c56b8ae1c diff --git a/src/exercises/probmod_2-basics.jl b/src/exercises/probmod_2-basics.jl new file mode 100644 index 00000000..a21c2c7c --- /dev/null +++ b/src/exercises/probmod_2-basics.jl @@ -0,0 +1,332 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "24" +#> title = "4. ProbMod basics" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Basic sampling exercises" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# ╔═╡ 7d4d4d20-b323-11ef-0926-b14785cb9ab5 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 4cfd4721-e29a-4270-8d15-021bcc966eb1 +using Turing, StatsPlots + +# ╔═╡ 73c2a5db-4019-4d91-b5f1-7ba378cb8c84 +using PlutoUI; TableOfContents() + +# ╔═╡ e4cb065e-12c6-4f1c-8497-1013fa9411d6 +md"# Sampling notebook #2: Basics" + +# ╔═╡ 7026f66f-9076-4aef-ada9-198450ef5da6 +md"## 1: Double Poisson" + +# ╔═╡ bb682b51-c3ac-4e31-9b79-4c13212d84e5 +md""" +Let `X ∼ Poisson(10)` and `Y ~ Poisson(X)`. + +1. Plot the (exact) distribution of `X` and use sampling (n = 10_000) to generate a histogram of `Y`. +2. Estimate the following probabilities: + - `P(3 < Y ≤ 10)`. + - `P(Y^2 > 100)`. +3. Consider `var(X|Y=15)` and `var(Y|X=15)`. + - Estimate them numerically. + - One of the two has a simple analytical answer: which one is it, and what is its exact value? +""" + +# ╔═╡ def27d26-2205-4a66-94f3-eddbc17483bf +md"### 1: Plots" + +# ╔═╡ ff38df99-f843-414d-8e45-b46e06a65c22 +@model function doublepoisson() + X ~ missing + Y ~ missing + return Y +end + +# ╔═╡ 42c18a70-efb3-436b-83bb-b586280d4a4e +dpmodel = doublepoisson(); + +# ╔═╡ a21b2f44-c8d7-4838-926f-8fa39c36884e +dpchain = missing + +# ╔═╡ b22a18d5-f70b-42a5-a09e-3de515148a6d +Y_samples = missing + +# ╔═╡ a11629df-31d2-4ac7-bf8e-80b910cab2fb +missing # plot of X + +# ╔═╡ 34bd60df-272f-49d7-9346-fb4d125fe89b +missing # histogram of Y + +# ╔═╡ 13989ba4-bcf8-4fdd-8aee-ab58c8905bc9 +md"### 2: Probabilities" + +# ╔═╡ aba42086-224f-44a0-b616-f8f651afdd18 +md""" +!!! tip + When comparing a vector of values to a single number, don't forget to use `.` to execute operations element-wise in Julia! + + ✅ `Y_samples .< 1` compares every element of `Y_samples` to `1` + + ❌ `Y_samples < 1` compares an entire vector with a single number → errors :( +""" + +# ╔═╡ 39e4f3eb-b7a9-4ece-846f-eb02dbd77860 +probXY1 = missing + +# ╔═╡ acca0e39-612f-43b8-9c25-74c143041978 +probXY2 = missing + +# ╔═╡ c243ca59-191d-4905-825e-6d7825a3c8a4 +md"### 3: Variances" + +# ╔═╡ 137a1727-08ab-4455-ad8e-88bbead80845 +md""" +!!! hint + To create a sample of $X$ that is conditional on some value(s) of $Y$, you can start from a sample of $X$ and select only those elements for which the corresponding sample of $Y$ has the conditioned value(s). + + In other words, you'll need to index `X_samples` based on `Y_samples` (and vice versa for $\text{var}(Y ∣ X)$). +""" + +# ╔═╡ 263048e5-206c-499e-836c-bfe489ed9b74 +X_samples = missing; + +# ╔═╡ aedd0fe8-da3e-4463-b0cf-7c4f9a22db52 +varXcondY = missing + +# ╔═╡ 7ef53a87-e5df-4724-b896-3d1d46214c68 +varYcondX = missing + +# ╔═╡ ce9e2ce2-26e0-4f17-adf5-c922ba98239d +missing # analytical answer of (missing) + +# ╔═╡ 19e16f77-ea34-45f6-83eb-8d256e5fd10d +md""" +!!! warning + Starting here, only part of the answer's structure will be given. You are therefore expected to **add more code cells** yourself, using the `+` at the left in between two cells. +""" + +# ╔═╡ 9740ea64-cd4f-46b1-a741-02e392280601 +md""" +## 2: Dice +""" + +# ╔═╡ 187854bb-9e30-454d-9e03-cccf77aebb6b +md"You're playing a fun game of Caverns and Chimeras, and are facing off against the mighty Carl the Chimera. The fight is not going great and your next spell **needs to deal 50 or more damage** to slay the scary monster before it kills you. Spells deal **damage equal to the sum of the dice** they let you roll. + +You can choose between your 2 mightiest spells: +- **Watercube**: lets you throw **4 dice with 20 sides** each. +- **Dirtprism**: lets you throw **20 dice with 4 sides** each. +" + +# ╔═╡ 747e3c0a-357a-448a-b479-d0fcbe44a6c0 +md""" +!!! questions + 1. What is the probability that Watercube does the job? Also plot a histogram of its damage. + 1. Do the same for Dirtprism. + 1. What is the probability that watercube deals more damage than dirtprism? +""" + +# ╔═╡ eda95f45-083a-4e65-b57e-bd9890da1f9c +md"### 1: Watercube" + +# ╔═╡ 4fefa78b-746d-4e25-baee-d791eb22d930 +md""" +!!! tip + Consider the humble `DiscreteUniform` distribution. Not sure how it works? Open the **🔍 Live Docs** at the bottom right of the screen for more information +""" + +# ╔═╡ 36477423-5628-4ed3-b54d-9a050557f6b7 +@model function watercube() + roll1 ~ missing + roll2 ~ missing + roll3 ~ missing + roll4 ~ missing + + dicesum = roll1 + roll2 + roll3 + roll4 + return dicesum +end + +# ╔═╡ 5fe1080e-8c2c-4c6b-84b4-3857dc1f399e +watercube_samples = missing # samples of dicesum for watercube + +# ╔═╡ b5886255-5c1d-4d84-b7ee-6c690fa526dc +p_watercube_kills = missing # probability that watercube kills the monster + +# ╔═╡ 84e06162-e3f1-4fd7-baf6-5095172413d2 +missing # histogram + +# ╔═╡ a1b933ac-5d1b-4800-a6e8-e942846b19d8 +md"### 2: Dirtprism" + +# ╔═╡ 6b009ba3-83a8-4176-86d4-dd9f70ed29ec +@model function dirtprism() + # check the "For-loops for many variables" section from the intro notebook! + + dicesum = missing + return dicesum +end + +# ╔═╡ d9152416-8a7b-480c-ba9f-7ab15404b7a6 +p_dirtprism_kills = missing # probability that dirtprism kills the monster + +# ╔═╡ 90e058d7-b3fe-4c42-a652-3c42bf9d851a +missing # histogram + +# ╔═╡ 49790a8f-9f53-4ba7-9543-d6a879b520e0 +md"### 3: Comparison" + +# ╔═╡ 9271b6df-fa3e-4b79-8f0f-48a3b1287b42 +p_watercube_is_better = missing + +# ╔═╡ 34f3014f-f4d4-43d1-b46f-bdca73aee33f +md"## 3: Super eggs" + +# ╔═╡ 372436c4-262f-49b8-b1cf-626b043542bf +md""" +When a chicken lays an egg, there's a small chance it contains two egg yolks. This chance, as well as the number of eggs a chicken lays per year, goes down as the chicken gets older. +""" + +# ╔═╡ 20111742-008a-44c3-8c27-62791cce3e1e +md""" +You can make the following assumptions +- The age $A$ of a random chicken (in years) is discrete and Uniformly distributed between 0 and 10. +- The number of eggs $N$ an $A$-year old chicken lays in a year is Poisson distributed with mean $300 - 20 \, A$. +- The probability $P$ of an $A$-year old chicken's egg having a double yolk is distributed as a `Beta(1, 800 + 100*A)`. +""" + +# ╔═╡ 6e020801-983d-4ebc-a0e9-b5dd58f66c55 +md""" +!!! questions + 1. If someone hands you a random chicken, what is the probability it will lay 2 or more double eggs in a year? + 1. Compare the distributions of double eggs for 1-year old and 5-year old chickens. +""" + +# ╔═╡ 98fcfcef-bf63-4eae-a325-ed4cef6d4fdd +md"### 1: Probability" + +# ╔═╡ a0341046-c14a-494d-a9bd-a60c207c9e76 +md""" +!!! tip + In this exercise, the output variable (the number of double-yolked eggs) is **also a random variable**! In other words, it also follows some distribution. + + When considering what distribution, consider that each of the $N$ eggs represents a "trial" with a $P$ chance of success for a double yolk. +""" + +# ╔═╡ 2b3d930f-53d9-4869-9e4a-86a1a681b9d8 +@model function eggs() + return missing +end + +# ╔═╡ 49d274f7-5810-48b1-8954-22b6a0941a47 +p_multiple_double_eggs = missing + +# ╔═╡ 8dcdc5d7-07b5-4041-b955-485b6f830b75 +md"### 2: Histograms" + +# ╔═╡ cee7c02a-62db-4181-8321-b8bea8fb9339 +missing # histogram 1 + +# ╔═╡ 6b3227c5-78aa-4031-b321-f938757d5ad8 +missing # histogram 2 + +# ╔═╡ ff06c070-50a2-43d0-9729-1c47e728ff52 +md"## 4: Birthdays" + +# ╔═╡ 6ac2238a-16fd-4a8d-b779-8627d87367ed +md""" +Sometimes, people are born on the same day of the year. +""" + +# ╔═╡ 01648616-bf50-4f66-82fc-eaae3de22a38 +md""" +!!! question + What is the probability that, in a class of 150 students, 3 or more share a birthday? Assume the probability for a person to be born is equal on every day of the year. +""" + +# ╔═╡ da44d18c-8be3-446e-a5c2-905af545d2c6 +md""" +!!! tip + You can solve this (among other possibilities) using either a for-loop and the `count_occurences` function given below, or the `Multinomial` distribution. +""" + +# ╔═╡ 52cf545a-d7c7-41d8-ad89-617d2f8b3eb9 +count_occurences(vec) = [count(==(element), vec) for element in unique(vec)] + +# ╔═╡ 0a6ff75d-fbc7-48a1-924b-e16d2654749c +count_occurences([5, 107, 364, 5, 5, 364]) # three 5's, one 107 and two 364's + +# ╔═╡ 2925bb92-32f8-415d-960f-b0a60a1037b8 +@model function birthdays() + missing +end + +# ╔═╡ Cell order: +# ╟─e4cb065e-12c6-4f1c-8497-1013fa9411d6 +# ╠═7d4d4d20-b323-11ef-0926-b14785cb9ab5 +# ╠═4cfd4721-e29a-4270-8d15-021bcc966eb1 +# ╠═73c2a5db-4019-4d91-b5f1-7ba378cb8c84 +# ╟─7026f66f-9076-4aef-ada9-198450ef5da6 +# ╟─bb682b51-c3ac-4e31-9b79-4c13212d84e5 +# ╟─def27d26-2205-4a66-94f3-eddbc17483bf +# ╠═ff38df99-f843-414d-8e45-b46e06a65c22 +# ╠═42c18a70-efb3-436b-83bb-b586280d4a4e +# ╠═a21b2f44-c8d7-4838-926f-8fa39c36884e +# ╠═b22a18d5-f70b-42a5-a09e-3de515148a6d +# ╠═a11629df-31d2-4ac7-bf8e-80b910cab2fb +# ╠═34bd60df-272f-49d7-9346-fb4d125fe89b +# ╟─13989ba4-bcf8-4fdd-8aee-ab58c8905bc9 +# ╟─aba42086-224f-44a0-b616-f8f651afdd18 +# ╠═39e4f3eb-b7a9-4ece-846f-eb02dbd77860 +# ╠═acca0e39-612f-43b8-9c25-74c143041978 +# ╟─c243ca59-191d-4905-825e-6d7825a3c8a4 +# ╟─137a1727-08ab-4455-ad8e-88bbead80845 +# ╠═263048e5-206c-499e-836c-bfe489ed9b74 +# ╠═aedd0fe8-da3e-4463-b0cf-7c4f9a22db52 +# ╠═7ef53a87-e5df-4724-b896-3d1d46214c68 +# ╠═ce9e2ce2-26e0-4f17-adf5-c922ba98239d +# ╟─19e16f77-ea34-45f6-83eb-8d256e5fd10d +# ╟─9740ea64-cd4f-46b1-a741-02e392280601 +# ╟─187854bb-9e30-454d-9e03-cccf77aebb6b +# ╟─747e3c0a-357a-448a-b479-d0fcbe44a6c0 +# ╟─eda95f45-083a-4e65-b57e-bd9890da1f9c +# ╟─4fefa78b-746d-4e25-baee-d791eb22d930 +# ╠═36477423-5628-4ed3-b54d-9a050557f6b7 +# ╠═5fe1080e-8c2c-4c6b-84b4-3857dc1f399e +# ╠═b5886255-5c1d-4d84-b7ee-6c690fa526dc +# ╠═84e06162-e3f1-4fd7-baf6-5095172413d2 +# ╟─a1b933ac-5d1b-4800-a6e8-e942846b19d8 +# ╠═6b009ba3-83a8-4176-86d4-dd9f70ed29ec +# ╠═d9152416-8a7b-480c-ba9f-7ab15404b7a6 +# ╠═90e058d7-b3fe-4c42-a652-3c42bf9d851a +# ╟─49790a8f-9f53-4ba7-9543-d6a879b520e0 +# ╠═9271b6df-fa3e-4b79-8f0f-48a3b1287b42 +# ╟─34f3014f-f4d4-43d1-b46f-bdca73aee33f +# ╟─372436c4-262f-49b8-b1cf-626b043542bf +# ╟─20111742-008a-44c3-8c27-62791cce3e1e +# ╟─6e020801-983d-4ebc-a0e9-b5dd58f66c55 +# ╟─98fcfcef-bf63-4eae-a325-ed4cef6d4fdd +# ╟─a0341046-c14a-494d-a9bd-a60c207c9e76 +# ╠═2b3d930f-53d9-4869-9e4a-86a1a681b9d8 +# ╠═49d274f7-5810-48b1-8954-22b6a0941a47 +# ╟─8dcdc5d7-07b5-4041-b955-485b6f830b75 +# ╠═cee7c02a-62db-4181-8321-b8bea8fb9339 +# ╠═6b3227c5-78aa-4031-b321-f938757d5ad8 +# ╟─ff06c070-50a2-43d0-9729-1c47e728ff52 +# ╟─6ac2238a-16fd-4a8d-b779-8627d87367ed +# ╟─01648616-bf50-4f66-82fc-eaae3de22a38 +# ╟─da44d18c-8be3-446e-a5c2-905af545d2c6 +# ╠═52cf545a-d7c7-41d8-ad89-617d2f8b3eb9 +# ╠═0a6ff75d-fbc7-48a1-924b-e16d2654749c +# ╠═2925bb92-32f8-415d-960f-b0a60a1037b8 diff --git a/src/exercises/probmod_3-advanced.jl b/src/exercises/probmod_3-advanced.jl new file mode 100644 index 00000000..232b1aab --- /dev/null +++ b/src/exercises/probmod_3-advanced.jl @@ -0,0 +1,228 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "25" +#> title = "4. ProbMod advanced" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Advanced sampling exercises" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 886c7932-da4b-45cc-ba73-8047389e4895 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 80bc0e86-5ad3-4d61-9600-8dc05b86599d +using Turing, StatsPlots + +# ╔═╡ fe68a4dd-038c-4f94-a4f0-48933ff2fa87 +using PlutoUI; TableOfContents() + +# ╔═╡ 52a38b60-178b-4a1d-ac32-e73fafd339f9 +md"# Sampling notebook #3: Advanced" + +# ╔═╡ 116b840c-e766-4ff6-aafa-0977fb122992 +md"## 1: Petridish peril" + +# ╔═╡ 415b5ba8-3f6d-46ea-8f89-19fa7c0e74f9 +md""" +Living the microbiology master thesis life, your mornings consist of inoculating petridishes with bacteria. Somewhere along the day, you need to split them. You want to do this **after** there's a decent amount of bacteria in the dish (>10\_000) but **before** they have overgrown the entire dish and start dying (<100\_000). This condition we call **splittable**. + +You'd like to estimate how long after inoculation you should return to your bacteria so that they're most likely to be in a splittable state. +""" + +# ╔═╡ 3691d6aa-c717-46e8-b8b3-f4aa56c9f761 +md""" +Bacteria follow **logistic growth**, and you can use the following assumptions: +- The initial population size $P_0$ has a 75% chance of originating from a small droplet and a 25% chance for a big droplet + - For small droplets, `P0` follows a `Poisson(10)` + - For big droplets, `P0` follows a `Poisson(30)` +- The growth rate $r$ follows a `LogNormal(0.0, 0.3)` +- The growth capacity $K$ of the inoculated medium follows a `Normal(1e5, 1e4)` +""" + +# ╔═╡ 749cacb9-b73d-470e-bf58-5550db5de7e0 +md""" +!!! questions + 1. Plot the prior distribution of P0. + 2. What is the probability that your bacteria are in a splittable state 8 hours after inoculation? + 3. Plot 100 of the sampled logistic growth curves from 0 to 12 hours. +""" + +# ╔═╡ cec8b8e8-850e-4549-97fc-71eb35b8334b +md"### 1: Droplet Prior" + +# ╔═╡ bebe6f6b-9603-4062-8685-363ed7ff3dcb +md""" +!!! tip + A simple way of representing the distribution of P0 is through a mixture model. + Mixture models are a way of modeling something that has a chance to be from different, simple distributions. + + If you wanted to model a variable that has a 0.8 chance of being from a `Normal(0, 1)` and a 0.2 chance of being from an `Exponential(10)`, you would model it as follows in Turing: + `MixtureModel([Normal(0, 1), Exponential(10)], [0.8, 0.2])` + + For the interested reader, mixture models are explained in more detail in theory section `4.5.2`. +""" + +# ╔═╡ eb6dc4e7-e779-4bfc-b865-3defa3894181 +missing # plot + +# ╔═╡ 107533fc-b300-4b8d-bea2-a3aa6a37938d +md"### 2: Probability" + +# ╔═╡ bde57599-1dca-41a4-94aa-498da72c2012 +logistic(t, P0, r, K) = K / (1 + (K - P0)/P0 * exp(-r*t)) + +# ╔═╡ 976cfb96-3196-4a61-bd5f-e4f5d24ba1e9 +@model function petrigrowth(t) + missing + return splittable +end + +# ╔═╡ e3092915-a083-425e-8fa1-b7bf370abc8a +prob_splittable = missing + +# ╔═╡ e4f42f16-5cce-4fc2-aa01-8971f37c710e +md"### 3: Plot" + +# ╔═╡ 2b39e0bd-91cd-456e-9053-7b8fe5a395fb +md""" +!!! tip + Remember: anonymous functions can be defined using `myfun = x -> ...`, and can be visualized using `plot(myfun)`. The same syntax applies if `myfun` is a vector of functions. However, don't forget it was asked to plot only **100** functions. +""" + +# ╔═╡ ac37dc49-ff1d-41c6-a2e3-74be2aaabd4c +logfuns = missing + +# ╔═╡ d6c193d7-9fe2-413b-801f-ebc33c772ee9 +missing # plot + +# ╔═╡ c8941726-9e81-47fc-9b7e-cb3b5c0c61ca +md"# 2: Attraction" + +# ╔═╡ 431023df-3724-4325-b0ac-96dbf5e4fd20 +md""" +Following a course on electromagnetism will teach one that computing the net force between 2 arbitrary shapes can be a terrifying task. Tragedy has it then, that this is a very general problem with applications from making fusion reactors to space travel. We can ease the pain by turning it into a sampling problem. + +We'll start in a humble manner and simulate **the gravitational force between 2 cubes**. Both cubes are size 1. The first cube is in [0, 1] x [0, 1] x [0, 1], and the second cube in [1.1, 2.1] x [0, 1] x [0, 1], as shown in the figure below. +""" + +# ╔═╡ 599ac984-ef1d-4c7a-8e87-9d4ddb1aa710 +begin + xe = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0] + ye = [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1] + ze = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1] + + xe2 = xe .+ 1.1 + + plot(xlims = (-0.5, 2.5), ylims = (-1, 2), zlims = (0, 3)) + plot!(xe, ye, ze; color = :blue, linewidth = 0.5, label = "cube 1") + plot!(xe2, ye, ze; color = :orange, lw = 0.5, label = "cube 2") +end + +# ╔═╡ 4cb55330-8a8a-4622-af4a-ab6f0b643123 +md""" +You can sample the gravitational force between both cubes by **randomly sampling a point from both cubes** and using the formula for gravitational force between those points, ignoring all constants: + +```math +F = \frac{1}{r^2} \, . +``` +""" + +# ╔═╡ ceeeee76-e31c-4429-8ed0-e1c503433dbf +md""" +!!! questions + 1. What is the estimated total force between the two cubes? Is this the same as if you had treated the cubes as point masses? + 1. To estimate the net force, you take the average of $n$ samples. Of course, the result will vary every time you take a new sample: if you take the average of only 10 samples, your estimated total force will vary wildly! We can quantify how much the estimated total force varies by taking a **sample of sample averages** and then calculating the variance. Assuming you want your sample average to have a variance of no more than $0.01$, how many samples do you need? Visualise the distribution of the sample average. + - EXTRA: How does the [central limit theorem](https://en.wikipedia.org/wiki/Central_limit_theorem) apply to this question? Can you use it to answer the question with less trial and error? +""" + +# ╔═╡ 9be28327-d87f-4d50-bbcc-91e799f14dbf +md"### 1: Net Force" + +# ╔═╡ 06d0e92f-1f07-41fd-b6ee-e94eb539627d +@model function cubeforce() + F = missing + return F +end + +# ╔═╡ 43119060-bd35-4c4c-8831-d5c5df1d8dd5 +cubemodel = cubeforce(); + +# ╔═╡ 7b117453-0875-4b41-a228-866c6c0a8208 +estimated_force = missing + +# ╔═╡ b28cfbae-2fac-4b38-b234-53f71e381bcd +pointmass_force = missing # (doesn't require Turing, only maths) + +# ╔═╡ 304d6052-6d3d-487c-8c01-dab259276d6f +md"### 2: Variance of Estimator" + +# ╔═╡ e38e8f51-90db-4136-84e2-f06cd03d502a +@bind n Slider(10:10:200, show_value = true) + +# ╔═╡ 55cf127f-5534-4926-955a-487ea9553b70 +force_samples = missing + # multiple samples of your estimated force, using `n` samples + +# ╔═╡ 45f0813b-c4c9-4f13-8d66-1e58293c4422 +missing # histogram of estimated force samples + +# ╔═╡ f6a67c1e-3677-4e51-b6dd-5d11a5146ea7 +force_samples_var = missing + # variance of the estimated force + +# ╔═╡ Cell order: +# ╟─52a38b60-178b-4a1d-ac32-e73fafd339f9 +# ╠═886c7932-da4b-45cc-ba73-8047389e4895 +# ╠═80bc0e86-5ad3-4d61-9600-8dc05b86599d +# ╠═fe68a4dd-038c-4f94-a4f0-48933ff2fa87 +# ╟─116b840c-e766-4ff6-aafa-0977fb122992 +# ╟─415b5ba8-3f6d-46ea-8f89-19fa7c0e74f9 +# ╟─3691d6aa-c717-46e8-b8b3-f4aa56c9f761 +# ╟─749cacb9-b73d-470e-bf58-5550db5de7e0 +# ╟─cec8b8e8-850e-4549-97fc-71eb35b8334b +# ╟─bebe6f6b-9603-4062-8685-363ed7ff3dcb +# ╠═eb6dc4e7-e779-4bfc-b865-3defa3894181 +# ╟─107533fc-b300-4b8d-bea2-a3aa6a37938d +# ╠═bde57599-1dca-41a4-94aa-498da72c2012 +# ╠═976cfb96-3196-4a61-bd5f-e4f5d24ba1e9 +# ╠═e3092915-a083-425e-8fa1-b7bf370abc8a +# ╟─e4f42f16-5cce-4fc2-aa01-8971f37c710e +# ╟─2b39e0bd-91cd-456e-9053-7b8fe5a395fb +# ╠═ac37dc49-ff1d-41c6-a2e3-74be2aaabd4c +# ╠═d6c193d7-9fe2-413b-801f-ebc33c772ee9 +# ╟─c8941726-9e81-47fc-9b7e-cb3b5c0c61ca +# ╟─431023df-3724-4325-b0ac-96dbf5e4fd20 +# ╟─599ac984-ef1d-4c7a-8e87-9d4ddb1aa710 +# ╟─4cb55330-8a8a-4622-af4a-ab6f0b643123 +# ╟─ceeeee76-e31c-4429-8ed0-e1c503433dbf +# ╟─9be28327-d87f-4d50-bbcc-91e799f14dbf +# ╠═06d0e92f-1f07-41fd-b6ee-e94eb539627d +# ╠═43119060-bd35-4c4c-8831-d5c5df1d8dd5 +# ╠═7b117453-0875-4b41-a228-866c6c0a8208 +# ╠═b28cfbae-2fac-4b38-b234-53f71e381bcd +# ╟─304d6052-6d3d-487c-8c01-dab259276d6f +# ╠═e38e8f51-90db-4136-84e2-f06cd03d502a +# ╠═55cf127f-5534-4926-955a-487ea9553b70 +# ╠═45f0813b-c4c9-4f13-8d66-1e58293c4422 +# ╠═f6a67c1e-3677-4e51-b6dd-5d11a5146ea7 diff --git a/src/exercises/probmod_4-review.jl b/src/exercises/probmod_4-review.jl new file mode 100644 index 00000000..42ee15b9 --- /dev/null +++ b/src/exercises/probmod_4-review.jl @@ -0,0 +1,77 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "26" +#> title = "4. ProbMod review" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Review sampling exercise" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# ╔═╡ 882b942c-a5ae-445d-a946-6eb8cddc423e +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ aa2737db-f89f-4e08-9abe-0a86e8880c19 +using Turing, StatsPlots + +# ╔═╡ 5be87389-1a97-481e-bec6-1d781f016077 +md"# Review exercise: Buffon's needles" + +# ╔═╡ a6435b94-f1af-4609-abfc-93d88730d023 +md""" +A wise man once said: ["there is no greater joy than estimating π"](https://en.wikipedia.org/wiki/Approximations_of_%CF%80). One method to accomplish this is using [Buffon's needle problem](https://en.wikipedia.org/wiki/Buffon%27s_needle_problem). + +The experiment is as follows: consider a floor with parallel lines all a distance of 1 away from eachother. Now drop a needle of length 1 (and width ~0) on the floor with a **random position and angle**. What is the probability $P_{cross}$ that the needle will cross one of the lines? +""" + +# ╔═╡ df1758da-50fa-4476-a132-b952620110c6 +md"The following image illustrates the problem (imagine $l$ = $t$ = 1) for two needles, where `a` crosses a line and `b` does not." + +# ╔═╡ c2ba2183-957f-4b94-a22e-0c2f3dd957ad +html""" + +""" + +# ╔═╡ d71cf8dc-99e5-48e0-9abe-2242a6ccc30b +md""" +Using sampling magic, it's not difficult to make an estimate of this probability, $\hat{P}_{cross}$. Solving the problem analytically shows that the exact value is: +```math +P_{cross} = \frac{2}{\pi} +``` + +Therefore, our estimator for π is: +```math +\hat{π} = \frac{2}{\hat{P}_{cross}} +``` +""" + +# ╔═╡ 99747189-d201-4583-ac9f-6875b0b606f2 +md""" +!!! question + Estimate π using the Buffon's needle approximation. +""" + +# ╔═╡ 6656cc86-db29-42ef-b612-c09252edfd49 +md""" +!!! hint + Assuming the lines are vertical, you only need to consider the **x-coordinates** of both ends of the needle. +""" + +# ╔═╡ Cell order: +# ╠═882b942c-a5ae-445d-a946-6eb8cddc423e +# ╠═aa2737db-f89f-4e08-9abe-0a86e8880c19 +# ╟─5be87389-1a97-481e-bec6-1d781f016077 +# ╟─a6435b94-f1af-4609-abfc-93d88730d023 +# ╟─df1758da-50fa-4476-a132-b952620110c6 +# ╟─c2ba2183-957f-4b94-a22e-0c2f3dd957ad +# ╟─d71cf8dc-99e5-48e0-9abe-2242a6ccc30b +# ╟─99747189-d201-4583-ac9f-6875b0b606f2 +# ╟─6656cc86-db29-42ef-b612-c09252edfd49 From 5762b7d0a6566d943f5440506d6ac44c719bac5a Mon Sep 17 00:00:00 2001 From: jarjarbinks02 Date: Thu, 13 Aug 2026 14:20:13 +0200 Subject: [PATCH 5/5] adding practical 5 to 8 --- src/exercises/MCMC_1-intro.jl | 487 +++++++ src/exercises/MCMC_2-basics.jl | 573 ++++++++ src/exercises/MCMC_3-advanced.jl | 211 +++ src/exercises/MCMC_4-review.jl | 103 ++ src/exercises/calib_fermenter_monod.jl | 270 ++++ src/exercises/calib_intro.jl | 1030 ++++++++++++++ src/exercises/calib_irrigation.jl | 316 +++++ src/exercises/model_selection_intro.jl | 1354 +++++++++++++++++++ src/exercises/optim_wastewater_treatment.jl | 337 +++++ src/exercises/probabilistic_selection.jl | 643 +++++++++ src/exercises/sens_bitrophic_model.jl | 413 ++++++ src/exercises/sens_fermenter_monod.jl | 678 ++++++++++ src/exercises/sens_insuline.jl | 532 ++++++++ src/exercises/sens_intro.jl | 854 ++++++++++++ src/exercises/uncert_bitrophic_model.jl | 168 +++ src/exercises/uncert_fermenter_monod.jl | 182 +++ src/exercises/uncert_intro.jl | 637 +++++++++ 17 files changed, 8788 insertions(+) create mode 100644 src/exercises/MCMC_1-intro.jl create mode 100644 src/exercises/MCMC_2-basics.jl create mode 100644 src/exercises/MCMC_3-advanced.jl create mode 100644 src/exercises/MCMC_4-review.jl create mode 100644 src/exercises/calib_fermenter_monod.jl create mode 100644 src/exercises/calib_intro.jl create mode 100644 src/exercises/calib_irrigation.jl create mode 100644 src/exercises/model_selection_intro.jl create mode 100644 src/exercises/optim_wastewater_treatment.jl create mode 100644 src/exercises/probabilistic_selection.jl create mode 100644 src/exercises/sens_bitrophic_model.jl create mode 100644 src/exercises/sens_fermenter_monod.jl create mode 100644 src/exercises/sens_insuline.jl create mode 100644 src/exercises/sens_intro.jl create mode 100644 src/exercises/uncert_bitrophic_model.jl create mode 100644 src/exercises/uncert_fermenter_monod.jl create mode 100644 src/exercises/uncert_intro.jl diff --git a/src/exercises/MCMC_1-intro.jl b/src/exercises/MCMC_1-intro.jl new file mode 100644 index 00000000..737405c5 --- /dev/null +++ b/src/exercises/MCMC_1-intro.jl @@ -0,0 +1,487 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "27" +#> title = "5. MCMC intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "MCMC intro" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# ╔═╡ 06bdc430-b965-11ef-36a4-3d863afbaf6e +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ ce9c8c34-3690-4241-b021-c08868157a55 +using Turing, StatsPlots + +# ╔═╡ ea5d107c-f171-401f-8972-6a928ab72907 +using PlutoUI; TableOfContents() + +# ╔═╡ eabed73e-19dc-4265-965a-cf762d630fb3 +md"# Inference notebook #1: Intro" + +# ╔═╡ daed8bc0-8a85-45ce-84ac-0d13ff1923f1 +md"## Problem" + +# ╔═╡ 6a79727c-5b5e-43cc-862f-7182c1ea878c +md""" +According to the [molecular clock hypothesis](https://en.wikipedia.org/wiki/Molecular_clock), the average amount of mutations in a gene $\bar{N}$ is proportional to how much time $t$ has passed, and identical for all species: + +$\bar{N} = \alpha \, t \, .$ + +While this is a bit of an oversimplification, the concept has become an important tool in evolutionary biology to estimate how long ago species have diverged. +""" + +# ╔═╡ 83aa4c69-c027-4ade-ac44-438d784b2b78 +md""" +Consider the below figure of a small slice of the [tree of life](https://en.wikipedia.org/wiki/Tree_of_life_(biology)). Every animal represents a (fossilized) individual living during some point in evolution. +""" + +# ╔═╡ 3ecd3ac7-2621-4ea6-8ff1-69962769d934 +md""" +![Evolution example](https://raw.githubusercontent.com/Kermit-UGent/ModSim/2a369561ce842cf079d7660a36d0d9308739dc69/examples/ProbMod/figures/treeoflife.excalidraw.svg) +""" + +# ╔═╡ 71bbd593-f2e4-40dd-b682-40e65305ebb3 +md""" +We start at time 0 with a common ancestor of fish and terrestrial animals. 30 million years (Ma) later it diverges into ray-finned fish, which will give rise to most modern fish species, and lob-finned fish, which will give rise to i.e. mammals and reptiles. + +The ray-finned fish fossil is also one of the individuals for which we have DNA for its *cytochrome C* gene. The number represents that it has 25 mutations in this gene compared to the gene's sequence from our starting organism, the ancient bony fish fossil. +""" + +# ╔═╡ 138538d4-f6b5-4b8e-af3a-273858cc463c +md""" +Taking into account all fossils, we can see that the number of mutations is roughly proportional with the time that has passed. +""" + +# ╔═╡ f49c6d3f-5870-4473-b142-799fa84dbfb7 +times = [30, 138, 375, 450] + +# ╔═╡ 4ea82c1e-490f-4d88-b60c-5c2118409408 +observed_mutations = [25, 94, 302, 335] + +# ╔═╡ 26128162-e355-4316-8d74-291fbca194a6 +scatter(times, observed_mutations, xlabel = "Time (My)", ylabel = "Number of mutations", legend = false, xlims = (0, 500), ylims = (0, 400)) + +# ╔═╡ d3fdfa6f-1ee2-48d0-bb6c-483a6fb4faf9 +md"This leads us to our first question:" + +# ╔═╡ d443d3e8-c1ee-467f-88b2-a0211d5b5cc0 +md""" +!!! question + - What are realistic values for *cytochrome C*'s mutation rate `α`? +""" + +# ╔═╡ 5d83f336-372b-4493-baf6-8efffb663ff1 +md""" +Figuring out the answer to the first question allows for some exciting follow-up questions! Consider for example that you find a new fossil of an ancient ancestor of the **seahorses**. +""" + +# ╔═╡ 6666a388-1c45-4f6f-804f-eb3a260eae98 +md"![Sharkmoment](https://raw.githubusercontent.com/Kermit-UGent/ModSim/2a369561ce842cf079d7660a36d0d9308739dc69/examples/ProbMod/figures/treeoflife2.excalidraw.svg)" + +# ╔═╡ b0dbd481-ce36-4364-b995-b4fa8f36f76d +md""" +You don't know how old the fossil is, but you do find that the fossilized DNA contains **156** mutations in the _cytochrome c_ gene. How old should it be estimated as? +""" + +# ╔═╡ 641a1a77-6fcb-4a6c-8f8f-3b171704efa8 +md"Our second question is then:" + +# ╔═╡ 442aa57f-79d8-4b22-93b2-466ac92c2b13 +md""" +!!! question + - What values are likely for the seahorse-ancestor fossil's age? +""" + +# ╔═╡ 12f017e3-b7b8-408d-a677-52dd2ea900eb +md"## Explanation" + +# ╔═╡ 7900fd13-cc69-41c5-972b-16d5b6d5452a +md"""### Making the model""" + +# ╔═╡ 430927e8-7fb8-494c-b9da-d46f000c142f +md""" +We start again by defining a Turing model. Similar to the models of previous practical, it describes the *forward process*: how do you generate your observations (the amount of mutations $N$) based on your inputs (time that has passed $t$) and parameters (the mutation rate $α$)? + +As discussed in the introduction, we assume a linear relationship between the **average** amount of mutations $\bar{N}$ and $t$: + +$\bar{N} = \alpha \, t$ +""" + +# ╔═╡ 656d3299-e5d0-4799-84ae-da32f51bd92e +md""" +Since the accumulation of mutations is a random process, we can't expect the number of mutations $N$ to be exactly the predicted average $\bar{N}$. We can however expect it to be close to the predicted average. We can express this by saying the amount of mutations follows a distribution centered around the expected average: + +$N \sim \text{Poisson}(\bar{N}) \, .$ +""" + +# ╔═╡ 6ec75944-0b62-43a3-80e9-d6cd2ebd4478 +md""" +!!! note + Can you explain why a Poisson distribution is a natural fit here? +""" + +# ╔═╡ 0b5205c2-0eb4-4734-8470-055d29954f63 +md""" +One problem: our model uses the gene's mutation rate `α`, but we don't know what it is. However, we do have some **prior** knowledge about mutation rates of essential genes (in general): they don't tend to be much larger than a few bp/My. We can encode this information by giving `α` the prior distribution `Exponential(2)`. +""" + +# ╔═╡ 2034e550-84df-42ee-9f0d-2bf417563dd4 +prior_alpha = Exponential(2) + +# ╔═╡ 3221f614-ef9a-4e09-a7da-b649fff0ab61 +plot(prior_alpha, title = "Prior belief of α", legend = false, xlabel = "α", ylabel = "Probability density") + +# ╔═╡ 7a26cfec-b362-41c4-9b2e-1d040db8a5eb +md""" +We can sample some values from this prior distribution and use them to plot the *a priori* expected trends between time $t$ and average number of mutations $\bar{N}$: +""" + +# ╔═╡ 1080294d-8da9-4326-a53b-afe158dc2ab9 +begin + plot(xlabel = "Time (My)", ylabel = "Number of mutations", xlims = (0, 500), + ylims = (0, 400), title = "A priori relationship between t and mean N"); + scatter!(times, observed_mutations, label = "Cyt C", color = :deeppink); + for α in rand(prior_alpha, 200) + plot!(x -> α*x, color = :dodgerblue, alpha = 0.3, label = false); + end + plot!() +end + +# ╔═╡ 624a0a67-c5c1-437c-89e1-e08e83003b41 +md""" +We can see that some of the mutation rates from our prior distribution result in a relationship that matches the data well. However, our prior belief is *much too broad*: only a few of the lines are realistic for the data. Finding which of these lines from our prior belief are realistic for the data is essentially what inference does. +""" + +# ╔═╡ 97257007-fbfa-4065-8788-869801ce3730 +md""" +!!! note + Why use `Exponential(2)` for the prior? + + Choosing a prior distribution is largely subjective and a big reason why some people are not fond of Bayesian modeling. There is no "one correct prior distribution". + + However, different choices of reasonable priors often give very similar outcomes. Try running this notebook at the end with a different prior for `α`, such as `Exponential(10)` or `Uniform(0, 100)`. When are the results significantly different? +""" + +# ╔═╡ 2a652734-bf47-405b-abb3-00e3d453bf47 +md""" +**In summary**, we now have: +- a model that predicts our output $N$ from the input $t$: +$\bar{N} = α \, t \, .$ +$N \sim \text{Poisson}(\bar{N}) \, .$ +- a prior distribution for the parameter $α$. +""" + +# ╔═╡ c52854b2-b2b6-4437-b65b-a1f912d054d5 +md"We then translate it into a Turing model:" + +# ╔═╡ 950c03a9-0056-4760-81f8-dd10d1e55cea +@model function mutations(ts) + α ~ prior_alpha # prior distribution of parameter + + N = zeros(length(ts)) # output variable: in this model we have multiple values, so we need to preallocate a vector + for i in 1:length(ts) + N_average = α * ts[i] + N[i] ~ Poisson(N_average) + end + + return N +end + +# ╔═╡ 31378eb3-51a5-4ad6-a713-7f77c7ceafcc +md""" +As you may notice, we have defined the mutation times $t$ as an input to the Turing model. This is not strictly necessary: you can also just hardcode the given values of $t$, variable `times`, in the Turing model, but this way you can easily define the model for different values of the input variables. + +You simply instantiate the model with the correct values of $t$ as follows: +""" + +# ╔═╡ 48f6b7dc-13aa-4057-8468-97db047773ba +mutation_model = mutations(times); + +# ╔═╡ d4dbc185-6087-4862-b6e5-b5e4f51c2866 +md"And can then generate random samples of the output as we are used to:" + +# ╔═╡ 3e4998e7-4981-4945-8bf2-ddb5afcb43b1 +chain = sample(mutation_model, Prior(), 2000); + +# ╔═╡ 3421987d-1aab-4cab-bf83-dd3653715bce +α_sp = chain[:α]; # random sample of α + +# ╔═╡ a9d54dfc-5337-412f-86b0-deeb5a0b6928 +histogram(α_sp, title = "Sample of prior of α") + +# ╔═╡ fdec279a-a7e3-4a57-a3f1-183e22558725 +generated_quantities(mutation_model, chain) # random samples of N + +# ╔═╡ 425b4b6c-76b8-4676-8d0a-fd26711400d6 +md"### Inference" + +# ╔═╡ b8adbdd4-2642-4375-9979-0cb8f52c5bc8 +md""" +The model so far has no extra information outside of our prior knowledge. +We can change this by **conditioning** the model on observed data as follows: +""" + +# ╔═╡ 70f9e94d-a4e6-47d6-8d19-b60f7011d572 +conditioned_model = mutation_model | (N = observed_mutations,) + +# ╔═╡ 00c24cbb-88ba-49f2-9bf5-f44538fb2413 +md"Note the syntax: we tell Turing that the value of the random variable `N` defined in our Turing model should be the values given by the variable `observed_mutations`." + +# ╔═╡ 2d0c969d-03a2-4e4c-ace4-e439f81c771b +md""" +!!! danger + Note the `,` at the end of `(N = observed_mutations,)`. This is important, as without it Julia thinks you simply put parentheses around a variable assignment and you'll get an error! Uncomment the below cell for an example. +""" + +# ╔═╡ a35a43e2-e6b0-47ce-80b2-48148336274c +# forgot_comma = mutation_model | (N = observed_mutations) + +# ╔═╡ c5f0dbb3-fba1-41f2-b7d2-740012603555 +md""" +We can verify that for our conditioned model, the value of `N` has been set as constant: +""" + +# ╔═╡ 31d48207-4432-4d8f-a18a-40f01d4a0a6c +# ╠═╡ show_logs = false +conditioned_chain = sample(conditioned_model, Prior(), 5); + +# ╔═╡ d03cef36-3e82-4de4-89e7-af9f772edd8d +generated_quantities(conditioned_model, conditioned_chain) # always returns `observed_mutations` + +# ╔═╡ 371a48d5-daea-4d0b-968b-7e3056a65494 +md""" +What we're after is our updated belief on the distribution of `α` given the observed data. We can do this by using the `sample` function on our model. We no longer use `Prior()` as second input, and instead choose one of the following sampling algorithms: +- `MH`: Metropolis-Hastings sampler +- `Gibbs`: Gibbs sampler +- `PG`: Particle Gibbs sampler +- `HMC`: Hamiltonian Monte Carlo sampler +- `NUTS`: No-U-Turn sampler + +You can find more information about them in the corresponding Julia docs (see the `🔍Live Docs` in the bottom right corner). In practice, `NUTS` is often an excellent choice if all prior distributions are continuous and `PG` with 10-20 particles is a good default choice in all other cases. (`MH` and `Gibbs` also have their uses, but usually it takes more effort to make them work well.) +""" + +# ╔═╡ 0d2c1359-434f-4f3d-8c04-c452c46d7ae8 +mutation_chain = sample(conditioned_model, NUTS(), 2000) + +# ╔═╡ 1c00437c-e2f3-44f6-b020-ca213e321239 +md"It's always a good idea to check whether your sampling process has converged. You can do this by plotting the chain. It should look like a fuzzy caterpillar." + +# ╔═╡ 4c79adff-0640-4e69-815d-ab94ebd9c937 +plot(mutation_chain) # looks appropriately fuzzy! + +# ╔═╡ f620d591-7982-4b67-9524-45cfac27436b +md""" +!!! note + For an example of a non-converged chain, try using the `MH()` sampler instead of `NUTS()`. This sampling algorithm takes a lot of fiddling with its parameters (or a larger number of samples) for it to work well. +""" + +# ╔═╡ 251a1b0e-7efc-4ce2-b0ea-48a5c13d2c63 +md""" +The chain plot also shows the resulting **posterior distribution** of `α`. It is the prior distribution updated with the information contained in the data. +""" + +# ╔═╡ 6ff29c57-aca3-4ebe-a206-e733e81bcc20 +md""" +Taking the sampled values of the mutation rate from the chain and plotting a histogram will show us the exact same distribution as above. The one in the chain plot was simply smoothed to look continuous. +""" + +# ╔═╡ b657217a-6ccc-4a41-b852-df4e39a7a10a +alpha_samples = mutation_chain[:α]; + +# ╔═╡ 9cf08616-d599-42a3-82e8-e8c98853c1d8 +histogram(alpha_samples) # note the difference with the prior distribution! + +# ╔═╡ e02c42dc-627c-4bc0-8ebb-fdb2b0f15b64 +md"Plotting some sampled mutation rates from this distribution onto our data shows that they fit well:" + +# ╔═╡ 87e70d5a-7a45-4a3e-b6c4-a894cc78621b +begin + plot(xlabel = "Time (My)", ylabel = "Number of mutations", xlims = (0, 500), + ylims = (0, 400), title = "A posteriori relationship between t and mean N"); + for α in alpha_samples[1:10:end] + plot!(x -> α*x, color = :dodgerblue, alpha = 0.1, label = false); + end + scatter!(times, observed_mutations, label = "Cyt C", color = :deeppink); + plot!() +end + +# ╔═╡ 87059440-2919-4f6d-9d32-0df3ce75e2a2 +md"And finally we can answer our first question:" + +# ╔═╡ 644cff58-68b9-4d4b-8896-617fcacc39c5 +mean(alpha_samples) + +# ╔═╡ 5f2f5a78-ca90-4baf-8bf0-ff1fae88d785 +sqrt(var(alpha_samples)) + +# ╔═╡ bfbf811f-7b2f-473b-b132-0c7e965c8b0d +md""" + $α$ is ± normally distributed around 0.75 with a standard deviation of 0.025. +""" + +# ╔═╡ d2d83d9a-0ed6-431b-8764-397c1bb019c2 +md"### Seahorses (extra)" + +# ╔═╡ 4e9dc370-6aca-40f7-807b-e85d412ab1a0 +md""" +To answer how old the ancestral seahorse fossil is, we need to update the model a little. +So far the fossil ages were considered to be known exactly and given as input to the model `ts`. Since the seahorse fossil's age is unknown, we add a parameter for it called `fossil_age`. + +As prior knowledge we can use the fact that it must have evolved _after_ the ray-finned fish fossil (30 Ma after the bony fish fossil), but _before_ modern seahorses (450 Ma after the bony fish fossil). +""" + +# ╔═╡ fd15afe1-72d7-4663-b2b6-afa0dd219db8 +@model function horsetations(ts) + α ~ prior_alpha # prior distribution of parameter + + N = zeros(length(ts)) # output variable: in this model we have multiple values, so we need to preallocate a vector + for i in 1:length(ts) + N_average = α * ts[i] + N[i] ~ Poisson(N_average) + end + + fossil_age ~ Uniform(30, 450) + horse_mutations ~ Poisson(α * fossil_age) + + return N +end + +# ╔═╡ 43509549-f926-478b-a4da-995de443b3a7 +md"Then we simply repeat model instantation, conditioning and sampling:" + +# ╔═╡ dbb8ad46-4ac1-443e-b39a-89ddf938ede2 +horse_model = horsetations(times) + +# ╔═╡ 8b380751-0e55-4691-bab0-247fa1b7c510 +horseditioned_model = horse_model | (N = observed_mutations, horse_mutations = 156); + +# ╔═╡ e4eda27b-cff2-4ef9-bbdc-75ce2b19b10b +horse_chain = sample(horseditioned_model, NUTS(), 2000) + +# ╔═╡ da82b71b-fdf5-4fba-8ef3-f2aac67d3494 +md"And we have our posterior distribution of `fossil_age`! It seems like the seahorse ancestor lived about 200-220 million years after the bony fish fossil, or about 240 million years ago." + +# ╔═╡ 7e5a39c7-cb66-4563-8f58-7245f88c9b85 +histogram(horse_chain[:fossil_age]) + +# ╔═╡ 1e152790-a277-4dba-8785-d6a6120cc34f +md"## The essentials" + +# ╔═╡ 0e044dc4-2fc4-4c41-9efa-e586fd138a69 +md"This section again reitarates the essential code for this practical without much explanations." + +# ╔═╡ e9d2f2ca-48ed-4cd8-bd0a-ff0ef640c8fb +let + @model function mutations(ts) + α ~ Exponential(2) # prior distribution of parameter + + N = zeros(length(ts)) # output variable: in this model we have multiple values, so we need to preallocate a vector + for i in 1:length(ts) + N_average = α * ts[i] + N[i] ~ Poisson(N_average) + end + + return N + end + + mutation_model = mutations(times); # instantiate model + conditioned_model = mutation_model | (N = observed_mutations,) + # condition model + # mind the `,` after `observed_mutations`! + mutation_chain = sample(conditioned_model, NUTS(), 2000); + # run inference with an appropriate sampler + α_sp = mutation_chain[:α] # get sample of posterior distribution of α + histogram(α_sp, + title = "Posterior distribution of mutation rate α" + ) +end + +# ╔═╡ Cell order: +# ╟─eabed73e-19dc-4265-965a-cf762d630fb3 +# ╠═06bdc430-b965-11ef-36a4-3d863afbaf6e +# ╠═ce9c8c34-3690-4241-b021-c08868157a55 +# ╠═ea5d107c-f171-401f-8972-6a928ab72907 +# ╟─daed8bc0-8a85-45ce-84ac-0d13ff1923f1 +# ╟─6a79727c-5b5e-43cc-862f-7182c1ea878c +# ╟─83aa4c69-c027-4ade-ac44-438d784b2b78 +# ╟─3ecd3ac7-2621-4ea6-8ff1-69962769d934 +# ╟─71bbd593-f2e4-40dd-b682-40e65305ebb3 +# ╟─138538d4-f6b5-4b8e-af3a-273858cc463c +# ╠═f49c6d3f-5870-4473-b142-799fa84dbfb7 +# ╠═4ea82c1e-490f-4d88-b60c-5c2118409408 +# ╟─26128162-e355-4316-8d74-291fbca194a6 +# ╟─d3fdfa6f-1ee2-48d0-bb6c-483a6fb4faf9 +# ╟─d443d3e8-c1ee-467f-88b2-a0211d5b5cc0 +# ╟─5d83f336-372b-4493-baf6-8efffb663ff1 +# ╟─6666a388-1c45-4f6f-804f-eb3a260eae98 +# ╟─b0dbd481-ce36-4364-b995-b4fa8f36f76d +# ╟─641a1a77-6fcb-4a6c-8f8f-3b171704efa8 +# ╟─442aa57f-79d8-4b22-93b2-466ac92c2b13 +# ╟─12f017e3-b7b8-408d-a677-52dd2ea900eb +# ╟─7900fd13-cc69-41c5-972b-16d5b6d5452a +# ╟─430927e8-7fb8-494c-b9da-d46f000c142f +# ╟─656d3299-e5d0-4799-84ae-da32f51bd92e +# ╟─6ec75944-0b62-43a3-80e9-d6cd2ebd4478 +# ╟─0b5205c2-0eb4-4734-8470-055d29954f63 +# ╠═2034e550-84df-42ee-9f0d-2bf417563dd4 +# ╟─3221f614-ef9a-4e09-a7da-b649fff0ab61 +# ╟─7a26cfec-b362-41c4-9b2e-1d040db8a5eb +# ╟─1080294d-8da9-4326-a53b-afe158dc2ab9 +# ╟─624a0a67-c5c1-437c-89e1-e08e83003b41 +# ╟─97257007-fbfa-4065-8788-869801ce3730 +# ╟─2a652734-bf47-405b-abb3-00e3d453bf47 +# ╟─c52854b2-b2b6-4437-b65b-a1f912d054d5 +# ╠═950c03a9-0056-4760-81f8-dd10d1e55cea +# ╟─31378eb3-51a5-4ad6-a713-7f77c7ceafcc +# ╠═48f6b7dc-13aa-4057-8468-97db047773ba +# ╟─d4dbc185-6087-4862-b6e5-b5e4f51c2866 +# ╠═3e4998e7-4981-4945-8bf2-ddb5afcb43b1 +# ╠═3421987d-1aab-4cab-bf83-dd3653715bce +# ╟─a9d54dfc-5337-412f-86b0-deeb5a0b6928 +# ╠═fdec279a-a7e3-4a57-a3f1-183e22558725 +# ╟─425b4b6c-76b8-4676-8d0a-fd26711400d6 +# ╟─b8adbdd4-2642-4375-9979-0cb8f52c5bc8 +# ╠═70f9e94d-a4e6-47d6-8d19-b60f7011d572 +# ╟─00c24cbb-88ba-49f2-9bf5-f44538fb2413 +# ╟─2d0c969d-03a2-4e4c-ace4-e439f81c771b +# ╠═a35a43e2-e6b0-47ce-80b2-48148336274c +# ╟─c5f0dbb3-fba1-41f2-b7d2-740012603555 +# ╠═31d48207-4432-4d8f-a18a-40f01d4a0a6c +# ╠═d03cef36-3e82-4de4-89e7-af9f772edd8d +# ╟─371a48d5-daea-4d0b-968b-7e3056a65494 +# ╠═0d2c1359-434f-4f3d-8c04-c452c46d7ae8 +# ╟─1c00437c-e2f3-44f6-b020-ca213e321239 +# ╠═4c79adff-0640-4e69-815d-ab94ebd9c937 +# ╟─f620d591-7982-4b67-9524-45cfac27436b +# ╟─251a1b0e-7efc-4ce2-b0ea-48a5c13d2c63 +# ╟─6ff29c57-aca3-4ebe-a206-e733e81bcc20 +# ╠═b657217a-6ccc-4a41-b852-df4e39a7a10a +# ╠═9cf08616-d599-42a3-82e8-e8c98853c1d8 +# ╟─e02c42dc-627c-4bc0-8ebb-fdb2b0f15b64 +# ╟─87e70d5a-7a45-4a3e-b6c4-a894cc78621b +# ╟─87059440-2919-4f6d-9d32-0df3ce75e2a2 +# ╠═644cff58-68b9-4d4b-8896-617fcacc39c5 +# ╠═5f2f5a78-ca90-4baf-8bf0-ff1fae88d785 +# ╟─bfbf811f-7b2f-473b-b132-0c7e965c8b0d +# ╟─d2d83d9a-0ed6-431b-8764-397c1bb019c2 +# ╟─4e9dc370-6aca-40f7-807b-e85d412ab1a0 +# ╠═fd15afe1-72d7-4663-b2b6-afa0dd219db8 +# ╟─43509549-f926-478b-a4da-995de443b3a7 +# ╠═dbb8ad46-4ac1-443e-b39a-89ddf938ede2 +# ╠═8b380751-0e55-4691-bab0-247fa1b7c510 +# ╠═e4eda27b-cff2-4ef9-bbdc-75ce2b19b10b +# ╟─da82b71b-fdf5-4fba-8ef3-f2aac67d3494 +# ╠═7e5a39c7-cb66-4563-8f58-7245f88c9b85 +# ╟─1e152790-a277-4dba-8785-d6a6120cc34f +# ╟─0e044dc4-2fc4-4c41-9efa-e586fd138a69 +# ╠═e9d2f2ca-48ed-4cd8-bd0a-ff0ef640c8fb diff --git a/src/exercises/MCMC_2-basics.jl b/src/exercises/MCMC_2-basics.jl new file mode 100644 index 00000000..74323354 --- /dev/null +++ b/src/exercises/MCMC_2-basics.jl @@ -0,0 +1,573 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "28" +#> title = "5. MCMC basics" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "MCMC basics" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# ╔═╡ 94c6f31d-1a43-4221-b60c-1fa0ef8738b8 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 45bc5b66-c81b-4afb-8a7e-51aff9609c62 +using Turing, StatsPlots + +# ╔═╡ 9daad9dc-b092-4845-a276-ed3a24249924 +using PlutoUI; TableOfContents() + +# ╔═╡ 41dc8060-cf5e-11ef-26f9-892577e77af0 +md"# Inference notebook #2: Basics" + +# ╔═╡ 299ba93b-0fc0-4bb3-9a2c-a571ce571f1b +md"## 1: Mole burrow" + +# ╔═╡ 0d068aa6-f7c9-4a75-8382-cfbc903efc5b +md""" +Consider a mole's underground tunnel network of length `X` (in m). Now and then the mole makes a new molehill somewhere randomly above its tunnel, the locations of which we denote `Y`. +""" + +# ╔═╡ 5d4e269d-bd5a-422e-9547-099e04b8eedf +md""" + Y1 Y2 Y3 + /\ /\ /\ <- molehills + ================================================================= <- ground + | | | + ------------------------------------------------ <- tunnel + 0 X +""" + +# ╔═╡ 7abeb4d3-4648-4d8b-9383-de5213c8cc41 +md""" +We're no mole experts, but for our prior information we can suppose a mole would not make a tunnel much longer than a few 100 m (probably a lot shorter). + +We can formulate this as `X ~ Exponential(100)` and `Y ~ Uniform(0, X)`. + +!!! questions + 1. Plot the prior of `X`. Is it diffuse or informative? + 1. Estimate `E[Y]`. + 1. Estimate `E[X|Y = 3]` and compare it with the prior expected value `E[X]`. + 1. Plot the histogram of `X` given `Y = 3.0`. + 1. You come back a day later and find even more molehills! You now measure the following values for `Y`: `[3.0, 1.5, 0.9, 5.7]`. How long do you estimate the tunnel given this extra information? +""" + +# ╔═╡ 47aa2304-d312-40e9-b9c6-7c79a7d64de4 +md"### 1: Prior plot" + +# ╔═╡ 78c9ce62-e375-48ac-8083-55ee085c61de +X_prior = missing + +# ╔═╡ c64b346a-059d-4bbe-b166-80116260e19b +missing # plot above + +# ╔═╡ 78eb7779-f182-4419-b5d8-79a2f5c5d6da +md"### 2: Unconditional expected value of Y" + +# ╔═╡ 3decb2ec-210a-4b2f-842d-6fd40dd3f77b +@model function mole() + X ~ missing + Y ~ missing + return Y +end + +# ╔═╡ 76dd814d-0d9b-4f7e-aff8-990da57d052b +molemodel = mole() + +# ╔═╡ 61b8ef63-a613-4dcb-8f7a-936e0d59b862 +Y_samples = missing + +# ╔═╡ 4e8b9000-cb61-4f01-9ba9-17276ad0335e +E_Y = missing + +# ╔═╡ 8777b133-7d7c-4a85-b89c-2f00093e9984 +md"### 3: Conditional expected value of X" + +# ╔═╡ 9695ee7e-359b-489c-962b-1bf84b052371 +cond_mole = missing + +# ╔═╡ 014538da-b5ef-41c8-b799-2c000b4c9134 +molechain = missing + +# ╔═╡ 977d0406-f154-4b87-b9c6-cafe4809a4a6 +missing # plot molechain + +# ╔═╡ af4811ce-c6e7-458a-8f89-0562355b3eb0 +X_samplescondY = missing + +# ╔═╡ fd540765-95e5-4071-81f7-e689b06cad0c +E_XcondY = missing + +# ╔═╡ 5521933a-a42e-4a67-94b9-84eab52ddf07 +E_X = missing + +# ╔═╡ 9403c355-dce8-4aa9-80a3-824ea6193905 +md"### 4: Conditional distribution of X" + +# ╔═╡ bedd99ca-4285-4c3d-87e2-c22d414f8f07 +# histogram + +# ╔═╡ 21743f62-c7f5-4171-8277-0667edb71c56 +md"### 5: Conditional distribution of X (with more data)" + +# ╔═╡ b2b16c34-e12a-4bea-8098-313d01913bbf +@model function mole2() + X ~ missing + # how to deal with a vector of values again... +end + +# ╔═╡ 26a245d2-c5b6-484d-bdf7-541948ff06ad +Y_obs = [3.0, 1.5, 0.9, 5.7] + +# ╔═╡ dc6aadff-7cff-4a9a-a967-32cd322b2696 +missing # (some lines of code) + +# ╔═╡ ea2235f0-0cfc-4603-b027-d0a1bc6fa2c1 +# I estimate the tunnel to be about (MISSING) long + +# ╔═╡ 951d0913-1a52-4d5b-b5bb-168487e50ab2 +md"## 2: Potatoes" + +# ╔═╡ 49035a16-c419-4531-b157-a5ab357b44fe +md""" +Consider a number of potatoes `N` each with an average weight `W`. You weigh them together on an old balance to get an estimate of their total weight `T`. + +Suppose the following priors: `N ~ Poisson(10)`, `W ~ Uniform(150, 250)` and the following relationship between expected - and actual outcome: `T ~ Normal(N*W, 50)`. + +!!! questions + 1. Plot a histogram of `N` given `T = 1200`. + 1. Estimate `P(N > 6, W > 175 | T = 1200)`. + 1. Estimate `P(N = 5 | T = 1200, W = 220)`. +""" + +# ╔═╡ 5f8322ca-0ded-4750-8ad6-e66e8280daca +md"### 1: Conditional distribution of N" + +# ╔═╡ ec479be9-0d8a-4c8d-97c9-6f64d861924c +@model function potatoes() + N ~ missing + W ~ missing + T ~ missing +end + +# ╔═╡ 438893d1-3107-493c-9299-d8813658a698 +potato_model = missing + +# ╔═╡ dd7d84b0-1f20-4478-96ba-b3f4e9df1d2c +potato_cond = missing + +# ╔═╡ accca8d7-8822-49ae-8588-7259da82e37a +potato_chain = missing + # be careful with your choice of sampling algorithm! are all priors continuous? + +# ╔═╡ 519a952b-f040-4588-8acd-1cfaab88c4da +# plot chain + +# ╔═╡ 57adc714-cdd1-46cf-a4ea-d0fb04924e1e +# histogram of N + +# ╔═╡ 0fddec58-67d9-4dec-a72d-445675fa46a6 +md"### 2: Probability" + +# ╔═╡ b77d8796-9065-4c61-be21-3dc292b94492 +p_potato1 = missing + +# ╔═╡ 5bde987a-3f5c-4ff7-96a0-9e174f73fdfb +md"### 3: Probability (with more data)" + +# ╔═╡ abffd02e-b917-4e9e-a94d-de197e1b0fc3 +potato_cond2 = missing # alternative conditioned model now also given `W = 220` + +# ╔═╡ 5b30d91a-7039-4d2a-bcf7-3a990622fd2a +pota2_chain = missing + +# ╔═╡ 27939d84-d6e1-47b2-a939-5038c6bdcb48 +# plot chain + +# ╔═╡ 744cf31f-89c0-44e8-a6fb-0f4dcea04e5d +p_potato2 = missing + +# ╔═╡ ed7c5547-bd82-4ca7-bf51-dd1c201d88af +md"## 3: Lights out" + +# ╔═╡ a3c26839-2acc-49d8-98c4-3a7142dd6512 +md""" +You use 4 of the same LED light in your room. Define: +- $μ$ the **average** lifespan of your LED lights (in khr or 1000 hours) +- $Lᵢ$ the lifespan of the `i`-th LED light. + +Assume that `μ ~ LogNormal(log(40), 0.5)`. +""" + +# ╔═╡ dac55632-6e17-479e-8090-5cf8eaa67dad +md""" +!!! questions + 1. What is `E[μ]` (given no information about `Lᵢ`)? + 1. What is a sensible distribution for `Lᵢ`? (requires no code) + 1. What is `E[μ | L = [16, 20, 23, 41]]`? + 1. 🌟🌟🌟 (EXTRA DIFFICULT BONUS QUESTION): After 30 khr, two lights have died: one at 16 khr and one at 20 khr. The two other lights are still working. What is the expected value of `μ` given this information? +""" + +# ╔═╡ 7df97000-5cf0-4a29-b3c3-f867403f4318 +md"### 1: Unconditional expected value" + +# ╔═╡ 0fe475dd-411e-474e-9524-ef9fcceed7af +lights_prior = missing + +# ╔═╡ db77b90b-33b5-4d70-8229-b4c7d74fa96c +E_mu = missing + +# ╔═╡ 308cb1f5-e26b-43b2-be4d-9eabd68b3670 +md"### 2: Choice of distribution" + +# ╔═╡ 738b4097-56e1-4921-9195-2d535c78ae73 +md""" +!!! note + See theory p. 96 for an overview of elementary distributions. +""" + +# ╔═╡ d797cb9b-430b-4d1d-8ded-21959a7cb3a9 +# A sensible distribution for the lifespan of a LED-light is (missing) + +# ╔═╡ cd37e5fc-ae9a-429d-844d-4b450b187b5e +md"### 3: Conditional expected value" + +# ╔═╡ 126d3954-c77d-4c98-abe0-fd87d14e6265 +@model function lights() + missing +end + +# ╔═╡ 37bf6ca5-e42b-406e-94d6-bcd1e706e2bd +L_obs = [16, 20, 23, 41] + +# ╔═╡ c49955c7-7082-4261-b4db-0056b5c637f1 +E_mu_cond = missing + +# ╔═╡ 9a168680-9d96-466f-ba75-122d6a391501 +md"### 4 🌟🌟🌟: Working with censored data" + +# ╔═╡ ab6331a3-676e-45e6-b5be-e9c4154ea071 +md""" +!!! note + This question is way above the exam's difficulty level. Don't feel bad if you can't find the answer right away! +""" + +# ╔═╡ 1f35d962-a249-4be7-9a96-17eb83fca7d8 +md""" +!!! hint + You can model the number of lights that still work as a `Binomial` distribution, the success rate of which depends on `μ`. +""" + +# ╔═╡ 8fc58fa4-b005-4f32-9eae-a8143582a1ae +@model function lights_censored(time_observed) + missing +end + +# ╔═╡ 410521d8-f767-4c4e-b19b-25cf31ec0f36 +E_mu_cond🌟 = missing + +# ╔═╡ 9dc0456b-7fd2-4120-8f9e-3de1984ff516 +md"## 4: Fish" + +# ╔═╡ 225cd579-1e0d-4680-8d3f-5a737a656eb8 +md""" +There are two populations of fish living in the same pond. Let `fs1` be the fraction of fish belonging to species 1, `L1` the length of a fish of species 1 and `L2` the length of a fish of species 2. + +Assume: +- You have no prior information about `fs1` except that it logically needs to be in `[0, 1]`. +- `L1 ~ Normal(90, 15)`. +- `L2 ~ Normal(60, 10)`. +""" + +# ╔═╡ 3ffac5ca-3635-4aa1-bab7-7c28e7a801cb +md""" +!!! questions + 1. If `fs1 = 0.3`, what is the prior distribution of the lengths of **all** fish in the pond? Make a plot. + 1. Estimate `fs1` if you observe fish of the following lengths: + `[94.0, 88.7, 89.6, 69.8, 52.8, 84.0, 89.3, 66.4, 95.1, 81.6]`. + 1. 🌟(BONUS QUESTION): What is the chance fish 4 belongs to species 1? +""" + +# ╔═╡ b477b212-83a2-42f0-a616-52516e152d48 +md"### 1: Prior distribution given `fs1`" + +# ╔═╡ 23056f1e-128e-463e-a80f-56299397022e +md""" +!!! hint + The distribution of fish lengths can be modelled as a `MixtureModel`. +""" + +# ╔═╡ 3cd0c888-9f11-47f1-a293-b96aa80ea3b0 +lengthdist = missing + +# ╔═╡ e23756e0-7688-4e3b-ba8d-a827e621e862 +missing # plot + +# ╔═╡ 8e030a06-5104-4c5b-b1f2-f86464e66502 +md"### 2: Conditional expected value" + +# ╔═╡ 6ebf3a16-0e6a-491f-8280-b4327ed52cf0 +len_obs = [94.0, 88.7, 89.6, 69.8, 52.8, 84.0, 89.3, 66.4, 95.1, 81.6] + +# ╔═╡ b15de91a-fc48-4cdc-a35f-6453a9a59982 +@model function fishmixture() + missing +end + +# ╔═╡ 819ed364-4bc9-43ec-aa50-b965c8f1c826 +fs1_est = missing + +# ╔═╡ 952a941a-8703-45a3-aac1-a290a181e8c5 +md"### 3🌟: Conditional expected value (spicy)" + +# ╔═╡ 8629d049-b9fd-4e9e-9b55-401a3069e956 +@model function fishmixture🌟() + missing +end + +# ╔═╡ 483cbe4d-64d3-4b16-b6fc-e97b21f174a6 +p_fish4_is_species1 = missing + +# ╔═╡ 30088664-5157-4d99-8584-7a42d0acdfb8 +md"## 5: Circleference" + +# ╔═╡ 7dd2c189-79c0-4d29-9e17-9c24a78b5791 +md""" +Given three (noisy) points $P_1=(x_1,y_1)$, $P_1=(x_2,y_2)$ and $P_3=(x_3,y_3)$, you want to infer the corresponding circle. + +You can assume that the circle center can appear anywhere in the $[-20, 20]\times [-20, 20]$ square and the radius is between 0 and 50. Points are sampled randomly on the circle and have a slight amount of Gaussian noise ($\sigma=0.25$ works well). +""" + +# ╔═╡ dcbf405c-786c-4226-b35c-dc718452bb61 +md""" +!!! questions + 1. Write a small probabilistic program that can infer the center and radius of the circle. + 1. What does the inferred circle look like if you condition on only one or two of the circle points? +""" + +# ╔═╡ 791ff4ed-c9d1-48e2-9dd0-4bf3979c6167 +x1, y1 = 18.0, 2.1 + +# ╔═╡ cb189957-f9d4-480b-a492-92cfc2a8c2aa +x2, y2 = -7.3, 8.1 + +# ╔═╡ 81eca3b3-12d9-43d7-af14-e9aeb73f2471 +x3, y3 = -13.0, -23.0 + +# ╔═╡ 28d28034-e999-4e34-b6b2-63c762094c59 +begin + + function plotcircle!(p, R, xC, yC; dθ=0.01) + θ = 0:dθ:2pi+0.1 + plot!(p, xC .+ R .* cos.(θ), yC .+ R .* sin.(θ), label="", alpha=0.5, color=:blue) + return p + end + + function plotsample(R=missing, xC=missing, yC=missing; kwargs...) + p = plot(xlab="x", ylab="y", aspect_ratio=:equal; + xlims=[-40, 40], ylims=[-40, 40], kwargs...) + + scatter!([x1], [y1], label="P1") + scatter!([x2], [y2], label="P2") + scatter!([x3], [y3], label="P3") + ismissing(R) || plotcircle!(p, R, xC, yC; dθ=0.1) + return p + end + + function plotsample!(p, R=missing, xC=missing, yC=missing) + scatter!([x1], [y1], label=false) + scatter!([x2], [y2], label=false) + scatter!([x3], [y3], label=false) + ismissing(R) || plotcircle!(p, R, xC, yC; dθ=0.1) + end + +end + +# ╔═╡ 4e96908a-4fc9-429d-bf37-7a569194a038 +scatter([x1, x2, x3], [y1, y2, y3]) + +# ╔═╡ 7eedb74d-eee1-4cf0-b2bf-5febf474edd2 +md"### 1: All points" + +# ╔═╡ 84d27c98-9513-4ae3-8101-621c083a1b01 +@model function circle(σ=0.25) + # generate a circle center + missing + + # generate a radius + missing + + # three random points in polar coordinates + + # P1 + missing + # P2 + missing + # P3 + missing +end + +# ╔═╡ 543c40d5-e8a7-492d-a0b8-e7e73e5953e2 +circlemodel = circle() | (x1=x1, y1=y1, x2=x2, y2=y2, x3=x3, y3=y3); + +# ╔═╡ 25ea29e7-b391-4bd1-bdbc-1957adb8c993 +circlechain = missing + +# ╔═╡ 0d04b0e8-3d1a-4281-b175-570148569ef2 +begin + p = plot( + xlab="x", ylab="y", aspect_ratio=:equal, xlims=[-40, 40], ylims=[-40, 40], + title="Samples of P(circle|P1,P2,P3)" + ) + for i in 1:100 + plotsample!(p, circlechain[:R][i], circlechain[:xC][i], circlechain[:yC][i]) + end + p +end + +# ╔═╡ 0a140d2a-a24b-48df-9af8-7fa5d586a26f +md"### 2: Some points" + +# ╔═╡ b7649523-fd40-4fe3-8d86-fc2cb2c8c488 +circle1 = missing # given one point + +# ╔═╡ b7ba8f91-f440-4166-af9e-11fcb5f1755f +circle2 = missing # given two points + +# ╔═╡ 3b5f3623-3d00-4ba4-9182-5bddacc56567 +chain1 = missing + +# ╔═╡ c11452bc-f404-4e51-8f68-292f5b538c88 +chain2 = missing + +# ╔═╡ c1174a5c-a6a1-46a0-96be-6997e3201dfc +begin + p1 = plot( + xlab="x", ylab="y", aspect_ratio=:equal, + xlims=[-40, 40], ylims=[-40, 40], title="Samples of P(circle|P1)" + ) + for i in 1:100 + plotsample!(p1, chain1[:R][i], chain1[:xC][i], chain1[:yC][i]) + end + p1 +end + +# ╔═╡ 793fc102-fc64-4041-a04e-e0b1b0741437 +begin + p2 = plot( + xlab="x", ylab="y", aspect_ratio=:equal, + xlims=[-40, 40], ylims=[-40, 40], title="Samples of P(circle|P1,P2)" + ) + for i in 1:100 + plotsample!(p2, chain2[:R][i], chain2[:xC][i], chain2[:yC][i]) + end + p2 +end + +# ╔═╡ Cell order: +# ╟─41dc8060-cf5e-11ef-26f9-892577e77af0 +# ╠═94c6f31d-1a43-4221-b60c-1fa0ef8738b8 +# ╠═45bc5b66-c81b-4afb-8a7e-51aff9609c62 +# ╠═9daad9dc-b092-4845-a276-ed3a24249924 +# ╟─299ba93b-0fc0-4bb3-9a2c-a571ce571f1b +# ╟─0d068aa6-f7c9-4a75-8382-cfbc903efc5b +# ╟─5d4e269d-bd5a-422e-9547-099e04b8eedf +# ╟─7abeb4d3-4648-4d8b-9383-de5213c8cc41 +# ╟─47aa2304-d312-40e9-b9c6-7c79a7d64de4 +# ╠═78c9ce62-e375-48ac-8083-55ee085c61de +# ╠═c64b346a-059d-4bbe-b166-80116260e19b +# ╟─78eb7779-f182-4419-b5d8-79a2f5c5d6da +# ╠═3decb2ec-210a-4b2f-842d-6fd40dd3f77b +# ╠═76dd814d-0d9b-4f7e-aff8-990da57d052b +# ╠═61b8ef63-a613-4dcb-8f7a-936e0d59b862 +# ╠═4e8b9000-cb61-4f01-9ba9-17276ad0335e +# ╟─8777b133-7d7c-4a85-b89c-2f00093e9984 +# ╠═9695ee7e-359b-489c-962b-1bf84b052371 +# ╠═014538da-b5ef-41c8-b799-2c000b4c9134 +# ╠═977d0406-f154-4b87-b9c6-cafe4809a4a6 +# ╠═af4811ce-c6e7-458a-8f89-0562355b3eb0 +# ╠═fd540765-95e5-4071-81f7-e689b06cad0c +# ╠═5521933a-a42e-4a67-94b9-84eab52ddf07 +# ╟─9403c355-dce8-4aa9-80a3-824ea6193905 +# ╠═bedd99ca-4285-4c3d-87e2-c22d414f8f07 +# ╟─21743f62-c7f5-4171-8277-0667edb71c56 +# ╠═b2b16c34-e12a-4bea-8098-313d01913bbf +# ╠═26a245d2-c5b6-484d-bdf7-541948ff06ad +# ╠═dc6aadff-7cff-4a9a-a967-32cd322b2696 +# ╠═ea2235f0-0cfc-4603-b027-d0a1bc6fa2c1 +# ╟─951d0913-1a52-4d5b-b5bb-168487e50ab2 +# ╟─49035a16-c419-4531-b157-a5ab357b44fe +# ╟─5f8322ca-0ded-4750-8ad6-e66e8280daca +# ╠═ec479be9-0d8a-4c8d-97c9-6f64d861924c +# ╠═438893d1-3107-493c-9299-d8813658a698 +# ╠═dd7d84b0-1f20-4478-96ba-b3f4e9df1d2c +# ╠═accca8d7-8822-49ae-8588-7259da82e37a +# ╠═519a952b-f040-4588-8acd-1cfaab88c4da +# ╠═57adc714-cdd1-46cf-a4ea-d0fb04924e1e +# ╟─0fddec58-67d9-4dec-a72d-445675fa46a6 +# ╠═b77d8796-9065-4c61-be21-3dc292b94492 +# ╟─5bde987a-3f5c-4ff7-96a0-9e174f73fdfb +# ╠═abffd02e-b917-4e9e-a94d-de197e1b0fc3 +# ╠═5b30d91a-7039-4d2a-bcf7-3a990622fd2a +# ╠═27939d84-d6e1-47b2-a939-5038c6bdcb48 +# ╠═744cf31f-89c0-44e8-a6fb-0f4dcea04e5d +# ╟─ed7c5547-bd82-4ca7-bf51-dd1c201d88af +# ╟─a3c26839-2acc-49d8-98c4-3a7142dd6512 +# ╟─dac55632-6e17-479e-8090-5cf8eaa67dad +# ╟─7df97000-5cf0-4a29-b3c3-f867403f4318 +# ╠═0fe475dd-411e-474e-9524-ef9fcceed7af +# ╠═db77b90b-33b5-4d70-8229-b4c7d74fa96c +# ╟─308cb1f5-e26b-43b2-be4d-9eabd68b3670 +# ╟─738b4097-56e1-4921-9195-2d535c78ae73 +# ╠═d797cb9b-430b-4d1d-8ded-21959a7cb3a9 +# ╟─cd37e5fc-ae9a-429d-844d-4b450b187b5e +# ╠═126d3954-c77d-4c98-abe0-fd87d14e6265 +# ╠═37bf6ca5-e42b-406e-94d6-bcd1e706e2bd +# ╠═c49955c7-7082-4261-b4db-0056b5c637f1 +# ╟─9a168680-9d96-466f-ba75-122d6a391501 +# ╟─ab6331a3-676e-45e6-b5be-e9c4154ea071 +# ╟─1f35d962-a249-4be7-9a96-17eb83fca7d8 +# ╠═8fc58fa4-b005-4f32-9eae-a8143582a1ae +# ╠═410521d8-f767-4c4e-b19b-25cf31ec0f36 +# ╟─9dc0456b-7fd2-4120-8f9e-3de1984ff516 +# ╟─225cd579-1e0d-4680-8d3f-5a737a656eb8 +# ╟─3ffac5ca-3635-4aa1-bab7-7c28e7a801cb +# ╟─b477b212-83a2-42f0-a616-52516e152d48 +# ╟─23056f1e-128e-463e-a80f-56299397022e +# ╠═3cd0c888-9f11-47f1-a293-b96aa80ea3b0 +# ╠═e23756e0-7688-4e3b-ba8d-a827e621e862 +# ╟─8e030a06-5104-4c5b-b1f2-f86464e66502 +# ╠═6ebf3a16-0e6a-491f-8280-b4327ed52cf0 +# ╠═b15de91a-fc48-4cdc-a35f-6453a9a59982 +# ╠═819ed364-4bc9-43ec-aa50-b965c8f1c826 +# ╟─952a941a-8703-45a3-aac1-a290a181e8c5 +# ╠═8629d049-b9fd-4e9e-9b55-401a3069e956 +# ╠═483cbe4d-64d3-4b16-b6fc-e97b21f174a6 +# ╟─30088664-5157-4d99-8584-7a42d0acdfb8 +# ╟─7dd2c189-79c0-4d29-9e17-9c24a78b5791 +# ╟─dcbf405c-786c-4226-b35c-dc718452bb61 +# ╟─28d28034-e999-4e34-b6b2-63c762094c59 +# ╠═791ff4ed-c9d1-48e2-9dd0-4bf3979c6167 +# ╠═cb189957-f9d4-480b-a492-92cfc2a8c2aa +# ╠═81eca3b3-12d9-43d7-af14-e9aeb73f2471 +# ╠═4e96908a-4fc9-429d-bf37-7a569194a038 +# ╟─7eedb74d-eee1-4cf0-b2bf-5febf474edd2 +# ╠═84d27c98-9513-4ae3-8101-621c083a1b01 +# ╠═543c40d5-e8a7-492d-a0b8-e7e73e5953e2 +# ╠═25ea29e7-b391-4bd1-bdbc-1957adb8c993 +# ╠═0d04b0e8-3d1a-4281-b175-570148569ef2 +# ╟─0a140d2a-a24b-48df-9af8-7fa5d586a26f +# ╠═b7649523-fd40-4fe3-8d86-fc2cb2c8c488 +# ╠═b7ba8f91-f440-4166-af9e-11fcb5f1755f +# ╠═3b5f3623-3d00-4ba4-9182-5bddacc56567 +# ╠═c11452bc-f404-4e51-8f68-292f5b538c88 +# ╠═c1174a5c-a6a1-46a0-96be-6997e3201dfc +# ╠═793fc102-fc64-4041-a04e-e0b1b0741437 diff --git a/src/exercises/MCMC_3-advanced.jl b/src/exercises/MCMC_3-advanced.jl new file mode 100644 index 00000000..7cad13c6 --- /dev/null +++ b/src/exercises/MCMC_3-advanced.jl @@ -0,0 +1,211 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "29" +#> title = "5. MCMC advanced" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "MCMC advanced" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 75581580-2fb2-4112-b397-2b775eb64630 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ e07a1ae5-43b7-4c12-831d-43e1738eeac0 +using Turing, StatsPlots + +# ╔═╡ d12cdd62-f90a-4ba8-8610-5e86e922e881 +using PlutoUI + +# ╔═╡ f84d9259-69c0-4165-8bc0-d924fef18182 +md"# Inference notebook #3: Advanced" + +# ╔═╡ aa2b6263-7d13-4683-bc92-25663ed02604 +md"## GPS" + +# ╔═╡ f3510387-1ab6-4aa2-bed9-9c8297a8b3c5 +md""" +GPS systems need to decide what road a car is following based on noisy positional data. We consider here a simplified example. + +At some known timepoints `ts`, we get noisy observations on the car's vertical position `ys_obs` (imagine it as a lattitude of sorts). There are two parallel roads (lines) the car can actually be on, which both have a constant vertical position. If the car is on road 1, then `y = 0`. If it is on road 2, then `y = 1`. The problem is visualized below. +""" + +# ╔═╡ 90f185f3-91d4-4ee7-8de3-b76251c0c169 +ts = 1:10; + +# ╔═╡ 599ae818-fb28-4803-b720-9fcf2bb162b7 +ys_obs = [0.6, 0.0, 0.8, -0.7, -0.5, 0.2, 1.0, 1.2, 1.8, 1.1]; + +# ╔═╡ b2da9dc3-006c-49d0-81a7-6375a2dc6872 +begin + p_cardata = scatter(ts, ys_obs, label = "Observed car positions", + xlabel = "Time", ylabel = "Vertical position" + ) + hline!([0.0], color = :orange, label = "Road 1", linewidth = 2) + hline!([1.0], color = :blue, label = "Road 2", linewidth = 2) +end + +# ╔═╡ 46d3214c-e724-4b98-bc1f-5b9913d2b14a +md""" +At some point `t_switch` ∈ [0, 10], the car switches from lane 1 to lane 2. We can describe the model as follows: +- If `t <= t_switch`, then `y ~ Normal(0.0, σ)`, +- If `t > t_switch`, then `y ~ Normal(1.0, σ)`, +with `σ` a noise parameter, of which you only know that it's probably small. +""" + +# ╔═╡ 0bdac14c-db71-47b5-95d5-d83fb1e68cab +md""" +Below is a plot showing the car's trajectory for some value of `t_switch`. You can adjust the slider to change this guess value. +""" + +# ╔═╡ 9e3db8b6-5466-48b6-9ca9-0d24515b9de3 +@bind switchtime Slider(0:0.1:10, default = 5.0, show_value = true) + +# ╔═╡ cc0d6b32-6f12-46b8-915f-8a471317c35e +begin + plot!( + deepcopy(p_cardata), [0.0, switchtime, switchtime, 10.0], + [0.0, 0.0, 1.0, 1.0], label = "Car trajectory", color = :black, + linewidth = 2, xticks = ([0, 10, switchtime], ["0", "10", "t_switch"]) + ) +end + +# ╔═╡ 96fc1412-83fc-4f67-8995-065b163d739c +md""" +!!! question + Infer the posterior probability of `t_switch` given the data. +""" + +# ╔═╡ 70fd793a-3ce3-446f-adc1-65ce0a68e48a +@model function cars(ts) + missing +end + +# ╔═╡ fb202b86-c058-4f03-9062-ab282c71d5c4 +missing # histogram of `t_switch` + +# ╔═╡ 34bf815a-3bd3-49b5-b06d-d4297ca213a8 +md"## Petridish peril (inference edition)" + +# ╔═╡ 78571fb5-c44f-4e7f-afde-4436db6c945b +md""" +We continue with the "petridish peril" question from the previous practical. + +You've made a model to predict bacterial population levels at certain timepoints based on your knowledge of how the species in question grows. You'd now like to update the model with information about the specific strain you're using, so you inoculate a petri dish and count the number of bacteria after a short incubation period. + +Incorporate the following information into the model to make it more accurate: +- The population level after 5 hours of incubating was 21000. +- You expect the number of bacteria you count to be Poisson distributed around the actual number. +""" + +# ╔═╡ 2a84472c-cb6f-4607-9b98-c88cc2744e3d +md""" +!!! questions + 1. Now taking into account the measurement, what are the chances of your petridish being in a splittable state after 8 hours? + 1. Visualise the updated growth curves. + 1. 🌟(BONUS): The prior for P0 being discrete doesn't allow for the use of a continuous sampler. Change the prior with a sufficiently similar continuous one to fix this. How does this affect the results? +""" + +# ╔═╡ 34120240-6fac-42b8-b5c8-bc3271927248 +md""" +!!! tip + Just like in the previous version of the question, `return`ing the estimated logistic function can be useful. +""" + +# ╔═╡ 4b94e5a6-1ed6-4095-ab18-06b76d4fec99 +logistic(t, P0, r, K) = K / (1 + (K - P0)/P0 * exp(-r*t)) + +# ╔═╡ 234b2c87-fe91-4be4-bf0c-a6f20dcc38fe +md"### 1" + +# ╔═╡ f080f708-a457-40a3-936c-b82d5159975d +dropletdist = missing + +# ╔═╡ 7b4aef69-10ec-4935-b7fd-4c1d49aa9b3d +@model function petrigrowth() + missing +end + +# ╔═╡ 3e98c640-4bb0-4b5d-bae0-769133a599a7 +prob_splittable = missing + +# ╔═╡ 0ea95b67-d4da-4c5a-ad2e-05024ad074a3 +md"### 2" + +# ╔═╡ 47b15322-0b8c-48f1-b123-271ebae92655 +missing # plot + +# ╔═╡ 9ab88be4-4cf8-4747-ac36-3f1b82899be0 +md"### 3🌟" + +# ╔═╡ 113d8311-7bdc-461c-b077-920e23b33d39 +dropletdist🌟 = missing + +# ╔═╡ 4e730df9-f619-464a-b8a3-57448132404b +begin + plot(dropletdist, label = ["Original prior" ""], color = :orange) + plot!(dropletdist🌟, label = ["Continuous alternative" ""], color = :blue) + # With mixture models it takes some fiddling to make the labels look nice - don't worry about this, it's not important for the course +end + +# ╔═╡ d9b50958-20ae-4085-80d6-19420c7d89df +@model function petrigrowth🌟() + missing +end + +# ╔═╡ 0704ed4d-5b3e-401d-a496-98782cb20b09 +prob_splittable🌟 = missing + +# ╔═╡ Cell order: +# ╟─f84d9259-69c0-4165-8bc0-d924fef18182 +# ╠═75581580-2fb2-4112-b397-2b775eb64630 +# ╠═e07a1ae5-43b7-4c12-831d-43e1738eeac0 +# ╠═d12cdd62-f90a-4ba8-8610-5e86e922e881 +# ╟─aa2b6263-7d13-4683-bc92-25663ed02604 +# ╟─f3510387-1ab6-4aa2-bed9-9c8297a8b3c5 +# ╠═90f185f3-91d4-4ee7-8de3-b76251c0c169 +# ╠═599ae818-fb28-4803-b720-9fcf2bb162b7 +# ╟─b2da9dc3-006c-49d0-81a7-6375a2dc6872 +# ╟─46d3214c-e724-4b98-bc1f-5b9913d2b14a +# ╟─0bdac14c-db71-47b5-95d5-d83fb1e68cab +# ╟─9e3db8b6-5466-48b6-9ca9-0d24515b9de3 +# ╟─cc0d6b32-6f12-46b8-915f-8a471317c35e +# ╟─96fc1412-83fc-4f67-8995-065b163d739c +# ╠═70fd793a-3ce3-446f-adc1-65ce0a68e48a +# ╠═fb202b86-c058-4f03-9062-ab282c71d5c4 +# ╟─34bf815a-3bd3-49b5-b06d-d4297ca213a8 +# ╟─78571fb5-c44f-4e7f-afde-4436db6c945b +# ╟─2a84472c-cb6f-4607-9b98-c88cc2744e3d +# ╟─34120240-6fac-42b8-b5c8-bc3271927248 +# ╠═4b94e5a6-1ed6-4095-ab18-06b76d4fec99 +# ╟─234b2c87-fe91-4be4-bf0c-a6f20dcc38fe +# ╠═f080f708-a457-40a3-936c-b82d5159975d +# ╠═7b4aef69-10ec-4935-b7fd-4c1d49aa9b3d +# ╠═3e98c640-4bb0-4b5d-bae0-769133a599a7 +# ╟─0ea95b67-d4da-4c5a-ad2e-05024ad074a3 +# ╠═47b15322-0b8c-48f1-b123-271ebae92655 +# ╟─9ab88be4-4cf8-4747-ac36-3f1b82899be0 +# ╠═113d8311-7bdc-461c-b077-920e23b33d39 +# ╠═4e730df9-f619-464a-b8a3-57448132404b +# ╠═d9b50958-20ae-4085-80d6-19420c7d89df +# ╠═0704ed4d-5b3e-401d-a496-98782cb20b09 diff --git a/src/exercises/MCMC_4-review.jl b/src/exercises/MCMC_4-review.jl new file mode 100644 index 00000000..9d8c0049 --- /dev/null +++ b/src/exercises/MCMC_4-review.jl @@ -0,0 +1,103 @@ +### A Pluto.jl notebook ### +# v0.20.4 + +#> [frontmatter] +#> order = "30" +#> title = "5. MCMC review" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "MCMC review" +#> +#> [[frontmatter.author]] +#> name = "Bram Spanoghe" + +using Markdown +using InteractiveUtils + +# ╔═╡ ef127ffc-1e2e-4c30-945b-d6cded4d6515 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ deb237f9-f0ff-4dfc-8627-43053ccdeb20 +using Turing, StatsPlots + +# ╔═╡ 2d709e7b-3350-47ec-a3ed-8aa47ad5a8c2 +function generate_data(n_wasps = 10; minbound = 0, maxbound = 1000) + + x_n, y_n = rand(DiscreteUniform(minbound, maxbound), 2) + xs, ys = [rand(DiscreteUniform(minbound, maxbound), n_wasps) for _ in 1:2] + v_wasps = rand(Uniform(5, 10), n_wasps) + ts = [2*sqrt((x-x_n)^2 + (y-y_n)^2)/v_wasp for (x, y, v_wasp) in zip(xs, ys, v_wasps)] + + return xs, ys, ts, [x_n, y_n] +end; + +# ╔═╡ af96af94-d969-4e9a-93f4-e205f8b7f576 +md"# Review exercise: Hornet nests" + +# ╔═╡ 07f05baa-1336-4e1a-9cbc-5f4506e5b34a +md""" +In recent years, the Asian giant hornet (_Vespa mandarinia_) has become an invasive species in a number of countries, including Belgium. Since they become aggressive when people get close to their nests, the nests often need to be removed when they appear in residential areas. Finding the nests, however, can be a difficult task: the hornets can go hunting over a kilometer from their nest. +""" + +# ╔═╡ 90aee617-84d5-4915-afb4-e7d422cfb4b7 +md""" +One method for finding the nest is to set up a feeder station, mark any hornets gathering food, and record how long it takes for them to fly back to their nest with it and return for more. Making an estimate of their flight speed, the return time can be used to infer the distance of that location to the nest. Repeated measurements in other locations gives enough information for a triangulation of sorts. +""" + +# ╔═╡ 082a4cc0-b151-4f8b-87da-278484218e6e +md""" +![The Asian giant hornet](https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Vespa_mandarinia_japonica1.jpg/1280px-Vespa_mandarinia_japonica1.jpg) +*The Asian giant hornet (credit: Picture by KENPEI on Wikipedia)* +""" + +# ╔═╡ bfb49d79-772d-4066-bd96-14143a1b5eeb +md""" +Consider below the coordinates of feeder stations with the return times of the hornets marked there. +""" + +# ╔═╡ c1850e64-9e2c-46fb-b7cd-22e8af81d3aa +xs, ys, ts, true_location = generate_data(); + +# ╔═╡ 28cb3363-6856-4164-b60e-36ec2e88ed56 +scatter( + xs, ys, label = "wasp locations", marker_z = ts, + title = "Locations of wasps colored by return time", + xlims = (0, 1000), ylims = (0, 1000) +) + +# ╔═╡ 484b56ec-57b2-496d-a5cf-1b1c0da97c58 +md""" +!!! question + Where is the hornet nest located? You may assume the nest is somewhere within the plot's boundaries. +""" + +# ╔═╡ 66862953-aeb2-4310-90bc-673260f0ecc0 +x_nest_sp = missing # vector with possible values of the nest's x-coordinate + +# ╔═╡ f522cbcb-112b-4d56-b860-5aee44b1aa2f +y_nest_sp = missing # vector with possible values of the nest's y-coordinate + +# ╔═╡ 511f27c3-7cdb-426f-b794-85897ff44135 +begin + scatter(x_nest_sp, y_nest_sp, opacity = 0.1, color = :blue, label = "Estimated nest locations", xlims = (0, 1000), ylims = (0, 1000), markershape = :square); + scatter!(xs, ys, color = :orange, label = "wasp locations", marker_z = ts) + scatter!(true_location[1:1], true_location[2:2], color = RGB(0, 1, 0), label = "True nest location", markershape = :square) +end + +# ╔═╡ Cell order: +# ╠═ef127ffc-1e2e-4c30-945b-d6cded4d6515 +# ╠═deb237f9-f0ff-4dfc-8627-43053ccdeb20 +# ╟─2d709e7b-3350-47ec-a3ed-8aa47ad5a8c2 +# ╟─af96af94-d969-4e9a-93f4-e205f8b7f576 +# ╟─07f05baa-1336-4e1a-9cbc-5f4506e5b34a +# ╟─90aee617-84d5-4915-afb4-e7d422cfb4b7 +# ╟─082a4cc0-b151-4f8b-87da-278484218e6e +# ╟─bfb49d79-772d-4066-bd96-14143a1b5eeb +# ╠═c1850e64-9e2c-46fb-b7cd-22e8af81d3aa +# ╟─28cb3363-6856-4164-b60e-36ec2e88ed56 +# ╟─484b56ec-57b2-496d-a5cf-1b1c0da97c58 +# ╠═66862953-aeb2-4310-90bc-673260f0ecc0 +# ╠═f522cbcb-112b-4d56-b860-5aee44b1aa2f +# ╠═511f27c3-7cdb-426f-b794-85897ff44135 diff --git a/src/exercises/calib_fermenter_monod.jl b/src/exercises/calib_fermenter_monod.jl new file mode 100644 index 00000000..83a2f0a1 --- /dev/null +++ b/src/exercises/calib_fermenter_monod.jl @@ -0,0 +1,270 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "32" +#> title = "6. Calibration fermenter monod" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Calibration fermenter monod" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ c54dae10-60af-4141-b56d-ed61cb0ced8a +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 245ca9d0-10f9-11ef-0ef6-a73594e96db9 +using Markdown, InteractiveUtils + +# ╔═╡ 16438e07-1b2b-467e-822a-081d19cae92b +using Catalyst, OrdinaryDiffEq + +# ╔═╡ 295caa68-db27-4c9b-bc34-86ab088fec24 +using Turing, StatsPlots, StatsBase + +# ╔═╡ dc6e9bdc-dae0-43aa-b624-f9314d1d9884 +using LinearAlgebra, Optim + +# ╔═╡ 2f0a4c62-3441-4c63-9bb9-383e7f554eb5 +md""" +# Exercise: Fermenter - Monod kinetics - Calibration +""" + +# ╔═╡ 595ea8ee-bc67-4696-9232-982612fb554d +md""" +In one of the previous practicals we were introduced to a fermenter in which biomass $X$ [$\mathrm{g/L}$] grows by breaking down substrate $S$ [$\mathrm{g/L}$]. The reactor is fed with an inlet flow rate $Q_{in}$ [$\mathrm{L/h}$], which consists of a (manipulable) input concentration of substrate $S_{in}$ [$\mathrm{g/L}$]. This process was modelled using Monod kinetics: + +$$\begin{eqnarray*} +S + X \xrightarrow[\quad\quad]{k} (1 + Y) \, X \quad\quad\quad\quad \textrm{with} \quad k = \cfrac{\mu_{max}}{S + K_s} \, . +\end{eqnarray*}$$ +""" + + +# ╔═╡ 824db995-7a66-4719-a534-7e0f6dec90b5 +md""" +The *reaction network object* for this model could be set-up as: +""" + +# ╔═╡ 245c2636-95da-4c76-8b03-c4d20bbabb48 +# fermenter_monod = @reaction_network begin +# @species missing +# @parameters missing +# missing +# missing +# missing +# end + +# ╔═╡ 956790e2-6cac-46c9-886e-24d5aceae1c5 +# convert(missing, missing) + +# ╔═╡ de8ddc14-8f82-403d-8f42-29673ef2a722 +md""" +which resulted in the following differential equations: + +$$\begin{eqnarray*} +\cfrac{dS}{dt} &=& \cfrac{Q}{V} \left(S_{in} - S \right) - \mu_{max}\cfrac{S}{S + K_s} X\\ +\cfrac{dX}{dt} &=& -\cfrac{Q}{V} X + Y \mu_{max}\cfrac{S}{S + K_s} X +\end{eqnarray*}$$ +""" + +# ╔═╡ b7b7d58f-d406-4596-b834-ced6d8fada83 +md""" +Suppose that during an experiment measurement data has been collected of the substrate $S$ and biomass $X$ concentration at an interval of $5\;\mathrm{h}$ within $100\;\mathrm{h}$: +""" + +# ╔═╡ 99c6f31a-0968-4804-9980-71fcc1af1f49 +S_meas = [1.0e-5, 0.0047, 0.00796, 0.01056, 0.01214, 0.01325, 0.01344, 0.01338, 0.0115, 0.00917, 0.00604, 0.00458, 0.00438, 0.00342, 0.00323, 0.00329, 0.00312, 0.00314, 0.00319, 0.00299, 0.00311] + +# ╔═╡ bf4ad873-e0fe-415c-9e78-fe0b5ac1414e +X_meas = [0.00052, 0.00042, 0.00074, 0.00078, 0.00122, 0.00159, 0.00242, 0.00372, 0.00534, 0.0077, 0.00935, 0.00997, 0.01114, 0.01144, 0.01264, 0.01276, 0.01183, 0.01319, 0.01256, 0.01277, 0.01377] + +# ╔═╡ 1dae5875-f405-4ecb-8b7b-3c3f22b549bb +t_meas = 0.0:5.0:100.0 + +# ╔═╡ 6c481447-28c6-4530-bf2c-64762121bc71 +md""" +Make a scatter plot of the measured data for both $S$ and $X$. Use the following options: +- `label=\"S meas\", color=:blue` for $S$, and +- `label=\"X meas\", color=:red` for $X$. +""" + +# ╔═╡ 918fd524-81fa-4aff-a403-37402e47235b +# begin +# missing +# missing +# end + +# ╔═╡ ef977370-06ee-4a73-85e2-609a744167d3 +md""" +We have previously used the following parameter values: + +- `μmax = 0.40`$\mathrm{h^{-1}}$, `Ks = 0.015`$\mathrm{g/L}$, `Sin = 0.022`$\mathrm{g/L}$ +- `Y = 0.67`, `Q = 2.0`$\mathrm{L/h}$, `V = 40.0`$\mathrm{L}$ + +Furthermore, suppose that at $t = 0\;h$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concentration of `0.0005`$\mathrm{g/L}$. + +Calibrate the parameter values for $\mu_{max}$ and $K_s$ using the aforementioned measurement data for $S$ and $X$ in a timespan of `[0, 100]`$\mathrm{h}$. Take the values above as initial values for $\mu_{max}$ and $K_s$. +""" + +# ╔═╡ f6a8f134-6db0-4d74-8af5-82826347d8f0 +md""" +Declare the Turing model. Assume the following for the priors: +- The measurement error is an unknown positive value, but probably near $0$. +- The parameters `μmax` and `K` are both positive and expected to be in $[0.0, 1.0]$, presumably around $0.1$. +""" + +# ╔═╡ 4c28a66a-ee2c-42a2-95c7-ea4ddb6a232d +# @model function fermenter_inference(t_meas) + # σ_S ~ missing + # σ_X ~ missing + # μmax ~ missing + # Ks ~ missing + # parms = missing + # oprob = missing + # osol = missing + # S ~ missing + # X ~ missing +# end + +# ╔═╡ 5928a9f3-f33c-4689-a6e5-637447f420d6 +md""" +!!! tip + This model can change between being non-stiff and stiff based on the sampled parameter values. You can use an auto-switching solver such as `AutoTsit5(Rosenbrock23())` here to make calibration more stable. +""" + +# ╔═╡ 3136b15d-5078-4bcd-954b-e89bcb8aed1b +md""" +Provide the measurements to the Turing model. +""" + +# ╔═╡ 6a508a62-61b9-4273-8e45-b26f594e8da9 +# fermenter_inf = missing + +# ╔═╡ 63420055-55f8-4def-8b0e-11ea61483010 +md""" +Optimize the likelihood of the parameters ($\sigma_S$, $\sigma_X$, $\mu_{max}$ and $K_s$) using the NelderMead optimizer. Store the optimization results in `results_mle`. Optionally, you can specify starting points for $\sigma_S$, $\sigma_X$, $\mu_{max}$ and $K_s$ to the optimizer to improve the consistency of the results. For the gaussian noise you can just use a value of 0.1. +""" + +# ╔═╡ d52c9da8-d8a4-4db0-ac6d-6d16ccf4775c +# results_mle = missing + +# ╔═╡ e1b0ee01-f16c-40e9-a0f9-80072d690936 +md""" +Visualize a summary of the optimized parameters. Beware that this may take a lot of time... +""" + +# ╔═╡ f2d7daf8-8218-446d-b1d2-e9e05aeadfd9 +# missing + +# ╔═╡ 23d58bb1-d077-402e-8bee-3866c68e069a +md""" +Get the optimized values and assign them to `μmax_opt` and `Ks_opt`. +""" + +# ╔═╡ 7b3a3677-b251-43c1-b125-6d6ff1a11ea3 +# μmax_opt = missing + +# ╔═╡ fa77bcbe-2ddc-4113-8f6a-4a18d219da9e +# Ks_opt = missing + +# ╔═╡ 05d13a48-adc8-4e24-a6e4-be24af2c7a59 +md""" +Make a plot of $S$ and $X$ simulated with the optimized parameter values. +""" + +# ╔═╡ 57ef3824-7c20-4876-8d83-665cb4f97a58 +md""" +Set up parameter values with optimized parameter values: +""" + +# ╔═╡ 75cf59ed-af8e-4e8a-8ed2-1f3bf4d386d0 +# params_opt = missing + +# ╔═╡ 4e8870dc-2da6-4b80-82d6-26c7ceedad7d +md""" +Create an ODEProblem and solve it. Use `Tsit5()` and `saveat=0.5`. +""" + +# ╔═╡ 853c1a92-d50f-4b05-9ed3-d3ee1656665a +# oprob_opt = missing + +# ╔═╡ f45e8124-e942-438e-99c5-3032ccc01454 +# osol_opt = missing + +# ╔═╡ 5a39b0e0-1ea1-4854-8e68-66d0d4bbf25c +md""" +Plot $S$ and $X$ simulated with the optimized parameter values together with the measured data. +""" + +# ╔═╡ d0156099-ad03-4711-ac0f-94882fb78266 +# begin +# missing +# missing +# missing +# end + +# ╔═╡ 257ae1a9-6264-4122-80be-8022b4b7500c +md""" +!!! question + How do the found optimal parameter values compare to the original values? *Or in other words*: what is the impact to be expected when we simulate the fermenter with the optimal values? +""" + +# ╔═╡ 43cdfb9d-1874-4b42-b350-545029a5f725 +md""" +- Answer: +""" + +# ╔═╡ 22a6aeb4-559c-4f69-82fd-d021f68e1f17 +md""" +!!! hint + Think about the meaning of the estimated parameters and their impact on the variables $S$ and $X$. +""" + +# ╔═╡ Cell order: +# ╠═245ca9d0-10f9-11ef-0ef6-a73594e96db9 +# ╠═c54dae10-60af-4141-b56d-ed61cb0ced8a +# ╠═16438e07-1b2b-467e-822a-081d19cae92b +# ╠═295caa68-db27-4c9b-bc34-86ab088fec24 +# ╠═dc6e9bdc-dae0-43aa-b624-f9314d1d9884 +# ╟─2f0a4c62-3441-4c63-9bb9-383e7f554eb5 +# ╟─595ea8ee-bc67-4696-9232-982612fb554d +# ╟─824db995-7a66-4719-a534-7e0f6dec90b5 +# ╠═245c2636-95da-4c76-8b03-c4d20bbabb48 +# ╠═956790e2-6cac-46c9-886e-24d5aceae1c5 +# ╟─de8ddc14-8f82-403d-8f42-29673ef2a722 +# ╟─b7b7d58f-d406-4596-b834-ced6d8fada83 +# ╠═99c6f31a-0968-4804-9980-71fcc1af1f49 +# ╠═bf4ad873-e0fe-415c-9e78-fe0b5ac1414e +# ╠═1dae5875-f405-4ecb-8b7b-3c3f22b549bb +# ╟─6c481447-28c6-4530-bf2c-64762121bc71 +# ╠═918fd524-81fa-4aff-a403-37402e47235b +# ╟─ef977370-06ee-4a73-85e2-609a744167d3 +# ╟─f6a8f134-6db0-4d74-8af5-82826347d8f0 +# ╠═4c28a66a-ee2c-42a2-95c7-ea4ddb6a232d +# ╟─5928a9f3-f33c-4689-a6e5-637447f420d6 +# ╟─3136b15d-5078-4bcd-954b-e89bcb8aed1b +# ╠═6a508a62-61b9-4273-8e45-b26f594e8da9 +# ╟─63420055-55f8-4def-8b0e-11ea61483010 +# ╠═d52c9da8-d8a4-4db0-ac6d-6d16ccf4775c +# ╟─e1b0ee01-f16c-40e9-a0f9-80072d690936 +# ╠═f2d7daf8-8218-446d-b1d2-e9e05aeadfd9 +# ╟─23d58bb1-d077-402e-8bee-3866c68e069a +# ╠═7b3a3677-b251-43c1-b125-6d6ff1a11ea3 +# ╠═fa77bcbe-2ddc-4113-8f6a-4a18d219da9e +# ╟─05d13a48-adc8-4e24-a6e4-be24af2c7a59 +# ╟─57ef3824-7c20-4876-8d83-665cb4f97a58 +# ╠═75cf59ed-af8e-4e8a-8ed2-1f3bf4d386d0 +# ╟─4e8870dc-2da6-4b80-82d6-26c7ceedad7d +# ╠═853c1a92-d50f-4b05-9ed3-d3ee1656665a +# ╠═f45e8124-e942-438e-99c5-3032ccc01454 +# ╟─5a39b0e0-1ea1-4854-8e68-66d0d4bbf25c +# ╠═d0156099-ad03-4711-ac0f-94882fb78266 +# ╟─257ae1a9-6264-4122-80be-8022b4b7500c +# ╠═43cdfb9d-1874-4b42-b350-545029a5f725 +# ╟─22a6aeb4-559c-4f69-82fd-d021f68e1f17 diff --git a/src/exercises/calib_intro.jl b/src/exercises/calib_intro.jl new file mode 100644 index 00000000..ed13fd6e --- /dev/null +++ b/src/exercises/calib_intro.jl @@ -0,0 +1,1030 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "31" +#> title = "6. Calibration intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Calibration intro" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ f8a92690-990b-4341-89e1-322adbcb8d1b +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ a09f814a-0c6a-11ef-0e79-a50b01287d63 +using Markdown, InteractiveUtils + +# ╔═╡ 015050b3-3339-4b1a-ad7d-c358cce73675 +using Catalyst, ModelingToolkit, OrdinaryDiffEq + +# ╔═╡ a83f424f-1bbb-4e71-af81-302c0ce68907 +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ dbfe4800-0974-4ca1-bb0a-d8803409a98b +using Turing, StatsPlots, StatsBase + +# ╔═╡ 6e227e07-166a-41ce-839a-4c4c72addb23 +using LinearAlgebra, Optim + +# ╔═╡ b992c080-a0ce-4188-b632-e734a141e67d +using PlutoUI; TableOfContents() + +# ╔═╡ 37da8786-fea0-4c2f-a76f-6e6c68325a78 +md""" +# Introduction to calibration +""" + +# ╔═╡ 4623369d-8c5a-422d-9e40-0f1dd7586260 +md""" +## Goal of this practicum +""" + +# ╔═╡ 3cb0a166-ac53-4c3f-9832-e93742040cfb +md""" +In the models discussed in the previous sessions, we always knew the values of all parameters. In reality, the value of a parameter has to be calibrated, hence, estimated from experimental data. During this parameter estimation one attempts to find the set of parameter values for which the model predictions are as close as possible to the collected experimental data. +""" + +# ╔═╡ 987f0a4d-e416-4ceb-adbe-3dcdca9d0996 +md""" +The search of optimal parameter values usually involves a function, such as a loss function, a (log) likelihood function or a posterior distribution function. In this session we will be (mainly) using the MLE (Maximum Likelihood Estimation) and MAP (Maximum A Posteriori estimation) methods, which respectively try to maximize the likelihood function and the posterior probability of the data. + +In order to understand the difference, recall Bayes' theorem applied to a set of parameters $θ$ and data $D$: +```math +P(θ \mid D) = \frac{P(D \mid θ) \, P(θ)}{P(D)} +``` +In the MLE method, the likelihood function $P(D \mid θ)$ (= the probability of the data given the parameters) is maximized during the search of optimal parameter values of a model in order to fit experimental data. The parameter values are considered unknown but viewed as fixed points. + +In the MAP method, the posterior probability $P(θ \mid D)$ (= the probability of the parameters given the data) is maximized instead. Instead of viewing the parameter values as fixed points, they are now treated as random variables in the model which follow a prior distribution. In other words, we have prior belief in which distribution these parameters come from (Normal, Beta, etc). Once new data comes in, we update our prior belief, leading to a posterior belief. Hence, we now have a better idea from which distribution these parameters come. + +One caveat with the MLE and MAP methods are that they only return a point estimate of the optimal value, which gives no information on how certain we are about this value. A more informative, yet computationally exhaustive method is using the MCMC (Markov chain Monte Carlo) sampling methods, which approximate the entire posterior distribution of the unknown values. +""" + +# ╔═╡ 75efff36-8da7-4d04-afa2-a2f8324bc103 +md""" +In this notebook we will calibrate the different parameters involved in the grass growth models. To illustrate this concept, we first revisit the three simple models modelling the grass growth yield. +""" + +# ╔═╡ 3dcb9c9d-370b-4031-b7c0-cee80742557a +md""" +## Grass growth models +""" + +# ╔═╡ 7a14aa59-6e6f-4266-a0b3-84ab55f2efc5 +md""" +In this notebook, three different models will be used, each modelling the yield of grass in a grassland: + +- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ +- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ +- Gompertz growth model: $\cfrac{dW}{dt} = \left( \mu - d \ln(W) \right) W$ + +with output $W$ the grass yield, and $W_f$, $\mu$ and $d$ parameters. The table below shows some typical parameter values and initial conditions for grasslands similar to the one we observed, which we can use as prior information. + +| | $\mu$ | $W_f$ | $d$ | $W_0$ | +|:----------- |:----------:|:-----------:|:------------:|:------------:| +| Logistic | 0.07 | 10.0 | | 2.0 | +| Exponential | 0.02 | 10.0 | | 2.0 | +| Gompertz | 0.09 | | 0.040 | 2.0 | + +Hence, for each grass growth model, we will optimize the parameter values together with the initial value. +""" + +# ╔═╡ 85cd60a8-b448-4375-9b6d-399c4336c319 +md""" +In each of the three models we will use the following timespan: +""" + +# ╔═╡ 5b320989-3e0b-447b-bc9a-25fb221ce609 +tspan = (0.0, 100.0) # this will be the same for the three models + +# ╔═╡ 2481cd4f-0efc-4450-ab3d-4a5492597f36 +md""" +Variables containing the initial condition and parameters values will be defined later in the objective function. +""" + +# ╔═╡ 1aa44f2b-6f33-437f-b9dd-89762d9f28ea +md""" +### The measurement data +""" + +# ╔═╡ b2b433ed-0266-4bea-a7e8-32adba542d4c +md""" +Assume that the measured grass yields (of a certain plant type) are the following: +""" + +# ╔═╡ 7c966a66-0091-4b81-9a7e-02ccd0d3db10 +W_meas = [1.87, 2.45, 3.72, 4.32, 5.28, 7.01, 6.83, 8.62, 9.45, 10.31, 10.56, 11.72, 11.05, 11.53, 11.39, 11.7, 11.15, 11.49, 12.04, 11.95, 11.68] + +# ╔═╡ 3edd2acc-a865-4675-afef-8868c68256f1 +md""" +They have been measured at the following corresponding time instances: +""" + +# ╔═╡ 877298e8-b61b-4c3a-ba2c-2827acdcfb50 +t_meas = 0:5:100 + +# ╔═╡ ef06cc43-510b-4ff9-b0b7-1c7fc267e9b1 +md""" +We can make a scatter plot of this data (including a title, a legend label, an X-axis label, X- and Y-axis limits) in the following way: +""" + +# ╔═╡ cb2bc6ee-4211-47e1-9956-5cf1b0c0671d +scatter(t_meas, W_meas, title="Grass growth data", + label="Yield", + xlabel="t", + xlims=(0, 100), + ylims=(0, 14)) + +# ╔═╡ 50e6af5b-04f4-495c-853b-c746fd27254f +md"## Calibration with Turing" + +# ╔═╡ a665740a-ea94-4452-8cbc-a1647319dfce +md""" +We will be using the familiar Turing framework to perform optimisation, which consists roughly of the following steps: +- Define your model as a Turing model, with the following components: + - The observed input data as input of the model + - Priors for all unknown values such as initial values, parameter values and the measurement error + - An (ODE-based) model that predicts values of the output variable based on the observed input values and the unknown initial values, parameter values, etc. + - The relationship between the observed outputs and the predicted outputs, which often comes down to specifying the measurement error +- Instantiate the Turing model and condition it on the observed values of the output variable +- Call an optimisation algorithm on the Turing model +- Extract the optimized parameters +- Visualise the results +""" + +# ╔═╡ b2f20a9a-0613-4bb4-a1d3-df1620aef105 +md"### The priors" + +# ╔═╡ 6852f166-350e-4434-8304-382c01113ba1 +md""" +During calibration, the prior distributions have two roles: +1. Define for every unknown quantity **the bounds**: the optimizer will only search within the domain of the prior distribution. Note: you can add additional bounds to any existing distribution using the `truncated` function. +1. Define part of the **posterior probability** of observing the data given the parameter values. In other words, the theoretically optimal values depend on your prior distributions. + +**Note that the second only applies to methods that take prior information into account, which Maximum Likelihood Estimation (MLE) does not.** +""" + +# ╔═╡ 4f78b2e8-9132-4175-b9ae-9f5979649e90 +md"#### Choice of priors for the grass growth models" + +# ╔═╡ 557e69c5-d0c6-41ee-b3df-851bd1d16923 +md""" +In general for our grass models, we will need to define priors for the following: +- the measurement error (standard deviation) $\sigma_W$. +- the initial condition $W_0$. +- the parameters $\mu$ and either $W_f$ or $d$ (depending on the model). +""" + +# ╔═╡ 237934b7-c89a-4ef6-938f-4629c0fa4c95 +md""" +For the measurement error $\sigma_W$, a distribution with most of its probability density around 0 and a long positive tail is often a good choice. We will use the Exponential distribution here for this purpose. As for its parameters, considering our yield is in the order of 1 to 10 [t/ha], an expected value (and therefore standard deviation) of around 1 seems appropriate. +""" + +# ╔═╡ 513ee94d-5d49-43d2-a1db-638fe9e2de1a +@bind example_mean_error Slider(0.01:0.01:10.0, show_value = true, default = 1) + +# ╔═╡ 8e163f77-b762-427a-b005-313284dc2e9e +plot(Exponential(example_mean_error), title = "Example prior for the measurement error", legend = false) + +# ╔═╡ d9c38e9d-12b8-4e98-8546-bbc123960e6f +md""" +For the initial- and parameter values, we will default to log-normal distributions. These have the following properties that generally fit well with biological parameters, such as is the case in this notebook: +- A positive domain, which bounds possible values to the positive numbers +- Most of the probability density around the expected value +- A long postive tail, which allows for outliers + +As the distribution of a variable whose logarithm is normally distributed, it does have some strange behaviour: the arguments of the distribution are the mean and standard deviation of the **logarithm**. To clarify, for $X \sim \mathrm{LogNormal(μ, σ)}$: +- $μ = E[\mathrm{log}(X)]$ +- $σ = \sqrt{\mathrm{Var}(\mathrm{log}(X))}$ + +Practically, this means **you need to specify the logarithm of the desired expected value and play around with the standard deviation**. For this exercise, we will choose an expected value based on the table discussed earlier in this section and keep the standard deviation to the default value of 1. Additionally, for some parameters we may want to truncate them to to keep them within (biologically and numerically) sensible bounds. +""" + +# ╔═╡ 39c70e22-86e0-467e-abab-24f99548b096 +@bind example_log_μ Slider(0.01:0.01:10.0, default = 0.1, show_value = true) + +# ╔═╡ ddef987d-59db-4adf-ae77-d4d32a41ddec +@bind example_log_σ Slider(0.01:0.01:3.0, default = 1.0, show_value = true) + +# ╔═╡ 54fb706b-9036-42b5-833e-4081f4c2179c +begin + example_param_prior = LogNormal(log(example_log_μ), example_log_σ) + + println("""Look at this strange distribution with + - mean = $(mean(example_param_prior)) + - median = $(median(example_param_prior)) + - σ = $(std(example_param_prior))""") + plot(example_param_prior, xlims = (0, 5*example_log_μ), title = "Example prior for the model parameters", legend = false) +end + +# ╔═╡ 9a5bc72b-346d-4e95-a873-783037ed98bc +md""" +## Example: the Logistic growth model +""" + +# ╔═╡ 4b17f7e6-26b0-425a-9f53-ccf1689639fe +md""" +We will illustrate the calibration with the logistic growth model. The latter can be done via a Catalyst reaction network model or via a model built with ModelingToolkit. As an example, we will show both possibilities for the logistic growth model. +""" + +# ╔═╡ 6736542c-5378-480e-a56b-65956b416225 +md""" +#### 1) Catalyst based model +""" + +# ╔═╡ ba56adb1-9405-40d5-be48-4273b42ab145 +# growth_log = @reaction_network begin +# @species W(t)=2.0 +# @parameters μ=0.07 Wf=10.0 +# μ*(1-W/Wf), W --> 2W +# end + +# ╔═╡ 1c311b6f-6b18-4170-b500-33a8e4d3cb2d +md""" +#### ̇2) ModelingToolkit based model +""" + +# ╔═╡ 50220e1f-8e42-44da-be03-08c11df967f0 +@variables W(t) + +# ╔═╡ 449286b0-0210-4c6b-b28f-fca87b52d674 +@parameters μ Wf d + +# ╔═╡ f720eac9-cb29-4eef-ac94-471395941c0f +# IF YOU UNCOMMENT THE FOLLOWING LINE, PLEASE COMMENT THE CATALYST MODEL FIRST!!! +@mtkbuild growth_log = ODESystem([D(W) ~ μ*(1 - W/Wf)*W], t) + +# ╔═╡ d75246d4-e03b-4684-be7d-4bcfb61ed7ef +md""" +### Declaration of the Turing model +""" + +# ╔═╡ 8a9115eb-4044-4cab-a7db-39b5dd86c70d +@model function growth_log_inference(t_meas) + σ_W ~ Exponential() + W0 ~ LogNormal(log(1)) + μ ~ LogNormal(log(0.1)) + Wf ~ truncated(LogNormal(log(10)), lower = 0.1) # prevent zeros in denominator of our model + u0_log = [:W => W0] + parms_log = [:μ => μ, :Wf => Wf] + oprob_log = ODEProblem(growth_log, u0_log, tspan, parms_log) + osol_log = solve(oprob_log, AutoTsit5(Rosenbrock23()), saveat=t_meas) + W_s ~ MvNormal(osol_log[:W], σ_W) +end + +# ╔═╡ 48c9f616-d298-40da-b917-225abd39b3d9 +md""" +Some remarks: +- The time points are the ones from the measurements, therefore, we set: `saveat=t_meas`. +- Depending on the priors, the parameter values of our ODE may vary wildly during calibration. As this can influence the stiffness of the system, it can be beneficial to use an ODE solver that automatically detects the stiffness of the system and switches to a stiff solver if necessary, such as `AutoTsit5(Rosenbrock23())`. We don't expect you to know when this is necessary, but if your calibration regularly gives instability errors, this may help! +""" + +# ╔═╡ 35f158c1-858d-4e4d-ac3d-bf4807dad9a0 +md""" +We will provide the measurements to the Turing model: +""" + +# ╔═╡ 8b6534d6-776b-4285-8498-a9b34051facc +growth_log_inf = growth_log_inference(t_meas) | (W_s = W_meas, ) + +# ╔═╡ a6972aef-63ad-401c-acf5-6d59f9fc6698 +md""" +We are now ready to optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). This is done by calling the `optimize` function, providing the previously created object `growth_log_inf`, the method for estimating the parameters and (optionally) an algorithm (default: Nelder-Mead) to implement the method. +""" + +# ╔═╡ 73e35289-6dc0-4e2e-83eb-b56f83cdbbbf +md""" +### Method - Maximum Likelihood Estimation +""" + +# ╔═╡ 47bd729c-4851-42f7-a03f-6ceacd3c717e +md""" +We will use the MLE (Maximum Likelihood Estimation) method here and store the optimization results in `results_log_mle`. +""" + +# ╔═╡ f34bb7ac-1ed8-4dd9-b0b9-49bd6e0e1d71 +results_log_mle = optimize(growth_log_inf, MLE(), NelderMead()) + +# ╔═╡ a4dc130a-87ef-4b77-9b62-07f64917e34d +md""" +!!! note "NelderMead algorithm" + We optimize the likelihood using the Nelder–Mead method, which is deterministic: given the same starting point, it will always return the same result for a fixed problem. However, runs may still differ because in the Turing model the initial values are randomly sampled from the specified distributions. Providing explicit starting points to the optimizer can therefore improve consistency. + + ``` + begin + init_params = [1, 2, 0.07, 10] + results_log_mle = optimize(growth_log_inf, MLE(), init_params, NelderMead()) + end + ``` +""" + +# ╔═╡ e55404ab-6762-4f39-bb42-9c195334a214 +md""" +You can visualize a summary of the optimized parameters by piping them to `coeftable`. Beware that this can take a lot of time... +""" + +# ╔═╡ 80e7f6b8-7592-48a3-8587-f1953d1bfcd8 +# results_log_mle |> coeftable + +# ╔═╡ a1ca7d0e-639c-42d4-be09-5c61a2008f29 +md""" +You can obtain the actual optimized values using the function `coef` on the results object in conjunction by calling the parameters by name preceded by a colon. Here we assign the optimized parameter values to some suitable variable names: +""" + +# ╔═╡ 30399b9a-1d77-4140-9ad3-5eed636a5b99 +W0_opt1_log = coef(results_log_mle)[:W0] + +# ╔═╡ e1b8e4ba-c1f1-48d5-87a2-edce19c9fe7a +μ_opt1_log = coef(results_log_mle)[:μ] + +# ╔═╡ a943e0fe-1376-4ef1-9c45-25c6f95e3b96 +Wf_opt1_log = coef(results_log_mle)[:Wf] + +# ╔═╡ 72e065d4-7b1b-4f46-b373-935be8d801fc +md""" +Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ 0b2ffd6f-01cd-4f11-9062-d38b3c13a5b1 +md""" +Setting up initial condition with optimized initial condition: +""" + +# ╔═╡ 590b1006-0e37-4668-9b4a-3588fab45696 +u0_opt1_log = [:W=>W0_opt1_log] + +# ╔═╡ 8ff28a2e-185d-4dca-ad3b-a0b1507646d6 +md""" +Setting up parameter values with optimized parameter values: +""" + +# ╔═╡ 14d24cbd-3259-41bb-9013-b4fe25a3be4c +parms_opt1_log = [:μ=>μ_opt1_log, :Wf=>Wf_opt1_log] + +# ╔═╡ 6c134677-3ec1-4e0a-88b5-01341a096675 +md""" +Next, we create an ODEProblem and solve it: +""" + +# ╔═╡ 7a81f4a0-8f7c-4e05-9c3f-2438eab9b691 +oprob_opt1_log = ODEProblem(growth_log, u0_opt1_log, tspan, parms_opt1_log) + +# ╔═╡ ac80099b-d8f0-4eba-809d-d482bd354d35 +osol_opt1_log = solve(oprob_opt1_log, Tsit5(), saveat=0.5) + +# ╔═╡ b58f2c24-e0ea-48a8-b0b7-d0faf9642340 +md""" +Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ 55eba435-6ca4-4f4f-b08a-be700d5bda91 +begin + plot(osol_opt1_log, label="Logistic growth", xlabel="t", + xlims=(0, 100), ylims=(0, 14)) + scatter!(t_meas, W_meas, label="Yield") +end + +# ╔═╡ 5d386b00-93b5-4a88-b4e3-e5c3eebd6dd5 +md""" +### Method - Maximum A Posterior +""" + +# ╔═╡ 6f0e91d0-6b99-4cf1-8145-523589a21e89 +md""" +We will use the MAP (Maximum A Posterior) method here and store the optimization results in `results_log_map`. +""" + +# ╔═╡ 209742ca-36bb-42a5-bf8a-291a40c47757 +results_log_map = optimize(growth_log_inf, MAP(), NelderMead()) + +# ╔═╡ 07efb21a-0dac-46d5-b9e1-8f4757c6cedf +md""" +You can visualize a summary of the optimized parameters by piping them to `coeftable`. Beware that this can take a lot of time... +""" + +# ╔═╡ a93a7eeb-aad1-49f3-a05f-1512adb08b6b +# results_log_map |> coeftable + +# ╔═╡ ccc66805-efce-4f25-b76b-7cff23901ba4 +md""" +You can obtain the actual optimized values using the function `coef` on the results object in conjunction by calling the parameters by name preceded by a colon. Here we assign the optimized parameter values to some suitable variable names: +""" + +# ╔═╡ 57ef7d03-a6dc-40da-858d-5f9b9be613cd +W0_opt2_log = coef(results_log_map)[:W0] + +# ╔═╡ 62bd2e58-343a-4155-a65e-cf326d0975f4 +μ_opt2_log = coef(results_log_map)[:μ] + +# ╔═╡ 4d6cba88-be68-412d-aeec-28300c26a9da +Wf_opt2_log = coef(results_log_map)[:Wf] + +# ╔═╡ 4bc97af7-debc-4170-a2d5-4e6c928e5183 +md""" +Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ a0033986-4946-45a2-a53e-29c38f35ed5c +md""" +Setting up initial condition with optimized initial condition: +""" + +# ╔═╡ a570ebab-64de-4934-b887-77a8e2fb42e8 +u0_opt2_log = [:W=>W0_opt2_log] + +# ╔═╡ feee9cc1-ebe2-4c77-a11e-53ca00864ccf +md""" +Setting up parameter values with optimized parameter values: +""" + +# ╔═╡ 73e86176-f314-463c-8821-51c0f3cc1a56 +parms_opt2_log = [:μ=>μ_opt2_log, :Wf=>Wf_opt2_log] + +# ╔═╡ c17c4e00-8688-4f9c-b0ae-741d70601aba +md""" +Next, we create an ODEProblem and solve it: +""" + +# ╔═╡ f751b962-1810-429c-b34b-658b63fa9ba0 +oprob_opt2_log = ODEProblem(growth_log, u0_opt2_log, tspan, parms_opt2_log) + +# ╔═╡ 0fd058cc-9bd8-4e12-b5dd-79818519165b +osol_opt2_log = solve(oprob_opt2_log, Tsit5(), saveat=0.5) + +# ╔═╡ 57c95382-69cc-47e7-aa94-4767136bb23b +md""" +Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ 4e07c39b-502d-4b38-ae7f-f103cb4bae16 +begin + plot(osol_opt2_log, label="Logistic growth", xlabel="t", + xlims=(0, 100), ylims=(0, 14)) + scatter!(t_meas, W_meas, label="Yield") +end + +# ╔═╡ 29170e2a-9916-438e-92ca-9f4783397b5e +md""" +### Method - MCMC with NUTS +""" + +# ╔═╡ 7ff9fe52-156b-4a92-9058-781670de3abb +md""" +We will use Markov chain Monte Carlo (MCMC) method in combination with the No U-Turn Sampler (NUTS) here and store the optimization results in `results_log_nuts`. +""" + +# ╔═╡ 0c047043-3284-422a-9c88-2f4f4c170edf +results_log_nuts = sample(growth_log_inf, NUTS(), 500) + +# ╔═╡ 78a608a4-0ba7-400e-abb9-a9285c60681c +plot(results_log_nuts) # check convergence + +# ╔═╡ 19c362cb-2764-41c9-a571-2e8e2bfcde93 +# summarize(results_log_nuts) + +# ╔═╡ 93db47b2-34e8-43b4-beac-b5620fd444e7 +W0_opt3_log = mean(results_log_nuts[:W0]) + +# ╔═╡ 68c71cd2-6cdc-4b80-b6e7-75bfea344295 +μ_opt3_log = mean(results_log_nuts[:μ]) + +# ╔═╡ d6b9eaba-1d43-4e56-8fa1-bb2f87f6fe79 +Wf_opt3_log = mean(results_log_nuts[:Wf]) + +# ╔═╡ b843ee5e-9618-4b50-93db-77e19b4be366 +md""" +Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ 58014411-128c-41f4-b192-b815a3a8cc60 +md""" +Setting up initial condition with optimized initial condition: +""" + +# ╔═╡ ecf4b951-9b5f-441a-89f5-b0ccd040ba02 +u0_opt3_log = [:W=>W0_opt3_log] + +# ╔═╡ b92bf532-5610-4ce8-b469-154b20cc5956 +md""" +Setting up parameter values with optimized parameter values: +""" + +# ╔═╡ e629fbd1-a7c7-4dc3-8b7d-bdca380fe5ad +parms_opt3_log = [:μ=>μ_opt3_log, :Wf=>Wf_opt3_log] + +# ╔═╡ a48df980-4847-418e-813d-1875d032ffd9 +md""" +Next, we create an ODEProblem and solve it: +""" + +# ╔═╡ 348e7454-c6dc-4d1e-b18b-6b34bc2fc0fc +oprob_opt3_log = ODEProblem(growth_log, u0_opt3_log, tspan, parms_opt3_log) + +# ╔═╡ f71f0cf4-b63e-452c-b971-f30d7503f1e5 +osol_opt3_log = solve(oprob_opt3_log, Tsit5(), saveat=0.5) + +# ╔═╡ 7346a003-aa05-41e3-a420-c97c1994cf3c +md""" +Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ 20a6f165-449a-42cc-9565-f342a7535422 +begin + plot(osol_opt3_log, label="Logistic growth", xlabel="t", + xlims=(0, 100), ylims=(0, 14)) + scatter!(t_meas, W_meas, label="Yield") +end + +# ╔═╡ 137bde23-76f2-4ebf-8bc2-ea8640001436 +md""" +## Exercises +""" + +# ╔═╡ 4aa71200-006b-4a15-ae75-67e36aa81522 +md""" +### Exercise 1 - Calibration of the exponential growth model + +Calibrate the initial condition and both parameters of the exponential growth model. Use the values mentioned in the *Table* as initials values for the optimization of the parameters. +""" + +# ╔═╡ cdab3079-04b0-4a44-b770-468c20e321e4 +md""" +Implement a *reaction network object* using Catalyst for the exponential growth model: +""" + +# ╔═╡ cf1a144e-09e9-42a3-b2a3-b8676a200a39 +# growth_exp = @reaction_network begin +# missing +# missing +# end +growth_exp = @reaction_network begin + μ*Wf, 0 --> W + μ, W --> 0 +end + +# ╔═╡ d69ed5dd-80cd-427e-9245-e42576a4688d +md""" +Convert the reaction network model into an ODE system to verify. +""" + +# ╔═╡ 8b429656-1c57-4278-9633-71311f400ed8 +# missing +convert(ODESystem, growth_exp) + +# ╔═╡ b4300e8a-8052-419b-98c8-0508ebee2393 +md""" +Declare the Turing model. Try to find sensible prior distributions. +""" + +# ╔═╡ 2c6ae74c-2da4-4867-ad8a-f4e835101d63 +# @model function growth_exp_inference(t_meas, W_meas) +# σ_W ~ missing +# W0 ~ missing +# μ ~ missing +# Wf ~ missing +# u0_exp = missing +# parms_exp = missing +# oprob_exp = missing +# osol_exp = missing +# W_meas ~ missing +# end +@model function growth_exp_inference(t_meas) + σ_W ~ Exponential() + W0 ~ LogNormal(log(1)) + μ ~ LogNormal(log(0.1)) + Wf ~ LogNormal(log(10)) + u0_exp = [:W => W0] + parms_exp = [:μ => μ, :Wf => Wf] + oprob_exp = ODEProblem(growth_exp, u0_exp, tspan, parms_exp) + osol_exp = solve(oprob_exp, AutoTsit5(Rosenbrock23()), saveat=t_meas) + W_s ~ MvNormal(osol_exp[:W], σ_W) +end + +# ╔═╡ c6a5d453-d610-4f65-847c-c878dd41726c +md""" +Provide the measurements to the Turing model. +""" + +# ╔═╡ fff17cf7-173d-4f64-94a9-4bf46acc882d +# growth_exp_inf = missing +growth_exp_inf = growth_exp_inference(t_meas) | (W_s = W_meas, ) + +# ╔═╡ eee55784-a641-445e-be75-0b19e2a94754 +md""" +Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). Do this with `MLE` method and Nelder-Mead. Store the optimization results in `results_exp_mle`. +""" + +# ╔═╡ 7844e4f5-3c7d-4b4b-beee-970c998c67a6 +# results_exp_mle = missing +results_exp_mle = optimize(growth_exp_inf, MLE(), NelderMead()) + +# ╔═╡ c81d0140-3f4e-4eb4-8a77-1f48c5e0ecbf +md""" +Visualize a summary of the optimized parameters. Beware: this can take a lot of time... +""" + +# ╔═╡ 7456455b-4f31-488f-990f-6ce534038e08 +# missing +# results_exp_mle |> coeftable + +# ╔═╡ b10c2ce4-d363-429c-a64c-ec29652137a5 +md""" +Get the optimized values and assign them to `W0_opt_exp`, `μ_opt_exp` and `Wf_opt_exp`. +""" + +# ╔═╡ 23b629a6-6866-416a-a768-9617ce6301db +# W₀_opt_exp = missing +W0_opt_exp = coef(results_exp_mle)[:W0] + +# ╔═╡ 8788082d-f5d1-4385-8037-a0d360a841c7 +# μ_opt_exp = missing +μ_opt_exp = coef(results_exp_mle)[:μ] + +# ╔═╡ 03a4fa85-08db-46d4-bb53-c0ccea90a211 +# Wf_opt_exp = missing +Wf_opt_exp = coef(results_exp_mle)[:Wf] + +# ╔═╡ 8f5e1413-227c-44fa-bb2d-3653cbc27e38 +md""" +Make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ 8a7f7aab-878e-41b5-b9da-d06747df042e +md" +Set up initial condition with optimized initial condition: +" + +# ╔═╡ 30602ff1-041b-4fca-bf8e-55ff57df9e37 +# u₀_opt_exp = missing +u0_opt_exp = [:W=>W0_opt_exp] + +# ╔═╡ 881011be-6434-416f-915b-3333e8dea32f +md""" +Set up parameter values with optimized parameter values: +""" + +# ╔═╡ 5602b88a-07f8-438b-994c-65f11e17a0ba +# parms_opt_exp = missing +parms_opt_exp = [:μ=>μ_opt_exp, :Wf=>Wf_opt_exp] + +# ╔═╡ 25be4255-0888-4ecd-a2fd-d66402c5cb50 +md""" +Create an ODEProblem and solve it. Use `Tsit5()` and `saveas=0.5`. +""" + +# ╔═╡ 36e8a174-d526-45ee-b3c6-88d698ad5d5f +# oprob_opt_exp = missing +oprob_opt_exp = ODEProblem(growth_exp, u0_opt_exp, tspan, parms_opt_exp) + +# ╔═╡ 7594147d-b3da-4e0d-896d-41baacb6d7be +# osol_opt_exp = missing +osol_opt_exp = solve(oprob_opt_exp, Tsit5(), saveat=0.5) + +# ╔═╡ 8e047be9-f0f0-4a75-91b5-c523f55f8c67 +md""" +Plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ 1a9587aa-2356-48df-abcc-2ce874fa5d24 +# begin +# missing +# missing +# end +begin + plot(osol_opt_exp, label="Exponential growth", xlabel="t", + xlims=(0, 100),ylims=(0, 14)) + scatter!(t_meas, W_meas, label="Yield") +end + +# ╔═╡ 785d500b-f8ea-446a-9952-2a5fd5d83d24 +md""" +### Exercise 2 - Calibration of the Gompertz growth model + +Calibrate the initial condition and both parameters of the Gompertz growth model. Use the values mentioned in the *Table* as initials values for the optimization of the parameters. +""" + +# ╔═╡ e754826a-7411-4072-b0dc-a4bad7a15f98 +md""" +Implement a system using MTK for the Gompertz growth model.\ +*Hint*: no need to redefine the variable `W(t)` and the parameters `μ` and `d` because they are defined at the beginning of this notebook. +""" + +# ╔═╡ 6e85c08e-9a97-4897-9c81-89517f55e254 +@mtkbuild growth_gom = ODESystem([D(W) ~ (μ - d*log(W))*W], t) + +# ╔═╡ e5081280-d226-4834-8932-c89becd8313c +md""" +Declare the Turing model. Try to find sensible prior distributions. +""" + +# ╔═╡ c739a908-2353-4e7a-8fbd-f640dc8cabe0 +# @model function growth_gom_inference(t_meas, W_meas) +# σ_W ~ missing +# W0 ~ missing +# μ ~ missing +# d ~ missing +# u0_gom = missing +# parms_gom = missing +# oprob_gom = missing +# osol_gom = missing +# W_meas ~ missing +# end +@model function growth_gom_inference(t_meas) + σ_W ~ Exponential() + W0 ~ truncated(LogNormal(log(1.0)), lower = 1e-5) # prevent log(0) + μ ~ truncated(LogNormal(log(0.1)), upper = 1.0) # prevent overly large exponential growth from crashing the solver - this model has no carrying capacity to slow things down + d ~ LogNormal(log(0.1)) + u0_gom = [:W => W0] + parms_gom = [:μ => μ, :d => d] + oprob_gom = ODEProblem(growth_gom, u0_gom, tspan, parms_gom) + osol_gom = solve(oprob_gom, AutoTsit5(Rosenbrock23()), saveat=t_meas) + W_s ~ MvNormal(osol_gom[:W], σ_W) +end + +# ╔═╡ 1d0383ad-54d6-4ff2-8555-def83bfff0e6 +md""" +Provide the measurements to the Turing model. +""" + +# ╔═╡ cd1cf2f8-9f7f-4ed4-9cb7-1a6efee68ab4 +# growth_gom_inf = missing +growth_gom_inf = growth_gom_inference(t_meas) | (W_s = W_meas, ) + +# ╔═╡ aba74ee0-0163-4e15-8b49-d8dcad4839f7 +md""" +Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $D$). Do this with `MLE` method and Nelder-Mead. Store the optimization results in `results_gom_mle`. +""" + +# ╔═╡ 0eda4142-1aaf-4e17-bd78-857e13e94acd +# results_gom_mle = missing +results_gom_mle = optimize(growth_gom_inf, MLE(), NelderMead()) + +# ╔═╡ 50629194-98ed-4d45-86a2-95ac22daac29 +md""" +Visualize a summary of the optimized parameters. Beware: this can take a lot of time... +""" + +# ╔═╡ 9c239fc6-275c-4d64-9fa2-6fd57295b757 +# missing +# results_gom_mle |> coeftable + +# ╔═╡ dede17f2-655d-4871-b6de-5a32804947dd +md""" +Get the optimized values and assign them to `W0_opt_gom`, `μ_opt_gom` and `D_opt_gom`. +""" + +# ╔═╡ e8c3f042-6058-4040-a67e-18563c04ee93 +# W₀_opt_gom = missing +W0_opt_gom = coef(results_gom_mle)[:W0] + +# ╔═╡ 665f4d03-3521-475a-a195-f861fd26bb69 +# μ_opt_gom = missing +μ_opt_gom = coef(results_gom_mle)[:μ] + +# ╔═╡ 13775d58-ac61-431c-a6c9-447c1eec7942 +# D_opt_gom = missing +d_opt_gom = coef(results_gom_mle)[:d] + +# ╔═╡ bc92f996-b626-4965-a286-d2c848eb1a21 +md""" +Make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ e2fde8e9-1f87-4ffe-8dae-2794664bfaa4 +md""" +Set up initial condition with optimized initial condition: +""" + +# ╔═╡ 23fd9fd9-a13c-4c56-a0b4-daec6f1d2cd8 +# u₀_opt_gom = missing +u0_opt_gom = [:W=>W0_opt_gom] + +# ╔═╡ 6f414d15-af0a-452a-98a1-dc0b7e54d617 +md""" +Set up parameter values with optimized parameter values: +""" + +# ╔═╡ 57ee8a12-24df-4598-935c-f5e259b504cb +# parms_opt_gom = missing +parms_opt_gom = [:μ=>μ_opt_gom, :d=>d_opt_gom] + +# ╔═╡ 48bc085c-9ce6-4752-a5a9-a814f803f571 +md""" +Create an ODEProblem and solve it. Use `Tsit5()` and `saveas=0.5`. +""" + +# ╔═╡ 5b9b2e5b-9deb-4ff6-a923-b15b8b08f0c9 +# oprob_opt_gom = missing +oprob_opt_gom = ODEProblem(growth_gom, u0_opt_gom, tspan, parms_opt_gom) + +# ╔═╡ 66ee9655-a006-4c0f-b1f1-5576efa8f896 +# osol_opt_gom = missing +osol_opt_gom = solve(oprob_opt_gom, Tsit5(), saveat=0.5) + +# ╔═╡ 52d7975a-5346-447f-9aad-4ecd11b6460a +md""" +Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ e96efd6a-a666-4120-8480-9423e5d82ae1 +# begin +# missing +# missing +# end +begin + plot(osol_opt_gom, label="Gompertz growth", xlabel="t", + xlims=(0, 100), ylims=(0, 14)) + scatter!(t_meas, W_meas, label="Yield") +end + +# ╔═╡ f0b4772d-a72b-44e0-a3a1-ba9ad4c4dfeb +md""" +Which grass growth model fits best these data? How can you prove this numerically? +""" + +# ╔═╡ 7e98a771-52bb-484e-82ab-2e42e7cb4053 +md"- Answer: missing" + +# ╔═╡ Cell order: +# ╟─37da8786-fea0-4c2f-a76f-6e6c68325a78 +# ╠═a09f814a-0c6a-11ef-0e79-a50b01287d63 +# ╠═f8a92690-990b-4341-89e1-322adbcb8d1b +# ╠═015050b3-3339-4b1a-ad7d-c358cce73675 +# ╠═a83f424f-1bbb-4e71-af81-302c0ce68907 +# ╠═dbfe4800-0974-4ca1-bb0a-d8803409a98b +# ╠═6e227e07-166a-41ce-839a-4c4c72addb23 +# ╠═b992c080-a0ce-4188-b632-e734a141e67d +# ╟─4623369d-8c5a-422d-9e40-0f1dd7586260 +# ╟─3cb0a166-ac53-4c3f-9832-e93742040cfb +# ╟─987f0a4d-e416-4ceb-adbe-3dcdca9d0996 +# ╟─75efff36-8da7-4d04-afa2-a2f8324bc103 +# ╟─3dcb9c9d-370b-4031-b7c0-cee80742557a +# ╟─7a14aa59-6e6f-4266-a0b3-84ab55f2efc5 +# ╟─85cd60a8-b448-4375-9b6d-399c4336c319 +# ╠═5b320989-3e0b-447b-bc9a-25fb221ce609 +# ╟─2481cd4f-0efc-4450-ab3d-4a5492597f36 +# ╟─1aa44f2b-6f33-437f-b9dd-89762d9f28ea +# ╟─b2b433ed-0266-4bea-a7e8-32adba542d4c +# ╠═7c966a66-0091-4b81-9a7e-02ccd0d3db10 +# ╟─3edd2acc-a865-4675-afef-8868c68256f1 +# ╠═877298e8-b61b-4c3a-ba2c-2827acdcfb50 +# ╟─ef06cc43-510b-4ff9-b0b7-1c7fc267e9b1 +# ╠═cb2bc6ee-4211-47e1-9956-5cf1b0c0671d +# ╟─50e6af5b-04f4-495c-853b-c746fd27254f +# ╟─a665740a-ea94-4452-8cbc-a1647319dfce +# ╟─b2f20a9a-0613-4bb4-a1d3-df1620aef105 +# ╟─6852f166-350e-4434-8304-382c01113ba1 +# ╟─4f78b2e8-9132-4175-b9ae-9f5979649e90 +# ╟─557e69c5-d0c6-41ee-b3df-851bd1d16923 +# ╟─237934b7-c89a-4ef6-938f-4629c0fa4c95 +# ╠═513ee94d-5d49-43d2-a1db-638fe9e2de1a +# ╠═8e163f77-b762-427a-b005-313284dc2e9e +# ╟─d9c38e9d-12b8-4e98-8546-bbc123960e6f +# ╠═39c70e22-86e0-467e-abab-24f99548b096 +# ╠═ddef987d-59db-4adf-ae77-d4d32a41ddec +# ╠═54fb706b-9036-42b5-833e-4081f4c2179c +# ╟─9a5bc72b-346d-4e95-a873-783037ed98bc +# ╟─4b17f7e6-26b0-425a-9f53-ccf1689639fe +# ╟─6736542c-5378-480e-a56b-65956b416225 +# ╠═ba56adb1-9405-40d5-be48-4273b42ab145 +# ╟─1c311b6f-6b18-4170-b500-33a8e4d3cb2d +# ╠═50220e1f-8e42-44da-be03-08c11df967f0 +# ╠═449286b0-0210-4c6b-b28f-fca87b52d674 +# ╠═f720eac9-cb29-4eef-ac94-471395941c0f +# ╟─d75246d4-e03b-4684-be7d-4bcfb61ed7ef +# ╠═8a9115eb-4044-4cab-a7db-39b5dd86c70d +# ╟─48c9f616-d298-40da-b917-225abd39b3d9 +# ╟─35f158c1-858d-4e4d-ac3d-bf4807dad9a0 +# ╠═8b6534d6-776b-4285-8498-a9b34051facc +# ╟─a6972aef-63ad-401c-acf5-6d59f9fc6698 +# ╟─73e35289-6dc0-4e2e-83eb-b56f83cdbbbf +# ╟─47bd729c-4851-42f7-a03f-6ceacd3c717e +# ╠═f34bb7ac-1ed8-4dd9-b0b9-49bd6e0e1d71 +# ╟─a4dc130a-87ef-4b77-9b62-07f64917e34d +# ╟─e55404ab-6762-4f39-bb42-9c195334a214 +# ╠═80e7f6b8-7592-48a3-8587-f1953d1bfcd8 +# ╟─a1ca7d0e-639c-42d4-be09-5c61a2008f29 +# ╠═30399b9a-1d77-4140-9ad3-5eed636a5b99 +# ╠═e1b8e4ba-c1f1-48d5-87a2-edce19c9fe7a +# ╠═a943e0fe-1376-4ef1-9c45-25c6f95e3b96 +# ╟─72e065d4-7b1b-4f46-b373-935be8d801fc +# ╟─0b2ffd6f-01cd-4f11-9062-d38b3c13a5b1 +# ╠═590b1006-0e37-4668-9b4a-3588fab45696 +# ╟─8ff28a2e-185d-4dca-ad3b-a0b1507646d6 +# ╠═14d24cbd-3259-41bb-9013-b4fe25a3be4c +# ╟─6c134677-3ec1-4e0a-88b5-01341a096675 +# ╠═7a81f4a0-8f7c-4e05-9c3f-2438eab9b691 +# ╠═ac80099b-d8f0-4eba-809d-d482bd354d35 +# ╟─b58f2c24-e0ea-48a8-b0b7-d0faf9642340 +# ╠═55eba435-6ca4-4f4f-b08a-be700d5bda91 +# ╟─5d386b00-93b5-4a88-b4e3-e5c3eebd6dd5 +# ╟─6f0e91d0-6b99-4cf1-8145-523589a21e89 +# ╠═209742ca-36bb-42a5-bf8a-291a40c47757 +# ╟─07efb21a-0dac-46d5-b9e1-8f4757c6cedf +# ╠═a93a7eeb-aad1-49f3-a05f-1512adb08b6b +# ╟─ccc66805-efce-4f25-b76b-7cff23901ba4 +# ╠═57ef7d03-a6dc-40da-858d-5f9b9be613cd +# ╠═62bd2e58-343a-4155-a65e-cf326d0975f4 +# ╠═4d6cba88-be68-412d-aeec-28300c26a9da +# ╟─4bc97af7-debc-4170-a2d5-4e6c928e5183 +# ╟─a0033986-4946-45a2-a53e-29c38f35ed5c +# ╠═a570ebab-64de-4934-b887-77a8e2fb42e8 +# ╟─feee9cc1-ebe2-4c77-a11e-53ca00864ccf +# ╠═73e86176-f314-463c-8821-51c0f3cc1a56 +# ╟─c17c4e00-8688-4f9c-b0ae-741d70601aba +# ╠═f751b962-1810-429c-b34b-658b63fa9ba0 +# ╠═0fd058cc-9bd8-4e12-b5dd-79818519165b +# ╟─57c95382-69cc-47e7-aa94-4767136bb23b +# ╠═4e07c39b-502d-4b38-ae7f-f103cb4bae16 +# ╟─29170e2a-9916-438e-92ca-9f4783397b5e +# ╟─7ff9fe52-156b-4a92-9058-781670de3abb +# ╠═0c047043-3284-422a-9c88-2f4f4c170edf +# ╠═78a608a4-0ba7-400e-abb9-a9285c60681c +# ╠═19c362cb-2764-41c9-a571-2e8e2bfcde93 +# ╠═93db47b2-34e8-43b4-beac-b5620fd444e7 +# ╠═68c71cd2-6cdc-4b80-b6e7-75bfea344295 +# ╠═d6b9eaba-1d43-4e56-8fa1-bb2f87f6fe79 +# ╟─b843ee5e-9618-4b50-93db-77e19b4be366 +# ╟─58014411-128c-41f4-b192-b815a3a8cc60 +# ╠═ecf4b951-9b5f-441a-89f5-b0ccd040ba02 +# ╟─b92bf532-5610-4ce8-b469-154b20cc5956 +# ╠═e629fbd1-a7c7-4dc3-8b7d-bdca380fe5ad +# ╟─a48df980-4847-418e-813d-1875d032ffd9 +# ╠═348e7454-c6dc-4d1e-b18b-6b34bc2fc0fc +# ╠═f71f0cf4-b63e-452c-b971-f30d7503f1e5 +# ╟─7346a003-aa05-41e3-a420-c97c1994cf3c +# ╠═20a6f165-449a-42cc-9565-f342a7535422 +# ╟─137bde23-76f2-4ebf-8bc2-ea8640001436 +# ╟─4aa71200-006b-4a15-ae75-67e36aa81522 +# ╟─cdab3079-04b0-4a44-b770-468c20e321e4 +# ╠═cf1a144e-09e9-42a3-b2a3-b8676a200a39 +# ╟─d69ed5dd-80cd-427e-9245-e42576a4688d +# ╠═8b429656-1c57-4278-9633-71311f400ed8 +# ╟─b4300e8a-8052-419b-98c8-0508ebee2393 +# ╠═2c6ae74c-2da4-4867-ad8a-f4e835101d63 +# ╟─c6a5d453-d610-4f65-847c-c878dd41726c +# ╠═fff17cf7-173d-4f64-94a9-4bf46acc882d +# ╟─eee55784-a641-445e-be75-0b19e2a94754 +# ╠═7844e4f5-3c7d-4b4b-beee-970c998c67a6 +# ╟─c81d0140-3f4e-4eb4-8a77-1f48c5e0ecbf +# ╠═7456455b-4f31-488f-990f-6ce534038e08 +# ╟─b10c2ce4-d363-429c-a64c-ec29652137a5 +# ╠═23b629a6-6866-416a-a768-9617ce6301db +# ╠═8788082d-f5d1-4385-8037-a0d360a841c7 +# ╠═03a4fa85-08db-46d4-bb53-c0ccea90a211 +# ╟─8f5e1413-227c-44fa-bb2d-3653cbc27e38 +# ╟─8a7f7aab-878e-41b5-b9da-d06747df042e +# ╠═30602ff1-041b-4fca-bf8e-55ff57df9e37 +# ╟─881011be-6434-416f-915b-3333e8dea32f +# ╠═5602b88a-07f8-438b-994c-65f11e17a0ba +# ╟─25be4255-0888-4ecd-a2fd-d66402c5cb50 +# ╠═36e8a174-d526-45ee-b3c6-88d698ad5d5f +# ╠═7594147d-b3da-4e0d-896d-41baacb6d7be +# ╟─8e047be9-f0f0-4a75-91b5-c523f55f8c67 +# ╠═1a9587aa-2356-48df-abcc-2ce874fa5d24 +# ╟─785d500b-f8ea-446a-9952-2a5fd5d83d24 +# ╟─e754826a-7411-4072-b0dc-a4bad7a15f98 +# ╠═6e85c08e-9a97-4897-9c81-89517f55e254 +# ╟─e5081280-d226-4834-8932-c89becd8313c +# ╠═c739a908-2353-4e7a-8fbd-f640dc8cabe0 +# ╟─1d0383ad-54d6-4ff2-8555-def83bfff0e6 +# ╠═cd1cf2f8-9f7f-4ed4-9cb7-1a6efee68ab4 +# ╟─aba74ee0-0163-4e15-8b49-d8dcad4839f7 +# ╠═0eda4142-1aaf-4e17-bd78-857e13e94acd +# ╟─50629194-98ed-4d45-86a2-95ac22daac29 +# ╠═9c239fc6-275c-4d64-9fa2-6fd57295b757 +# ╟─dede17f2-655d-4871-b6de-5a32804947dd +# ╠═e8c3f042-6058-4040-a67e-18563c04ee93 +# ╠═665f4d03-3521-475a-a195-f861fd26bb69 +# ╠═13775d58-ac61-431c-a6c9-447c1eec7942 +# ╟─bc92f996-b626-4965-a286-d2c848eb1a21 +# ╟─e2fde8e9-1f87-4ffe-8dae-2794664bfaa4 +# ╠═23fd9fd9-a13c-4c56-a0b4-daec6f1d2cd8 +# ╟─6f414d15-af0a-452a-98a1-dc0b7e54d617 +# ╠═57ee8a12-24df-4598-935c-f5e259b504cb +# ╟─48bc085c-9ce6-4752-a5a9-a814f803f571 +# ╠═5b9b2e5b-9deb-4ff6-a923-b15b8b08f0c9 +# ╠═66ee9655-a006-4c0f-b1f1-5576efa8f896 +# ╟─52d7975a-5346-447f-9aad-4ecd11b6460a +# ╠═e96efd6a-a666-4120-8480-9423e5d82ae1 +# ╟─f0b4772d-a72b-44e0-a3a1-ba9ad4c4dfeb +# ╠═7e98a771-52bb-484e-82ab-2e42e7cb4053 diff --git a/src/exercises/calib_irrigation.jl b/src/exercises/calib_irrigation.jl new file mode 100644 index 00000000..29028fe9 --- /dev/null +++ b/src/exercises/calib_irrigation.jl @@ -0,0 +1,316 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "33" +#> title = "6. Calibration irrigation" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Calibration irrigation" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 55675f3d-2fae-4a97-a0a0-ead29a6352e6 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 2b010e5c-1121-11ef-16fe-a5e3317122e4 +using Markdown, InteractiveUtils + +# ╔═╡ 4947b0fd-13be-4f6a-b605-ed35b509d7ff +using ModelingToolkit, OrdinaryDiffEq + +# ╔═╡ 61d14819-ba44-40fe-95a9-9d7b0bf3dc33 +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ f6e77c8d-de11-4b9d-93c6-45bdcfbbbf9b +using StatsPlots, StatsBase, Turing + +# ╔═╡ 9345dd8f-0a60-4aaf-a27f-ef8bf860f495 +using LinearAlgebra, Optim + +# ╔═╡ 55d5400d-1777-4918-a030-b94cb9a59f63 +md" +# Exercise: Irrigation experiment - Calibration +" + +# ╔═╡ 8f1afdec-b78d-4aba-a74f-cd3e4b35fab1 +md""" +In one of the previous practica we were introduced to an irrigation experiment carried out on a soil column consisting of two layers of soil, each with specific soil characteristics. However, here the volume of water per unit of time, $r$, irrigated evenly over the soil column, will be kept constant at $5\;mm\,h^{-1}$ in these new experiments. + +The water falls on the upper layer and percolates to the lower layer. The relative moisture content in both layers (i.e., relative to their residual moisture contents) is denoted by $S_1$ and $S_2$. + +A model description of the relative moisture content in both soil layers is given by: + +$$\begin{align} +\frac{dS_1}{dt} &= r\left(1-\cfrac{S_{1,res}}{S_{max}}\right) - \cfrac{r}{S_{max}}S_1 - \cfrac{k}{S_{max}}S_1 \\ +\frac{dS_2}{dt} &= \cfrac{k}{S_{max}}S_1 - v \,S_2^2 +\end{align}$$ + +where $v = 10^{-3}\;h^{-1}\,mm^{-1}$ and $S_{1,res}=10 \;mm$. Previously, we also assumed $k = 3\;mm\,h^{-1}$ and $S_{max} = 150\;mm$. +""" + +# ╔═╡ 9f6ad49c-cfbe-46e2-a2dd-c01bb67eeb61 +# @variables missing + +# ╔═╡ fb2c7db4-e52f-45df-afa0-aeb1db78c849 +# @parameters missing + +# ╔═╡ c1773f17-825f-414f-9761-5744b34f1b71 +# change_S1 = missing + +# ╔═╡ 6e2165d4-ef72-4c37-bac0-2dfac417a192 +# change_S2 = missing + +# ╔═╡ 59654ad7-f42c-443f-bb34-6674db72606f +# @mtkbuild sys_irrigation = missing + +# ╔═╡ e5d7520d-fd8c-48c0-bd36-826766212217 +md""" +In order to have better estimates the parameters $k$ and $S_{max}$, two experiments were conducted, each with a different initial condition: + +1. Starting from zero relative moisture content in both soil layers. +2. Starting from a relative moisture content of $140\;mm$ in the top layer, and $135\;mm$ in the bottom layer. + +The measurement data consist of measurements of the relative moisture contents $S_1$ and $S_2$ measured at intervals of $10\;h$ within a timespan of $150\;h$. +""" + +# ╔═╡ 73c9b5fb-4f56-4bde-beb4-387651409c1b +md""" +The measurement data for the 1st experiment are: +""" + +# ╔═╡ 9f94c63e-628f-4ff3-ad29-0f90d32dfcb1 +S1_meas1 = [0.2, 35.94, 52.49, 66.86, 60.66, 67.81, 73.22, 71.31, 72.94, 64.08, 70.11, 68.53, 70.54, 63.63, 67.39, 62.84] + +# ╔═╡ 3d406f41-62fa-4a31-8e6b-a621a06a118c +S2_meas1 = [0.63, 6.2, 17.67, 22.96, 35.41, 44.08, 43.5, 53.34, 47.57, 47.77, 43.96, 52.22, 46.67, 46.74, 46.46, 39.92] + +# ╔═╡ 68b6158a-a918-4809-bf81-b554bc70c6d0 +md""" +The measurement data for the 2nd experiment are: +""" + +# ╔═╡ 620729c8-62c2-4f0c-8684-713033a208bd +S1_meas2 = [137.96, 106.15, 90.15, 84.64, 76.15, 75.73, 73.32, 68.48, 70.06, 69.36, 70.91, 72.13, 76.25, 74.34, 74.93, 71.58] + +# ╔═╡ 5b83a98c-9c2b-459c-9d7f-7cc75d3bf70e +S2_meas2 = [124.08, 80.14, 60.15, 50.12, 49.66, 47.78, 46.56, 48.41, 42.7, 43.72, 49.03, 51.91, 48.24, 46.14, 51.22, 43.78] + +# ╔═╡ 9c568f28-8985-4a4a-a7aa-0010bbe37dc8 +md""" +For both experiments: +""" + +# ╔═╡ 0dc6fa2c-1eb2-4877-9155-dc7ea6cf6f18 +t_meas = 0:10:150 + +# ╔═╡ a4160054-3dfe-4595-81cb-94db4dd2fe20 +md""" +We can make a scatter plot of the measured data for both $S_1$ and $S_2$ for the 1st and 2nd experiments in the following way: +""" + +# ╔═╡ cef4b9a8-b5bf-4a2d-8a8f-5d8f85534859 +begin + scatter(t_meas, S1_meas1, label="S1 meas", color=:blue, title="Experiment 1") + scatter!(t_meas, S2_meas1, label="S2 meas", color=:red, ylims=(0, 150)) +end + +# ╔═╡ fc2cabd7-e778-4211-bf87-b5c11ca054c9 +begin + scatter(t_meas, S1_meas2, label="S1 meas", color=:blue, title="Experiment 2") + scatter!(t_meas, S2_meas2, label="S2 meas", color=:red, ylims=(0, 150)) +end + +# ╔═╡ c0b2db7b-0632-4008-9cff-d5fbf3e59807 +md""" +Calibrate the parameter values for $k$ and $S_{max}$ using the aforementioned measurement data for $S_1$ and $S_2$ in a timespan of $[0, 150]\,h$. Take the values from above as initial values for $k$ and $S_{max}$. +""" + +# ╔═╡ a65a0997-9945-436e-925b-8fc02055f61a +# tspan = missing + +# ╔═╡ 923d04ce-b4d2-44b0-afff-7062c4628ad0 +md""" +Declare the Turing model. Make sure you take both experiments into account for optimizing $k$ and $S_{max}$. Based on literature, you can assume that the value of $S_{max}$ lies somewhere between 100 and 200 mm. +""" + +# ╔═╡ 481eb8b9-5de2-4f68-b06a-ec18e054c9f5 +# @model function irrigation_inference(t_meas) +# σ_S1 ~ missing +# σ_S2 ~ missing +# k ~ missing +# Smax ~ missing +# parms = missing +# # For experiment 1: +# u01 = missing +# oprob1 = missing +# osol1 = missing +# S1_ex1 ~ missing +# S2_ex1 ~ missing +# # For experiment 2: +# u02 = missing +# oprob2 = missing +# osol2 = missing +# S1_ex2 ~ missing +# S2_ex2 ~ missing +# end + +# ╔═╡ df933ae8-1f51-4467-93a7-33f153e5e4f8 +md""" +Instantiate the model and condition it with the measurements of $S_1$ and $S_2$ from both experiments: +""" + +# ╔═╡ 0e2aa675-9e09-4e06-b5f8-118707ee652a +# irrigation_inf = missing + +# ╔═╡ f7f47956-7c3b-44cc-bff7-fb7d32af874a +md""" +Optimize the priors ($\sigma_{S1}$, $\sigma_{S2}$, $k$ and $S_{max}$). Do this with `MLE` method and Nelder-Mead. Store the optimization results in `results_mle`. +""" + +# ╔═╡ 8c254d5a-225b-4772-9fdd-e9f700495fbd +# results_mle = missing + +# ╔═╡ f15a1df5-047a-4f46-9419-8492ac1248e0 +md""" +Visualize a summary of the optimized parameters. Beware that this may take a lot of time... +""" + +# ╔═╡ 00d944e4-2c88-4a5d-b809-69f435df4684 +# missing + +# ╔═╡ 89eb31ef-b24f-44c8-bbe5-19101d859937 +md""" +Get the optimized values and assign them to `k_opt` and `Smax_opt`. +""" + +# ╔═╡ 92daa779-3373-40c0-b308-23e75e6674b6 +# k_opt = missing + +# ╔═╡ 35ab6ee5-fcd7-4dcc-9909-cc918fb1fe80 +# Smax_opt = missing + +# ╔═╡ 4026773f-ac5b-433e-bd9d-2122242861fd +md""" +Make plots of $S_1$ and $S_2$ for both experiments simulated with the optimized parameter values. +""" + +# ╔═╡ 8aa60652-eb9f-4dd3-ab06-0ce3dd261fe6 +md""" +Set up parameter values with optimized parameter values: +""" + +# ╔═╡ 97d53e48-590a-485b-bcf3-edc6a6124faf +# params_opt = missing + +# ╔═╡ dfd2ac98-5cdc-4627-b6cf-71b33c0ff0d4 +md""" +Plot the simulation results $S_1$ and $S_2$ for the 1st experiment together with the corresponding measured data. Therefore initialize a vector `u01` with initial conditions for the 1st experiment. +""" + +# ╔═╡ 95ace332-52c0-46c3-ae28-d038320ed2c8 +# u01 = missing + +# ╔═╡ 6ae63a13-d5ae-4dfb-b88d-be295b11a472 +# oprob1_opt = missing + +# ╔═╡ bc6505ca-a61d-467f-afe6-47792a510ad5 +# osol1_opt = missing + +# ╔═╡ 67e423ea-e941-45bf-af4f-3fdecb648fbc +# begin +# missing +# missing +# missing +# end + +# ╔═╡ 8c7e0c75-01d2-4e21-a6cf-7ad70e0c6aae +md""" +Plot the simulation results $S_1$ and $S_2$ for the 1st experiment together with the corresponding measured data. Therefore initialize a vector `u02` with initial conditions for the 2nd experiment. +""" + +# ╔═╡ a7040b8e-c240-415b-8a9a-4a1a137398d4 +# u02 = missing + +# ╔═╡ fe8f4961-68bd-42dc-a3f5-6692e918e241 +# oprob2_opt = missing + +# ╔═╡ 7f280230-7846-4529-a2ff-a81a2b9480bf +# osol2_opt = missing + +# ╔═╡ ad9818a9-ccbe-4645-8b91-0c3fa773632a +# begin +# missing +# missing +# missing +# end + +# ╔═╡ 3c243670-2ba7-4396-8c6d-084726636741 +md""" +!!! question + Do your simulations fit well the measurements? +""" + +# ╔═╡ 1aeb0d86-b276-4bd6-9811-faf4a297ae6f +md"- Answer: missing" + +# ╔═╡ Cell order: +# ╠═2b010e5c-1121-11ef-16fe-a5e3317122e4 +# ╠═55675f3d-2fae-4a97-a0a0-ead29a6352e6 +# ╠═4947b0fd-13be-4f6a-b605-ed35b509d7ff +# ╠═61d14819-ba44-40fe-95a9-9d7b0bf3dc33 +# ╠═f6e77c8d-de11-4b9d-93c6-45bdcfbbbf9b +# ╠═9345dd8f-0a60-4aaf-a27f-ef8bf860f495 +# ╟─55d5400d-1777-4918-a030-b94cb9a59f63 +# ╟─8f1afdec-b78d-4aba-a74f-cd3e4b35fab1 +# ╠═9f6ad49c-cfbe-46e2-a2dd-c01bb67eeb61 +# ╠═fb2c7db4-e52f-45df-afa0-aeb1db78c849 +# ╠═c1773f17-825f-414f-9761-5744b34f1b71 +# ╠═6e2165d4-ef72-4c37-bac0-2dfac417a192 +# ╠═59654ad7-f42c-443f-bb34-6674db72606f +# ╟─e5d7520d-fd8c-48c0-bd36-826766212217 +# ╟─73c9b5fb-4f56-4bde-beb4-387651409c1b +# ╠═9f94c63e-628f-4ff3-ad29-0f90d32dfcb1 +# ╠═3d406f41-62fa-4a31-8e6b-a621a06a118c +# ╟─68b6158a-a918-4809-bf81-b554bc70c6d0 +# ╠═620729c8-62c2-4f0c-8684-713033a208bd +# ╠═5b83a98c-9c2b-459c-9d7f-7cc75d3bf70e +# ╟─9c568f28-8985-4a4a-a7aa-0010bbe37dc8 +# ╠═0dc6fa2c-1eb2-4877-9155-dc7ea6cf6f18 +# ╟─a4160054-3dfe-4595-81cb-94db4dd2fe20 +# ╠═cef4b9a8-b5bf-4a2d-8a8f-5d8f85534859 +# ╠═fc2cabd7-e778-4211-bf87-b5c11ca054c9 +# ╟─c0b2db7b-0632-4008-9cff-d5fbf3e59807 +# ╠═a65a0997-9945-436e-925b-8fc02055f61a +# ╟─923d04ce-b4d2-44b0-afff-7062c4628ad0 +# ╠═481eb8b9-5de2-4f68-b06a-ec18e054c9f5 +# ╟─df933ae8-1f51-4467-93a7-33f153e5e4f8 +# ╠═0e2aa675-9e09-4e06-b5f8-118707ee652a +# ╟─f7f47956-7c3b-44cc-bff7-fb7d32af874a +# ╠═8c254d5a-225b-4772-9fdd-e9f700495fbd +# ╟─f15a1df5-047a-4f46-9419-8492ac1248e0 +# ╠═00d944e4-2c88-4a5d-b809-69f435df4684 +# ╟─89eb31ef-b24f-44c8-bbe5-19101d859937 +# ╠═92daa779-3373-40c0-b308-23e75e6674b6 +# ╠═35ab6ee5-fcd7-4dcc-9909-cc918fb1fe80 +# ╟─4026773f-ac5b-433e-bd9d-2122242861fd +# ╟─8aa60652-eb9f-4dd3-ab06-0ce3dd261fe6 +# ╠═97d53e48-590a-485b-bcf3-edc6a6124faf +# ╟─dfd2ac98-5cdc-4627-b6cf-71b33c0ff0d4 +# ╠═95ace332-52c0-46c3-ae28-d038320ed2c8 +# ╠═6ae63a13-d5ae-4dfb-b88d-be295b11a472 +# ╠═bc6505ca-a61d-467f-afe6-47792a510ad5 +# ╠═67e423ea-e941-45bf-af4f-3fdecb648fbc +# ╟─8c7e0c75-01d2-4e21-a6cf-7ad70e0c6aae +# ╠═a7040b8e-c240-415b-8a9a-4a1a137398d4 +# ╠═fe8f4961-68bd-42dc-a3f5-6692e918e241 +# ╠═7f280230-7846-4529-a2ff-a81a2b9480bf +# ╠═ad9818a9-ccbe-4645-8b91-0c3fa773632a +# ╟─3c243670-2ba7-4396-8c6d-084726636741 +# ╠═1aeb0d86-b276-4bd6-9811-faf4a297ae6f diff --git a/src/exercises/model_selection_intro.jl b/src/exercises/model_selection_intro.jl new file mode 100644 index 00000000..e5743164 --- /dev/null +++ b/src/exercises/model_selection_intro.jl @@ -0,0 +1,1354 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "42" +#> title = "8. Model selection intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Model selection intro" + +using Markdown +using InteractiveUtils + +# ╔═╡ e1e7bc8e-7264-4cbc-98d2-aa73679fa2df +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 5f4fea06-0632-11ef-102e-21f5606d2056 +using Markdown + +# ╔═╡ 489b5399-fe4c-481a-834f-0101bbe28cea +using InteractiveUtils + +# ╔═╡ ab4a1911-461e-4878-9258-931fc2f1ea06 +using PlutoUI; TableOfContents() + +# ╔═╡ cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 +using OrdinaryDiffEq + +# ╔═╡ 22489bd4-ab64-4bf9-ad03-5372ea273935 +using Catalyst + +# ╔═╡ 4ad72a6a-f541-4d0a-8392-56369e29ac96 +using Turing, StatsPlots, StatsBase + +# ╔═╡ 70590d28-058a-44df-8cf6-092d8c87438c +using LinearAlgebra, Optim + +# ╔═╡ 427b509f-d08e-4d93-99ba-a79f9c244b28 +md""" +# Introduction to model selection +""" + +# ╔═╡ 49e7085c-3691-4182-9630-68dc9371ad18 +md""" +## Goal of this practicum +""" + +# ╔═╡ 84caa99f-32ab-433c-b620-f592d329d18d +md""" +In previous practicals, we have developed models to study phenomena and predict future behavior. We have also estimated the parameters associated with these models and we have also analyzed the sensitivity of the model predictions to changes in these parameters. We found that the mathematical structure of different models determines the sensitivity to errors in the parameters and errors in the model itself and we determined how these errors propagate through the model, allowing us to quantify the uncertainty in the predictions. +""" + +# ╔═╡ 7cc1d1ce-a34a-4432-953d-4eb2df92696f +md""" +In this practical, we investigate how to make an objective choice between different candidate models by weighing the complexity of the models against the fit to the experimental data and the quality of the prediction. We will use two information criteria often used in practice to balance model quality and complexity: the Akaike information criterion (AIC) and the Bayesian information criterion (BIC). +""" + +# ╔═╡ dcb51063-2b3f-4de9-90bc-0048e82cd4ab +md""" +In the Akaike information criterion, the fit or quality of the model (likelihood $L$) is compared against the number of model parameters ($k$), thus giving a measure of the balance between complexity and quality of the fit: + +$AIC = 2k - 2\,\log(L)$ +""" + +# ╔═╡ 6690a844-8fd5-4ea1-9e62-a834bd454efb +md""" +The Bayesian information criterion gives similar information, but penalizes complexity more heavily: + +$BIC = k\,\log(n) - 2\,\log(L)$ + +where $n$ is the number of data points considered. +""" + +# ╔═╡ 61e5bead-6fc3-4c07-b2b4-832ce7a69198 +md""" +In this notebook we will compare the different grass growth models and judge the quality of their fit to the calibration data set in order to select the simplest or least complex model that best represents the system. +""" + +# ╔═╡ 04e95855-7c4a-4d2c-b836-c5dde291adad +md""" +## Grass growth models +""" + +# ╔═╡ 4673bfdf-8f4a-42bd-a026-21a66d800f2b +md""" +In this notebook, three different models will be used, each modelling the yield of grass in a grassland: + +- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ +- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ +- Gompertz growth model: $\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$ + +with output $W$ the grass yield, and $W_f$, $\mu$ and $D$ parameters. The table below shows some typical values for the parameters: + +| | $\mu$ | $W_f$ | $D$ | +|:----------- |:----------:|:-----------:|:------------:| +| Logistic | 0.07 | 10.0 | | +| Exponential | 0.02 | 10.0 | | +| Gompertz | 0.09 | | 0.04 | + +We will use an initial condition of $W_0 = 2.0$ for each and a simulation time of $100$ days. +""" + +# ╔═╡ 6e5f494e-ecfb-467b-a0f1-c85427da8215 +md""" +In each of the three models we will use the following timespan: +""" + +# ╔═╡ 0b3d35bb-c5b0-44b7-94b3-06fa571d339e +tspan = (0.0, 100.0) # this will be the same for the three models + +# ╔═╡ d1c5a659-70c4-4768-9542-a2988ea39c43 +md""" +### The calibration data +""" + +# ╔═╡ 583ed493-27e7-4fa7-b519-75cc71468fc6 +md""" +Assume that the measured grass yields (of a certain plant type) over time are the following: +""" + +# ╔═╡ d4ca6986-df24-48dd-8637-0974e0b16f2c +W_meas = [1.87, 2.45, 3.72, 4.32, 5.28, 7.01, 6.83, 8.62, 9.45, 10.31, 10.56, 11.72, 11.05, 11.53, 11.39, 11.7, 11.15, 11.49, 12.04, 11.95, 11.68] + +# ╔═╡ 8215b6f0-017e-4a73-b4bf-aa61b8f3ca6f +md""" +They have been measured at the following corresponding time instances: +""" + +# ╔═╡ 2ff5f464-38d7-4a90-ad31-d19d412781fc +t_meas = 0:5:100 + +# ╔═╡ b1d02f3c-f528-4297-a0c1-993cfe0d7c17 +md""" +We can make a scatter plot of this data (including a title, a legend label, an X-axis label, X- and Y-axis limits) in the following way: +""" + +# ╔═╡ 1499c727-ff19-4ef0-b467-bc71c1802979 +scatter(t_meas, W_meas, title="Grass growth data", + label="Yield", + xlabel="t", + xlims=(0, 100), + ylims=(0, 14)) + +# ╔═╡ c692b627-8743-48f6-bb26-b7d9b3d3b9c8 +md""" +### Logistic growth +""" + +# ╔═╡ 676119ce-f4fe-41f9-8121-b2c21f0dd28c +md""" + +$$\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$$\ +$W_0$ = 2.0, $\mu$ = 0.07 and $W_f$ = 10.0\ +We will start by modelling our system and simulating using the aforementioned parameters values, initial condition and timespan in a way that we are familiar with. +""" + +# ╔═╡ 5492210b-6ee6-4baf-b37e-d21358cdeb60 +md""" +Implementation of the system: +""" + +# ╔═╡ 18eece66-46fa-4458-aaed-f4c8fa002c20 +growth_log = @reaction_network begin + @species W(t)=2.0 # default initial condition + @parameters μ=0.07 Wf=10.0 # default parameter values + μ*(1-W/Wf), W --> 2W +end + +# ╔═╡ 8bb0b24b-6cce-49cd-a625-5f375b92d9b7 +md""" +Convert the *reaction model* to check that we work with the correct differential equation: +""" + +# ╔═╡ ba181db8-d176-4b83-9168-d5939ffe9661 +osys_log = convert(ODESystem, growth_log) + +# ╔═╡ cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 +md""" +Setting initial conditions, timespan and parameter values: +""" + +# ╔═╡ cbb2ca49-b019-495b-9310-83fcc00cad26 +u0_log = [:W => 2.0] + +# ╔═╡ be565a3c-31b6-4df1-b73b-08f308a8c09b +md""" +For the sake of clarity, we will use the variables `μ_log` and `Wf_log` to store the parameter values. +""" + +# ╔═╡ f62806d1-77e1-470b-9711-33a924c788cc +μ_log = 0.07 + +# ╔═╡ 546ed163-26a7-4235-982f-7568ed609488 +Wf_log = 10.0 + +# ╔═╡ 0da53fa2-5a42-46e6-8bd8-45d6aa903d46 +params_log = [:μ => μ_log, :Wf => Wf_log] + +# ╔═╡ b1298f40-4696-49d0-ac94-896e0cdbc996 +md""" +Creating and solving the ODEProblem and plotting results: +""" + +# ╔═╡ 270647d2-1371-4272-8bc1-3a6ad77bc716 +oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log); +# Also possible here if initial conditions and parameter values are defined in the catalyst model: +# oprob_log = ODEProblem(growth_mod_log, [], tspan, []) + +# ╔═╡ ac235d86-1d93-4944-aa89-1b4fd38f0e6e +osol_log = solve(oprob_log, Tsit5(), saveat=0.5) + +# ╔═╡ 3e13efa1-9bc6-456f-8e62-ecd3165e2a65 +begin + plot(osol_log, label="model", lw=2, ylabel="W") + scatter!(t_meas, W_meas, title="Logistic growth model", label="data", xlabel="t", + xlims=(0, 100), ylims=(0, 14)) +end + +# ╔═╡ e2955af0-edf0-4702-9a8f-478141ffdc3b +md""" +We can see that the model does not predict well the data set for the considered parameter values. Thus we will use the data to both calibrate the model parameters and assess the quality of the fit. +""" + +# ╔═╡ c73669c4-d7af-4877-b32d-6499f339e27a +md""" +### Parameter estimation +""" + +# ╔═╡ 28e4a44f-e907-42ce-a748-6d21b60f0e33 +md""" +We declare our Turing model function: +""" + +# ╔═╡ 169a67ff-55fb-4d1d-b98b-126f4af47e77 +@model function growth_log_fun(t_meas) + σ_W ~ InverseGamma() + W0 ~ LogNormal() + μ ~ LogNormal() + Wf ~ LogNormal() + u0_log = [:W => W0] + params_log = [:μ => μ, :Wf => Wf] + oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log) + osol_log = solve(oprob_log, Tsit5(), saveat=t_meas) + W_s ~ MvNormal(osol_log[:W], σ_W^2 * I) + return osol_log # optionally, to be used with MCMC +end + +# ╔═╡ aa3e553d-2731-42d6-b0e6-1821e4d7f4d4 +md""" +We now provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: +""" + +# ╔═╡ 51426716-03b8-4d54-9064-3943df282fa4 +growth_log_cond_mod = growth_log_fun(t_meas) | (W_s = W_meas,) + +# ╔═╡ f40873af-3ef9-420b-b0a8-ed9f56b17047 +md""" +We are now ready to optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). This is done by calling the `optimize` function, providing the previously created object `growth_log_inf`, the method for estimating the parameters and (optionally) an algorithm (default: Nelder-Mead) to implement the method. +""" + +# ╔═╡ 4536c6b7-f3bc-42e8-b044-0633f00b56bb +md""" +We will use the MLE (Maximum Likelihood Estimation) method here and store the optimization results in `results_log_mle`. If you get an error the first time, try running the optimization again. +""" + +# ╔═╡ bc55fb07-1d84-4927-80d2-c1a012409400 +results_log_mle = optimize(growth_log_cond_mod, MLE(), NelderMead()) + +# ╔═╡ c224f1b8-e5a1-4379-ae50-981ae89f9eaa +md""" +You can visualize a summary of the optimized parameters by piping them to `coeftable`: +""" + +# ╔═╡ 4fe2b4cd-d0b0-489a-a867-14c58637327b +coeftable(results_log_mle) + +# ╔═╡ 6cf5e2f2-344c-4a7a-8901-5a0ee9cb8689 +md""" +You can obtain the actual optimized values using the function `coef` on the results object in conjunction by calling the parameters by name preceded by a colon. Here we assign the optimized parameter values to some suitable variable names: +""" + +# ╔═╡ 551859cb-9cd1-4977-a528-b96423e83504 +W0_opt_log = coef(results_log_mle)[:W0] + +# ╔═╡ 955524a6-1a53-4d2d-ad16-a5a3a2a39b80 +μ_opt_log = coef(results_log_mle)[:μ] + +# ╔═╡ 118a79cc-afaf-4a99-8486-8e0e01fda40a +Wf_opt_log = coef(results_log_mle)[:Wf] + +# ╔═╡ 8536cc9e-6cd4-4e38-9988-e45b06b75ea4 +md""" +Now we can make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ 25db8d7b-e467-4a2f-b3f3-0ad7df64988a +md""" +Setting up initial condition with optimized initial condition: +""" + +# ╔═╡ 01241971-cc9a-42d6-8e52-c840b91e6431 +u0_opt_log = [:W => W0_opt_log] + +# ╔═╡ e2db7492-4d68-4045-91b6-dca3aef8b514 +md""" +Setting up parameter values with optimized parameter values: +""" + +# ╔═╡ 7a890da6-7547-4854-b877-a5d935a9f2dd +params_opt_log = [:μ => μ_opt_log, :Wf => Wf_opt_log] + +# ╔═╡ c95bcde6-ffb9-4ce6-9dfb-38c79cc2e4b8 +md""" +Next, we create an ODEProblem and solve it: +""" + +# ╔═╡ bf90a66a-8505-4b33-b821-d708a3b7f8b0 +oprob_opt_log = ODEProblem(growth_log, u0_opt_log, tspan, params_opt_log) + +# ╔═╡ 7af67f91-58e2-429d-a88f-b27a0258a805 +osol_opt_log = solve(oprob_opt_log, Tsit5(), saveat=0.5); + +# ╔═╡ 3ee0d4de-fdf5-40f0-a849-63b0759eb44b +md""" +Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ 2c048fc0-5908-4f01-a9a5-480e12e098cc +begin + plot(osol_opt_log, label="model", xlabel="t", ylabel="W", + xlims=(0, 100), ylims=(0, 14), lw=2.0, title="Calibrated logistic growth model") + scatter!(t_meas, W_meas, label="data") +end + +# ╔═╡ 19ed4aa9-776e-4a80-b180-65d32f3f9f26 +md""" +We can extract from the calibration results the log-likelihood or quality of the fit: +""" + +# ╔═╡ 12a11f90-fa55-47f2-a75f-853cd54cbeab +L_log = results_log_mle.lp + +# ╔═╡ f6b44166-3ba9-4770-80db-303bd28112cf +md""" +## Model selection criteria +""" + +# ╔═╡ 02037c91-448e-4714-b932-86f4df631907 +md""" +### Akaike information criterion +""" + +# ╔═╡ cfea46a6-636c-4fcd-a04b-714e7a567cb3 +md""" +To calculate the AIC, we can implement a function that uses the information from the calibration: +""" + +# ╔═╡ cb9384a3-96b0-4694-a218-25ebf96187a9 +function AIC(results, measurements) + L = results.lp + k = length(results.values) + n = length(measurements) + return 2k - 2L # L = log-likelihood +end + +# ╔═╡ 73917400-43e0-4b9f-9448-9bbe02daebb9 +md""" +This function uses the results from the calibration, from where we can extract as well the number of calibrated parameters, which includes the estimated prediction error: +""" + +# ╔═╡ afba526d-0f71-4ba5-9a84-3c24804f959a +k_log = length(results_log_mle.values) # alternative: length(coef(results_log_mle)) + +# ╔═╡ 903380ce-1320-464e-a703-1222b4f381b1 +md""" +The AIC will use this to balance the complexity with the quality of the fit. For the logistic model: +""" + +# ╔═╡ 22bd4450-6c6d-4a4d-aa1d-a080d3b06eac +AIC_log = AIC(results_log_mle, W_meas) + +# ╔═╡ 34f8cb7d-7482-4883-87e0-5c68be7ec0e9 +md""" +### Bayesian information criterion +""" + +# ╔═╡ 4d976b86-74d6-4bb5-af17-8a6bd658abe2 +md""" +We can also calculate the BIC in a similar way to the AIC: +""" + +# ╔═╡ ba7974ec-3965-4c92-ab37-08cdf5ab8125 +function BIC(results, measurements) + L = results.lp + k = length(results.values) + n = length(measurements) + return k*log(n) - 2L # L = log-likelihood +end + +# ╔═╡ 5e6837d5-35e9-434c-9337-7c90daec9c33 +md""" +The BIC will additionally use the length of the data set for the complexity penalty term: +""" + +# ╔═╡ b97ca16c-e6c3-4840-a855-1d29140eff59 +n = length(W_meas) + +# ╔═╡ 1af53492-f363-4d39-bfaf-2f668a441725 +BIC_log = BIC(results_log_mle, W_meas) + +# ╔═╡ e176e6ce-33da-4f3b-860e-4db99a931079 +md""" +!!! question + What conclusions can we extract from a comparison of the AIC or BIC for different models? +""" + +# ╔═╡ 770e70d0-8a69-4585-a423-41335d453dcd +md""" +Conclusions: +- Lower values are better. For the same value of $L$, a simpler model would be preferred. +- BIC seems to penalize more complex models than AIC for the same values of $L$ and $k$. +""" + +# ╔═╡ f4f3b1fb-165d-4f77-88f1-8c1a79f3fb42 +md""" +### The posterior model probability +""" + +# ╔═╡ 4a86d852-634c-43f5-90e5-3e675aeb2d69 +md""" +We can use the AIC to compute the posterior probabilities of the different candidate models: + +$P(M_i|D) \propto \exp(-AIC(M_i)/2)$ +""" + +# ╔═╡ 8a43d4b8-1902-4e02-9cd7-c36131ec9528 +md""" +The following function will use the supplied AIC of several models to compute the normalized posterior probability that the model is the "true model", explaining the considered data set: +""" + +# ╔═╡ d149d829-7d3d-4f7a-bdad-b1debb2c5149 +function posterior(AICs) # AICs vector of AIC values + AICmin = minimum(AICs) + posterior = zeros(length(AICs)) + + for i in 1:length(AICs) + posterior[i] = exp((AICmin-AICs[i])/2) + end + + return round.(posterior/sum(posterior); digits=3) # normalized sum +end + +# ╔═╡ e2bec1ab-f161-45b6-a67e-1f4f197ae685 +posterior([AIC_log]) + +# ╔═╡ 5db6b1a5-ed2e-4812-94db-37cfffb46227 +md""" +!!! note + This function will be used to compare the different candidate models (more than one). +""" + +# ╔═╡ bc8d315a-ddfb-4c82-84d0-90a4fb05256c +md""" +### Least squares model fitting +""" + +# ╔═╡ 51b348ef-0270-41d0-bbcb-a27eb10ff5d2 +md""" +The Akaike information criterion can be reformulated in terms of **least squares** if we assume that the model residuals are normally (and independently) distributed with zero mean, giving rise to: + +$AIC = 2k + n \log{\bigg(\frac{SSR}{n}}\bigg)$ + +where $SSR$ is the **squared sum of the model's residuals**. For small data sets, a correction is done: + +$AIC_c = 2k + n \log{\bigg(\frac{SSR}{n}}\bigg) + \frac{2k(k+1)}{n-k-1}$ + +When the number of observations is large enough, the corrected $AIC_c$ and $AIC$ are identical. + +The Bayesian information criterion can also be expressed in terms of the residuals: + +$BIC = k\log(n) + n \log{\bigg(\frac{SSR}{n}}\bigg)$ + +Both criteria are implemented below and can be used to compare the fitness of different models. +""" + +# ╔═╡ 7bf8fa3a-ce98-4f40-8d6c-4e84b79ae1c0 +function AIC_LS(SSR, n, k) + if n > 40 + return 2k + n*log(SSR/n) + else + return 2k + n*log(SSR/n) + 2k*(k+1)/(n-k-1) + end +end + +# ╔═╡ 498e0b9b-17a9-4059-9196-c6c4d86f926e +function BIC_LS(SSR, n, k) + return k*log(n) + n*log(SSR/n) +end + +# ╔═╡ b57131ad-e9d6-4318-bca7-c59c5d8da4c3 +md""" +We can thus obtain the squared sum of residuals from the calibrated model prediction and the data: +""" + +# ╔═╡ f2fe206b-2832-42f6-84bd-20c3838fcb0b +function SSR(y_pred, y_data) + return sum((y_pred - y_data).^2) # squared sum of residuals +end + +# ╔═╡ d51fff9b-17d3-4a0d-aa5e-01d58821a246 +md""" +We can now calculate the SSR and alternative AIC and BIC forms for the logistic model: +""" + +# ╔═╡ 6d95d3b1-d964-4b61-adb6-f44875e23ecb +begin + W_log = solve(oprob_opt_log, Tsit5(), saveat=t_meas)[:W] # model prediction + SSR_log = SSR(W_log, W_meas) + AIC_LS_log = AIC_LS(SSR_log, n, k_log) + BIC_LS_log = BIC_LS(SSR_log, n, k_log) +end; + +# ╔═╡ b1783355-0221-4f2d-a050-932b40920eab +AIC_log, BIC_log + +# ╔═╡ 3c69edb3-ba7d-4ee0-996a-40fd985e57d3 +AIC_LS_log, BIC_LS_log + +# ╔═╡ 982e1de1-0820-42fb-8abb-31f1c99def46 +md""" +!!! note + See the exercises below to apply the different criteria for model selection to the other models. +""" + +# ╔═╡ 79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 +md""" +## Exercises +""" + +# ╔═╡ fa2270d0-2548-409c-a23f-4369d8bce8ec +md""" +### Exercise 1 - Compare the logistic and exponential models +""" + +# ╔═╡ 545a317d-8bca-4b6c-9915-84b0b4ffbed5 +md""" +Calibrate the initial condition and both parameters of the exponential growth model. Use the values mentioned in the Table as initials values for the optimization of the parameters. Then compare the fit to that of the logistic model by plotting both predictions in the same figure. +""" + +# ╔═╡ 794b93c9-0a0a-4e77-b13c-0e2c06a9a0ec +md""" +$$\cfrac{dW}{dt} = \mu \left( W_f - W \right)$$\ +$W_0$ = 2.0, $\mu$ = 0.02 and $W_f$ = 10.0 +""" + +# ╔═╡ cfca0095-81e7-4c88-84bd-773eb5af7aa4 +growth_exp = @reaction_network begin + μ*Wf, 0 --> W + μ, W --> 0 +end + +# ╔═╡ ba9162dd-27f4-42b3-a6b5-ca9531614d5e +md""" +Use the same measurement data (`W_meas`, `t_meas`) as before. +""" + +# ╔═╡ b1f03df5-4353-45ed-9d91-9e2c9960f706 +md""" +Declare the Turing model function. +""" + +# ╔═╡ 89bf91c6-117c-4647-bfb9-6fc9b8dcfb5f +@model function growth_exp_fun(t_meas) + σ_W ~ InverseGamma() + W0 ~ LogNormal() + μ ~ LogNormal() + Wf ~ LogNormal() + u0_exp = [:W => W0] + params_exp = [:μ => μ, :Wf => Wf] + oprob_exp = ODEProblem(growth_exp, u0_exp, tspan, params_exp) + osol_exp = solve(oprob_exp, Tsit5(), saveat=t_meas) + W_s ~ MvNormal(osol_exp[:W], σ_W^2 * I) +end + +# ╔═╡ b9a8c0cb-fe38-448e-aa4c-e9422f24a4a4 +md""" +Provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: +""" + +# ╔═╡ 6b5fc852-1d40-45ce-a2e2-d22a6cc03156 +growth_exp_cond_mod = growth_exp_fun(t_meas) | (W_s = W_meas,) + +# ╔═╡ 29591722-bb44-47d0-a336-b2151502fd96 +md""" +Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $W_f$). Do this with both the `MLE` and `MAP` methods and the Nelder-Mead algorithm. Store the optimization results in `results_exp_mle` and `results_exp_map`. +""" + +# ╔═╡ ec032fb5-76a7-4a0c-a8be-b0f6bd166580 +results_exp_mle = optimize(growth_exp_cond_mod, MLE(), NelderMead()) + +# ╔═╡ d44ed37a-d022-457c-befe-01bae5cd0ed9 +md""" +Visualize a summary of the optimized parameters. +""" + +# ╔═╡ 5d3f6390-1f71-4543-ae4e-d72f28b94f40 +coeftable(results_exp_mle) + +# ╔═╡ 9eb6e432-3f41-4b5f-9194-241e6ab3a039 +md""" +Get the optimized values and assign them to `W0_opt_exp`, `μ_opt_exp` and `Wf_opt_exp`. +""" + +# ╔═╡ cc0fc9ba-5564-4104-b0b3-a10ae39ef933 +W0_opt_exp = coef(results_exp_mle)[:W0] + +# ╔═╡ 6083305f-c838-4770-95c5-258fd7becbcb +μ_opt_exp = coef(results_exp_mle)[:μ] + +# ╔═╡ 9777f3e8-785d-4ff5-a6c6-80a68a9c53ba +Wf_opt_exp = coef(results_exp_mle)[:Wf] + +# ╔═╡ e80de22e-d5e6-4af7-af0e-feaf9ef660bf +md""" +Make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ 0020352d-52dc-4ca4-90dd-f53865306f9e +md" +Set up initial condition with optimized initial condition: +" + +# ╔═╡ e58c49dd-5365-4dcb-9baa-c7a6048eb0ed +u0_opt_exp = [:W => W0_opt_exp] + +# ╔═╡ 03a35cce-cacf-4b11-a0b8-3a26ad93c227 +md""" +Set up parameter values with optimized parameter values: +""" + +# ╔═╡ b3556b32-4665-439e-8751-a3ffa8ba1907 +params_opt_exp = [:μ => μ_opt_exp, :Wf => Wf_opt_exp] + +# ╔═╡ ae030bbd-6416-44b8-ba9e-3fe9be5c9c52 +md""" +Create an ODEProblem and solve it. Solve it using `Tsit5()` and `saveat=0.5`. +""" + +# ╔═╡ bfd9d570-7b12-4bbf-aaa9-449f2377a294 +oprob_opt_exp = ODEProblem(growth_exp, u0_opt_exp, tspan, params_opt_exp) + +# ╔═╡ f9e1ca7a-2a27-46aa-82a2-5e06882a9fad +osol_opt_exp = solve(oprob_opt_exp, Tsit5(), saveat=0.5); + +# ╔═╡ 0e62cf17-f885-4c38-8df2-39d1719bfa04 +md""" +Plot now $W$ simulated with the optimized initial value and parameter values of both logistic and exponential models together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ fe3c059f-cb4f-4ddf-ad54-c08aa70bc740 +# Uncomment and complete the instruction +# begin +# plot() +# missing +# missing +# missing +# title!("Comparison logistic vs. exponential growth") +# end + +# ╔═╡ fcfd18a2-154f-46fd-b4a5-01e406b500f1 +md""" +!!! question + By looking at the figure, how can you decide which candidate model is better? +""" + +# ╔═╡ 78d5ad0e-318f-4af1-883b-b887f8d72591 +md""" +- Answer: missing +""" + +# ╔═╡ 58006718-5e76-4c6b-a968-aa5372bb4a15 +md""" +Compare now the fit of both models by applying both the AIC and BIC criteria. +""" + +# ╔═╡ 7606cd16-7463-462e-83e6-5adaea4632a2 +md""" +Extract the log-probability and number of parameters from the calibration results of the exponential: +""" + +# ╔═╡ 2379169c-7f2c-415c-8955-e8b06fc9edf5 +L_exp = results_exp_mle.lp + +# ╔═╡ 577bb77e-d8c6-4121-84e6-1d3c000ce7b9 +k_exp = length(coef(results_exp_mle)) + +# ╔═╡ ac2d3b13-54d2-4217-8b18-7f9e276f1b6f +md""" +Calculate the AIC and BIC for the exponential model: +""" + +# ╔═╡ 2174d25b-b603-4d7b-b396-7ea00780143c +AIC_exp = AIC(results_exp_mle, W_meas) + +# ╔═╡ 0e8a6614-3164-4eab-b28b-fde3a328cbc3 +BIC_exp = BIC(results_exp_mle, W_meas) + +# ╔═╡ f2805dab-cac4-4a31-8418-6763c14d6415 +L_log, L_exp + +# ╔═╡ 41d8cfe2-a997-4731-971e-3e0c6166177e +AIC_log, AIC_exp + +# ╔═╡ 2b2110eb-b867-47a1-8238-3d43a64851bb +BIC_log, BIC_exp + +# ╔═╡ 4fb53b80-4ea1-47a0-871a-adde769f6d18 +md""" +!!! question + Draw your conclusions. +""" + +# ╔═╡ 22b58c97-1aa2-49c4-8a0a-488c63014a90 +md" +- missing +" + +# ╔═╡ 41abf8c4-e67e-4f66-a38a-7204a878d98d +md""" +### Exercise 2 - Comparison of the three models +""" + +# ╔═╡ b02787ec-40eb-4000-9684-e69cbd912c6c +md""" +Perform the calibration of the Gompertz model and compare its fitness to the other two candidates. +""" + +# ╔═╡ e34fed6f-6b58-4642-84eb-167467881bb2 +md""" +$$\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$$\ +$W_0$ = 2.0, $\mu$ = 0.09 and $D$ = 0.04. +""" + +# ╔═╡ 68895842-0a1b-4236-b159-19a2805ea44d +growth_gom = @reaction_network begin + μ-D*log(W), W --> 2W +end + +# ╔═╡ ec12d3c4-6b48-4f3a-b463-0fc67d3adff3 +md""" +Declare the Turing model. Take the same priors as before. +""" +# Take for $\sigma_W$ and $W_0$ the same priors (and distributions) as before, but take for $\mu$ a Uniform prior distribution in the range $[0, 2]$ and the same for $D$ but in the range $[0, 1]$. + +# ╔═╡ 1f8b10ba-f380-492e-800c-2673774cb25c +@model function growth_gom_fun(t_meas) + σ_W ~ InverseGamma() + W0 ~ LogNormal() + μ ~ LogNormal() + D ~ LogNormal() + u0_gom = [:W => W0] + params_gom = [:μ => μ, :D => D] + oprob_gom = ODEProblem(growth_gom, u0_gom, tspan, params_gom) + osol_gom = solve(oprob_gom, Tsit5(), saveat=t_meas) + W_s ~ MvNormal(osol_gom[:W], σ_W^2 * I) +end + +# ╔═╡ 82bee644-7b9a-42fa-a32f-76b3eb5038b5 +md""" +Provide the time measurements to the defined function (this results in the Turing model) and instantly condition the Turing model with the measurements of $W$: +""" + +# ╔═╡ e481b87c-c6f3-48c1-bfff-5d76de0dce36 +growth_gom_cond_mod = growth_gom_fun(t_meas) | (W_s = W_meas,) + +# ╔═╡ a438470b-e037-4b65-b6b0-fb9c202e74a4 +md""" +Optimize the priors ($\sigma_W$, $W_0$, $\mu$ and $D$). Do this now with `MAP` method and Nelder-Mead. Store the optimization results in `results_gom_map`. +""" + +# ╔═╡ f4c3dcd0-70ec-49ab-bbad-04b550da481c +results_gom_mle = optimize(growth_gom_cond_mod, MLE(), NelderMead()) + +# ╔═╡ df166b45-3c28-4bad-95b8-f43429a58fd0 +md""" +Visualize a summary of the optimized parameters. +""" + +# ╔═╡ a2738236-86a1-419a-9617-b1229f7c9240 +coeftable(results_gom_mle) + +# ╔═╡ 59f78a22-cac1-49a7-b3e6-9d74b694be64 +md""" +Get the optimized values and assign them to `W0_opt_gom`, `μ_opt_gom` and `D_opt_gom`. +""" + +# ╔═╡ 7c351d72-c5ce-4e5a-b2e5-87ff9dbc70e5 +W0_opt_gom = coef(results_gom_mle)[:W0] + +# ╔═╡ d7312308-7c95-4c14-9bd6-56a7ac99d49a +μ_opt_gom = coef(results_gom_mle)[:μ] + +# ╔═╡ 8f8b23b3-2e33-46e4-917a-294934fa090e +D_opt_gom = coef(results_gom_mle)[:D] + +# ╔═╡ 4ff1079f-8dfb-4069-9a98-0d263eba9920 +md""" +Make a plot of $W$ simulated with the optimized initial condition and parameter values. +""" + +# ╔═╡ 9edd77c3-eb1e-4be7-bb6c-83803e675102 +md""" +Set up initial condition with optimized initial condition: +""" + +# ╔═╡ c02a4f18-d51d-4954-b7a4-a4ae64b10fb7 +u0_opt_gom = [:W => W0_opt_gom] + +# ╔═╡ 060b5869-881e-4d57-8357-cff95912ba16 +md""" +Set up parameter values with optimized parameter values: +""" + +# ╔═╡ 0d225f18-bdf6-49d1-a827-2bc9551c158d +params_opt_gom = [:μ => μ_opt_gom, :D => D_opt_gom] + +# ╔═╡ 5d27e199-ad09-454a-8966-1531371ce67b +md""" +Create an ODEProblem and solve it. Use the solver `Tsit5()` and `saveat=0.5`. +""" + +# ╔═╡ 79c4439b-df8f-467a-b791-5581bd564996 +oprob_opt_gom = ODEProblem(growth_gom, u0_opt_gom, tspan, params_opt_gom) + +# ╔═╡ bcbe7409-e969-4c7a-9417-195d406617bf +osol_opt_gom = solve(oprob_opt_gom, Tsit5(), saveat=0.5); + +# ╔═╡ 6dbc81c8-4bc7-4499-a9d1-9719df368176 +md""" +Finally, we plot $W$ simulated with the optimized initial value and parameter values together with the measured data that was used to find the optimized values. +""" + +# ╔═╡ ef5270de-da37-4a01-8734-dac1fd190a05 +# Uncomment and complete the instruction +# begin +# plot() +# missing +# missing +# missing +# title!("Comparison logistic vs. exponential growth") +# end + +# ╔═╡ b9030c3f-2cbb-44bf-90b7-23d116de4001 +# L_gom = missing + +# ╔═╡ 2e684fd0-8410-4037-a6a6-2c472d6592ab +# k_gom = missing + +# ╔═╡ 79797d6d-9ca8-4f4f-9694-8045a8cf8cbb +# AIC_gom = missing + +# ╔═╡ 2cd566bb-3088-43f8-8926-9952342b5e5c +# BIC_gom = missing + +# ╔═╡ 750edde4-16b5-4025-82e0-078c0eacaca9 +# AIC_log, AIC_exp, AIC_gom + +# ╔═╡ 1bbbd1ae-3540-44da-bc53-c8182a85b611 +# BIC_log, BIC_exp, BIC_gom + +# ╔═╡ 3afb2a69-32d6-42c2-8c1e-51e64b83a6b4 +md""" +!!! question + Draw your conclusions. +""" + +# ╔═╡ 01ddf3dd-5199-48cf-ab13-75686278f3dc +md""" +- Answer: missing +""" + +# ╔═╡ 36f71777-ad33-4b27-9a0e-ff03b22ad79e +md""" +You can use the following graph with all the information calculated so far for your conclusions. +""" + +# ╔═╡ d3b23128-2e7a-4246-a1f2-7325e0c2c432 +# plot( +# bar(1:3, [AIC_log, AIC_exp, AIC_gom], title="AIC", ylims=(0, 50)), +# bar(1:3, [BIC_log, BIC_exp, BIC_gom], title="BIC", ylims=(0, 50)), +# bar(1:3, [L_log, L_exp, L_gom], title="Log-probability", ylims=(-20, 0)), +# bar(1:3, [k_log, k_exp, k_gom], title="no. parameters", ylims=(0, 8)), +# xticks=(1:3, ["Logistic", "Exponential", "Gompertz"]), +# legend=:none +# ) + +# ╔═╡ a9e5b236-67d5-400b-ba9a-8b565f6feeb1 +md""" +### Exercise 3 - Calculation of the posterior probabilities +""" + +# ╔═╡ 3bf0c957-f08b-4a45-90e0-40d56330c8e0 +md""" +Use the above implemented function `posterior` to calculate the posterior model probabilities. +""" + +# ╔═╡ 947e26fe-2010-48ad-b927-d4e5186f21e0 +# posteriors = missing + +# ╔═╡ 3506765b-a6cf-41ca-9201-fcc650c6f56d +# posteriors + +# ╔═╡ 72dc27a0-0e61-4073-843a-67e1bfd1427a +md""" +We can summarize all calculated criteria so far in the following table: + +| Model | k | Log(L) | AIC | BIC | $P(M_i\|D)$ | +|:---|:---|:---|:---|:---|:---| +| Logistic | $k_log | $(round(L_log;digits=3)) | $(round(AIC_log;digits=3)) | $(round(BIC_log;digits=3)) | $(posteriors[1]) | +| Exponential | $k_exp | $(round(L_exp;digits=3)) | $(round(AIC_exp;digits=3)) | $(round(BIC_exp;digits=3)) | $(posteriors[2]) | +| Gompertz | $k_gom | $(round(L_gom;digits=3)) | $(round(AIC_gom;digits=3)) | $(round(BIC_gom;digits=3)) | $(posteriors[3]) | + +""" + +# ╔═╡ 6240553c-0c7c-45f4-b190-ebacf09ab632 +md""" +!!! question + Draw your conclusions. Does the posterior probability give the same raking as the other criteria? +""" + +# ╔═╡ 12f64333-36d0-4e2a-9061-e3dcc7a4ae96 +md" +- missing +" + +# ╔═╡ 95ba48fe-f430-4f47-ab04-f5896d75343b +md""" +### Exercise 4 - Comparison with least squares +""" + +# ╔═╡ ef293617-7a7a-4597-99d2-2c39c543c296 +md""" +Repeat below the comparison to least squares for the exponential and Gompertz models. +""" + +# ╔═╡ 8bbed35b-fec5-4682-8b45-99fd3bd55bf2 +begin + # Uncomment and complete the instruction + # W_exp = missing + # SSR_exp = missing + # AIC_LS_exp = missing + # BIC_LS_exp = missing +end; + +# ╔═╡ 414ebb03-4fb7-4591-90e9-9ce3cf11e93d +begin + # Uncomment and complete the instruction + # W_gom = missing + # SSR_gom = missing + # AIC_LS_gom = missing + # BIC_LS_gom = missing +end; + +# ╔═╡ 7a633bd6-10f1-4755-94ac-40d1dc9580a7 +# AIC_LS_log, AIC_LS_exp, AIC_LS_gom + +# ╔═╡ aef172f1-8563-4e51-8a88-ec6b764a735e +# BIC_LS_log, BIC_LS_exp, BIC_LS_gom + +# ╔═╡ 091e978a-fd89-455a-9b97-f3088d580683 +# posterior([AIC_LS_log, AIC_LS_exp, AIC_LS_gom]) + +# ╔═╡ d3d6e099-66b0-444e-8142-ccd1718ac9ab +# plot( +# bar(1:3, [AIC_LS_log, AIC_LS_exp, AIC_LS_gom], title="AIC", ylims=(-40, 0)), +# bar(1:3, [BIC_LS_log, BIC_LS_exp, BIC_LS_gom], title="BIC", ylims=(-40, 0)), +# bar(1:3, [SSR_log, SSR_exp, SSR_gom], title="SSR", ylims=(0, 8)), +# bar(1:3, [k_log, k_exp, k_gom], title="no. parameters", ylims=(0, 8)), +# xticks=(1:3, ["Logistic", "Exponential", "Gompertz"]), +# legend=:none, +# suptitle="(Least squares)" +# ) + +# ╔═╡ 2de0f619-63e0-444b-916b-316e4ebfa13e +md""" +!!! question + Draw your conclusions. Do the SSR and alternative AIC and BIC provide the same model ranking? +""" + +# ╔═╡ ef5329d2-ac63-4b49-a4cb-e8d6281a3184 +md""" +- Answer: missing +""" + +# ╔═╡ 0b8e6775-7562-4c93-b64d-cb694531f250 +md""" +## Additional exercises +""" + +# ╔═╡ c99ac3a9-1d5e-43a3-a4ef-a22147187bb3 +md""" +### 1. MAP estimation +""" + +# ╔═╡ b620006e-d6ac-4038-960f-8d0d93c2de00 +md""" +We can repeat the calibration and take into account the priors to obtain the MAP estimation. +""" + +# ╔═╡ 00dbb9d0-2c9d-4559-91d8-1a54171cc257 +results_log_map = optimize(growth_log_cond_mod, MAP(), NelderMead()) + +# ╔═╡ 3d5e9bb7-5e65-4de5-a521-6b1f1cb872b3 +coeftable(results_log_map) + +# ╔═╡ 3a3cd103-78b1-481a-8746-749d402346d1 +md""" +!!! question + How will this affect the different criteria for the model selection? How is log(L) compared to MLE? +""" + +# ╔═╡ b9986617-923e-4744-9d44-1935770edbd7 +md""" +### 2 - Watanabe-Akaike information criterion (WAIC) +""" + +# ╔═╡ 6e0c869d-6f90-4104-8589-41c2fa1b7342 +md""" +The AIC and BIC are easy to compute but do not take into account the uncertainty in the predictions for the assessment of the model. The more complex Widely Applicable Information Criterion (WAIC) or Watanabe-Akaike information criterion takes samples from the posterior distribution and provides a measure of uncertainty for each observation, which can be used for model assessment. +""" + +# ╔═╡ 6ca83977-cb8c-4465-ab2e-c91971f16157 +md""" +We can generate new samples from the posterior distribution with MCMC. +""" + +# ╔═╡ fb76009b-3aaa-4eb5-8d69-45372a23af00 +N = 200 + +# ╔═╡ b9c59973-512b-48eb-a2b1-831449e120a4 +results_log_nuts = sample(growth_log_cond_mod, NUTS(), N) + +# ╔═╡ cfeb28b8-2dd7-4c1d-8069-e8fe00d90567 +plot(results_log_nuts) + +# ╔═╡ 56784bdf-2a78-4986-9dd2-790a2a2ceea9 +md""" +The log-pointwise-predictive-density (lppd) is the sum of the log-likelihood of all observations: +""" + +# ╔═╡ bfb99943-1637-4dd6-adee-8a9e79274672 +lppd_log = sum(results_log_nuts.value[:, :lp]) + +# ╔═╡ a08fb39b-3911-450e-8377-cc94398825a9 +md""" +The second part of WAIC is the variance of the log-likelihood of each observation, also called the effective number of parameters, $p_{WAIC}$, considered here as a penalty term, similarly to AIC and BIC: +""" + +# ╔═╡ 2575199f-a3ec-470f-87ea-d62be28c42dc +pWAIC_log = sum(results_log_nuts.value[:, :lp].^2)/N - (lppd_log/N)^2 + +# ╔═╡ 6391d316-8826-4f19-9a2e-7065db787bee +sum((results_log_nuts.value[:, :lp] .- lppd_log/N).^2)/N + +# ╔═╡ 2879ff01-860f-4fb1-b52d-3a43e57ccb23 +md""" +Finally, WAIC is defined as: + +$WAIC = -2(\text{lppd} - p_{WAIC})$ +""" + +# ╔═╡ 3b78c1ff-a2f6-4c1c-8124-5fe58c5a312d +WAIC_log = -2*(lppd_log - pWAIC_log) + +# ╔═╡ 75e221b4-fdfe-4ef0-a8ea-1d75700fc1e7 +md""" +We repeat the calculation for the exponential and Gompertz models below. +""" + +# ╔═╡ 0f228304-de82-48f2-8e43-6ef81f7a9830 +results_exp_nuts = sample(growth_exp_cond_mod, NUTS(), N) + +# ╔═╡ 9843e282-44b3-40d0-b2b5-506144c22b27 +lppd_exp = sum(results_exp_nuts.value[:, :lp]) + +# ╔═╡ eb9fe668-0a0f-4a8f-a793-ba545cb02e85 +pWAIC_exp = (sum(results_exp_nuts.value[:, :lp].^2)/N - (lppd_exp/N)^2) + +# ╔═╡ ddf9ae6d-0abe-4c8c-9646-024dd9a41e02 +WAIC_exp = -2*(lppd_exp - pWAIC_exp) + +# ╔═╡ 8330fce5-13c9-447e-8368-01b7c7ebc68f +results_gom_nuts = sample(growth_gom_cond_mod, NUTS(), N) + +# ╔═╡ 0d2cab0c-04a2-4c8b-bb4f-2115ae0cf871 +lppd_gom = sum(results_gom_nuts.value[:, :lp]) + +# ╔═╡ 6900e703-de7d-4ea3-8ac5-0b38b21b083d +pWAIC_gom = sum(results_gom_nuts.value[:, :lp].^2)/N - (lppd_gom/N)^2 + +# ╔═╡ b353eab2-d78f-4c5e-8e38-bd57813b6816 +WAIC_gom = -2*(lppd_gom - pWAIC_gom) + +# ╔═╡ 48dd67e7-363a-4e7c-ad81-8f70fc6db847 +[WAIC_log, WAIC_exp, WAIC_gom] + +# ╔═╡ a7fa7870-f202-4694-a9c1-7021ed242966 +posterior([WAIC_log, WAIC_exp, WAIC_gom]) + +# ╔═╡ 1699d3ae-d77a-4272-8c4b-ddd4eadd503f +md""" +!!! question + Why is the WAIC criterion significantly better for model selection despite its complexity? +""" + +# ╔═╡ 72878862-dcc0-4c79-9c91-7c0bc44359d3 +md""" +#### References +1. [https://en.wikipedia.org/wiki/Watanabe%E2%80%93Akaike_information_criterion](https://en.wikipedia.org/wiki/Watanabe%E2%80%93Akaike_information_criterion) +2. [https://civil.colorado.edu/~balajir/CVEN6833/bayes-resources/RM-StatRethink-Bayes.pdf](https://civil.colorado.edu/~balajir/CVEN6833/bayes-resources/RM-StatRethink-Bayes.pdf) +""" + +# ╔═╡ Cell order: +# ╠═5f4fea06-0632-11ef-102e-21f5606d2056 +# ╠═489b5399-fe4c-481a-834f-0101bbe28cea +# ╠═e1e7bc8e-7264-4cbc-98d2-aa73679fa2df +# ╠═ab4a1911-461e-4878-9258-931fc2f1ea06 +# ╠═cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 +# ╠═22489bd4-ab64-4bf9-ad03-5372ea273935 +# ╠═4ad72a6a-f541-4d0a-8392-56369e29ac96 +# ╠═70590d28-058a-44df-8cf6-092d8c87438c +# ╟─427b509f-d08e-4d93-99ba-a79f9c244b28 +# ╟─49e7085c-3691-4182-9630-68dc9371ad18 +# ╟─84caa99f-32ab-433c-b620-f592d329d18d +# ╟─7cc1d1ce-a34a-4432-953d-4eb2df92696f +# ╟─dcb51063-2b3f-4de9-90bc-0048e82cd4ab +# ╟─6690a844-8fd5-4ea1-9e62-a834bd454efb +# ╟─61e5bead-6fc3-4c07-b2b4-832ce7a69198 +# ╟─04e95855-7c4a-4d2c-b836-c5dde291adad +# ╟─4673bfdf-8f4a-42bd-a026-21a66d800f2b +# ╟─6e5f494e-ecfb-467b-a0f1-c85427da8215 +# ╠═0b3d35bb-c5b0-44b7-94b3-06fa571d339e +# ╟─d1c5a659-70c4-4768-9542-a2988ea39c43 +# ╟─583ed493-27e7-4fa7-b519-75cc71468fc6 +# ╠═d4ca6986-df24-48dd-8637-0974e0b16f2c +# ╟─8215b6f0-017e-4a73-b4bf-aa61b8f3ca6f +# ╠═2ff5f464-38d7-4a90-ad31-d19d412781fc +# ╟─b1d02f3c-f528-4297-a0c1-993cfe0d7c17 +# ╠═1499c727-ff19-4ef0-b467-bc71c1802979 +# ╟─c692b627-8743-48f6-bb26-b7d9b3d3b9c8 +# ╟─676119ce-f4fe-41f9-8121-b2c21f0dd28c +# ╟─5492210b-6ee6-4baf-b37e-d21358cdeb60 +# ╠═18eece66-46fa-4458-aaed-f4c8fa002c20 +# ╟─8bb0b24b-6cce-49cd-a625-5f375b92d9b7 +# ╠═ba181db8-d176-4b83-9168-d5939ffe9661 +# ╟─cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 +# ╠═cbb2ca49-b019-495b-9310-83fcc00cad26 +# ╟─be565a3c-31b6-4df1-b73b-08f308a8c09b +# ╠═f62806d1-77e1-470b-9711-33a924c788cc +# ╠═546ed163-26a7-4235-982f-7568ed609488 +# ╠═0da53fa2-5a42-46e6-8bd8-45d6aa903d46 +# ╟─b1298f40-4696-49d0-ac94-896e0cdbc996 +# ╠═270647d2-1371-4272-8bc1-3a6ad77bc716 +# ╠═ac235d86-1d93-4944-aa89-1b4fd38f0e6e +# ╠═3e13efa1-9bc6-456f-8e62-ecd3165e2a65 +# ╟─e2955af0-edf0-4702-9a8f-478141ffdc3b +# ╟─c73669c4-d7af-4877-b32d-6499f339e27a +# ╟─28e4a44f-e907-42ce-a748-6d21b60f0e33 +# ╠═169a67ff-55fb-4d1d-b98b-126f4af47e77 +# ╟─aa3e553d-2731-42d6-b0e6-1821e4d7f4d4 +# ╠═51426716-03b8-4d54-9064-3943df282fa4 +# ╟─f40873af-3ef9-420b-b0a8-ed9f56b17047 +# ╟─4536c6b7-f3bc-42e8-b044-0633f00b56bb +# ╠═bc55fb07-1d84-4927-80d2-c1a012409400 +# ╟─c224f1b8-e5a1-4379-ae50-981ae89f9eaa +# ╠═4fe2b4cd-d0b0-489a-a867-14c58637327b +# ╟─6cf5e2f2-344c-4a7a-8901-5a0ee9cb8689 +# ╠═551859cb-9cd1-4977-a528-b96423e83504 +# ╠═955524a6-1a53-4d2d-ad16-a5a3a2a39b80 +# ╠═118a79cc-afaf-4a99-8486-8e0e01fda40a +# ╟─8536cc9e-6cd4-4e38-9988-e45b06b75ea4 +# ╟─25db8d7b-e467-4a2f-b3f3-0ad7df64988a +# ╠═01241971-cc9a-42d6-8e52-c840b91e6431 +# ╟─e2db7492-4d68-4045-91b6-dca3aef8b514 +# ╠═7a890da6-7547-4854-b877-a5d935a9f2dd +# ╟─c95bcde6-ffb9-4ce6-9dfb-38c79cc2e4b8 +# ╠═bf90a66a-8505-4b33-b821-d708a3b7f8b0 +# ╠═7af67f91-58e2-429d-a88f-b27a0258a805 +# ╟─3ee0d4de-fdf5-40f0-a849-63b0759eb44b +# ╠═2c048fc0-5908-4f01-a9a5-480e12e098cc +# ╟─19ed4aa9-776e-4a80-b180-65d32f3f9f26 +# ╠═12a11f90-fa55-47f2-a75f-853cd54cbeab +# ╟─f6b44166-3ba9-4770-80db-303bd28112cf +# ╟─02037c91-448e-4714-b932-86f4df631907 +# ╟─cfea46a6-636c-4fcd-a04b-714e7a567cb3 +# ╠═cb9384a3-96b0-4694-a218-25ebf96187a9 +# ╟─73917400-43e0-4b9f-9448-9bbe02daebb9 +# ╠═afba526d-0f71-4ba5-9a84-3c24804f959a +# ╟─903380ce-1320-464e-a703-1222b4f381b1 +# ╠═22bd4450-6c6d-4a4d-aa1d-a080d3b06eac +# ╟─34f8cb7d-7482-4883-87e0-5c68be7ec0e9 +# ╟─4d976b86-74d6-4bb5-af17-8a6bd658abe2 +# ╠═ba7974ec-3965-4c92-ab37-08cdf5ab8125 +# ╟─5e6837d5-35e9-434c-9337-7c90daec9c33 +# ╠═b97ca16c-e6c3-4840-a855-1d29140eff59 +# ╠═1af53492-f363-4d39-bfaf-2f668a441725 +# ╟─e176e6ce-33da-4f3b-860e-4db99a931079 +# ╟─770e70d0-8a69-4585-a423-41335d453dcd +# ╟─f4f3b1fb-165d-4f77-88f1-8c1a79f3fb42 +# ╟─4a86d852-634c-43f5-90e5-3e675aeb2d69 +# ╟─8a43d4b8-1902-4e02-9cd7-c36131ec9528 +# ╠═d149d829-7d3d-4f7a-bdad-b1debb2c5149 +# ╠═e2bec1ab-f161-45b6-a67e-1f4f197ae685 +# ╟─5db6b1a5-ed2e-4812-94db-37cfffb46227 +# ╟─bc8d315a-ddfb-4c82-84d0-90a4fb05256c +# ╟─51b348ef-0270-41d0-bbcb-a27eb10ff5d2 +# ╠═7bf8fa3a-ce98-4f40-8d6c-4e84b79ae1c0 +# ╠═498e0b9b-17a9-4059-9196-c6c4d86f926e +# ╟─b57131ad-e9d6-4318-bca7-c59c5d8da4c3 +# ╠═f2fe206b-2832-42f6-84bd-20c3838fcb0b +# ╟─d51fff9b-17d3-4a0d-aa5e-01d58821a246 +# ╠═6d95d3b1-d964-4b61-adb6-f44875e23ecb +# ╠═b1783355-0221-4f2d-a050-932b40920eab +# ╠═3c69edb3-ba7d-4ee0-996a-40fd985e57d3 +# ╟─982e1de1-0820-42fb-8abb-31f1c99def46 +# ╟─79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 +# ╟─fa2270d0-2548-409c-a23f-4369d8bce8ec +# ╟─545a317d-8bca-4b6c-9915-84b0b4ffbed5 +# ╟─794b93c9-0a0a-4e77-b13c-0e2c06a9a0ec +# ╠═cfca0095-81e7-4c88-84bd-773eb5af7aa4 +# ╟─ba9162dd-27f4-42b3-a6b5-ca9531614d5e +# ╟─b1f03df5-4353-45ed-9d91-9e2c9960f706 +# ╠═89bf91c6-117c-4647-bfb9-6fc9b8dcfb5f +# ╟─b9a8c0cb-fe38-448e-aa4c-e9422f24a4a4 +# ╠═6b5fc852-1d40-45ce-a2e2-d22a6cc03156 +# ╟─29591722-bb44-47d0-a336-b2151502fd96 +# ╠═ec032fb5-76a7-4a0c-a8be-b0f6bd166580 +# ╟─d44ed37a-d022-457c-befe-01bae5cd0ed9 +# ╠═5d3f6390-1f71-4543-ae4e-d72f28b94f40 +# ╟─9eb6e432-3f41-4b5f-9194-241e6ab3a039 +# ╠═cc0fc9ba-5564-4104-b0b3-a10ae39ef933 +# ╠═6083305f-c838-4770-95c5-258fd7becbcb +# ╠═9777f3e8-785d-4ff5-a6c6-80a68a9c53ba +# ╟─e80de22e-d5e6-4af7-af0e-feaf9ef660bf +# ╟─0020352d-52dc-4ca4-90dd-f53865306f9e +# ╠═e58c49dd-5365-4dcb-9baa-c7a6048eb0ed +# ╟─03a35cce-cacf-4b11-a0b8-3a26ad93c227 +# ╠═b3556b32-4665-439e-8751-a3ffa8ba1907 +# ╟─ae030bbd-6416-44b8-ba9e-3fe9be5c9c52 +# ╠═bfd9d570-7b12-4bbf-aaa9-449f2377a294 +# ╠═f9e1ca7a-2a27-46aa-82a2-5e06882a9fad +# ╟─0e62cf17-f885-4c38-8df2-39d1719bfa04 +# ╠═fe3c059f-cb4f-4ddf-ad54-c08aa70bc740 +# ╟─fcfd18a2-154f-46fd-b4a5-01e406b500f1 +# ╠═78d5ad0e-318f-4af1-883b-b887f8d72591 +# ╟─58006718-5e76-4c6b-a968-aa5372bb4a15 +# ╟─7606cd16-7463-462e-83e6-5adaea4632a2 +# ╠═2379169c-7f2c-415c-8955-e8b06fc9edf5 +# ╠═577bb77e-d8c6-4121-84e6-1d3c000ce7b9 +# ╟─ac2d3b13-54d2-4217-8b18-7f9e276f1b6f +# ╠═2174d25b-b603-4d7b-b396-7ea00780143c +# ╠═0e8a6614-3164-4eab-b28b-fde3a328cbc3 +# ╠═f2805dab-cac4-4a31-8418-6763c14d6415 +# ╠═41d8cfe2-a997-4731-971e-3e0c6166177e +# ╠═2b2110eb-b867-47a1-8238-3d43a64851bb +# ╟─4fb53b80-4ea1-47a0-871a-adde769f6d18 +# ╠═22b58c97-1aa2-49c4-8a0a-488c63014a90 +# ╟─41abf8c4-e67e-4f66-a38a-7204a878d98d +# ╟─b02787ec-40eb-4000-9684-e69cbd912c6c +# ╟─e34fed6f-6b58-4642-84eb-167467881bb2 +# ╠═68895842-0a1b-4236-b159-19a2805ea44d +# ╟─ec12d3c4-6b48-4f3a-b463-0fc67d3adff3 +# ╠═1f8b10ba-f380-492e-800c-2673774cb25c +# ╟─82bee644-7b9a-42fa-a32f-76b3eb5038b5 +# ╠═e481b87c-c6f3-48c1-bfff-5d76de0dce36 +# ╟─a438470b-e037-4b65-b6b0-fb9c202e74a4 +# ╠═f4c3dcd0-70ec-49ab-bbad-04b550da481c +# ╟─df166b45-3c28-4bad-95b8-f43429a58fd0 +# ╠═a2738236-86a1-419a-9617-b1229f7c9240 +# ╟─59f78a22-cac1-49a7-b3e6-9d74b694be64 +# ╠═7c351d72-c5ce-4e5a-b2e5-87ff9dbc70e5 +# ╠═d7312308-7c95-4c14-9bd6-56a7ac99d49a +# ╠═8f8b23b3-2e33-46e4-917a-294934fa090e +# ╟─4ff1079f-8dfb-4069-9a98-0d263eba9920 +# ╟─9edd77c3-eb1e-4be7-bb6c-83803e675102 +# ╠═c02a4f18-d51d-4954-b7a4-a4ae64b10fb7 +# ╟─060b5869-881e-4d57-8357-cff95912ba16 +# ╠═0d225f18-bdf6-49d1-a827-2bc9551c158d +# ╟─5d27e199-ad09-454a-8966-1531371ce67b +# ╠═79c4439b-df8f-467a-b791-5581bd564996 +# ╠═bcbe7409-e969-4c7a-9417-195d406617bf +# ╟─6dbc81c8-4bc7-4499-a9d1-9719df368176 +# ╠═ef5270de-da37-4a01-8734-dac1fd190a05 +# ╠═b9030c3f-2cbb-44bf-90b7-23d116de4001 +# ╠═2e684fd0-8410-4037-a6a6-2c472d6592ab +# ╠═79797d6d-9ca8-4f4f-9694-8045a8cf8cbb +# ╠═2cd566bb-3088-43f8-8926-9952342b5e5c +# ╠═750edde4-16b5-4025-82e0-078c0eacaca9 +# ╠═1bbbd1ae-3540-44da-bc53-c8182a85b611 +# ╟─3afb2a69-32d6-42c2-8c1e-51e64b83a6b4 +# ╠═01ddf3dd-5199-48cf-ab13-75686278f3dc +# ╟─36f71777-ad33-4b27-9a0e-ff03b22ad79e +# ╠═d3b23128-2e7a-4246-a1f2-7325e0c2c432 +# ╟─a9e5b236-67d5-400b-ba9a-8b565f6feeb1 +# ╟─3bf0c957-f08b-4a45-90e0-40d56330c8e0 +# ╠═947e26fe-2010-48ad-b927-d4e5186f21e0 +# ╠═3506765b-a6cf-41ca-9201-fcc650c6f56d +# ╟─72dc27a0-0e61-4073-843a-67e1bfd1427a +# ╟─6240553c-0c7c-45f4-b190-ebacf09ab632 +# ╠═12f64333-36d0-4e2a-9061-e3dcc7a4ae96 +# ╟─95ba48fe-f430-4f47-ab04-f5896d75343b +# ╟─ef293617-7a7a-4597-99d2-2c39c543c296 +# ╠═8bbed35b-fec5-4682-8b45-99fd3bd55bf2 +# ╠═414ebb03-4fb7-4591-90e9-9ce3cf11e93d +# ╠═7a633bd6-10f1-4755-94ac-40d1dc9580a7 +# ╠═aef172f1-8563-4e51-8a88-ec6b764a735e +# ╠═091e978a-fd89-455a-9b97-f3088d580683 +# ╠═d3d6e099-66b0-444e-8142-ccd1718ac9ab +# ╟─2de0f619-63e0-444b-916b-316e4ebfa13e +# ╠═ef5329d2-ac63-4b49-a4cb-e8d6281a3184 +# ╟─0b8e6775-7562-4c93-b64d-cb694531f250 +# ╟─c99ac3a9-1d5e-43a3-a4ef-a22147187bb3 +# ╟─b620006e-d6ac-4038-960f-8d0d93c2de00 +# ╠═00dbb9d0-2c9d-4559-91d8-1a54171cc257 +# ╠═3d5e9bb7-5e65-4de5-a521-6b1f1cb872b3 +# ╟─3a3cd103-78b1-481a-8746-749d402346d1 +# ╟─b9986617-923e-4744-9d44-1935770edbd7 +# ╟─6e0c869d-6f90-4104-8589-41c2fa1b7342 +# ╟─6ca83977-cb8c-4465-ab2e-c91971f16157 +# ╠═fb76009b-3aaa-4eb5-8d69-45372a23af00 +# ╠═cfeb28b8-2dd7-4c1d-8069-e8fe00d90567 +# ╠═b9c59973-512b-48eb-a2b1-831449e120a4 +# ╟─56784bdf-2a78-4986-9dd2-790a2a2ceea9 +# ╠═bfb99943-1637-4dd6-adee-8a9e79274672 +# ╟─a08fb39b-3911-450e-8377-cc94398825a9 +# ╠═2575199f-a3ec-470f-87ea-d62be28c42dc +# ╠═6391d316-8826-4f19-9a2e-7065db787bee +# ╟─2879ff01-860f-4fb1-b52d-3a43e57ccb23 +# ╠═3b78c1ff-a2f6-4c1c-8124-5fe58c5a312d +# ╟─75e221b4-fdfe-4ef0-a8ea-1d75700fc1e7 +# ╠═0f228304-de82-48f2-8e43-6ef81f7a9830 +# ╠═9843e282-44b3-40d0-b2b5-506144c22b27 +# ╠═eb9fe668-0a0f-4a8f-a793-ba545cb02e85 +# ╠═ddf9ae6d-0abe-4c8c-9646-024dd9a41e02 +# ╠═8330fce5-13c9-447e-8368-01b7c7ebc68f +# ╠═0d2cab0c-04a2-4c8b-bb4f-2115ae0cf871 +# ╠═6900e703-de7d-4ea3-8ac5-0b38b21b083d +# ╠═b353eab2-d78f-4c5e-8e38-bd57813b6816 +# ╠═48dd67e7-363a-4e7c-ad81-8f70fc6db847 +# ╠═a7fa7870-f202-4694-a9c1-7021ed242966 +# ╟─1699d3ae-d77a-4272-8c4b-ddd4eadd503f +# ╟─72878862-dcc0-4c79-9c91-7c0bc44359d3 diff --git a/src/exercises/optim_wastewater_treatment.jl b/src/exercises/optim_wastewater_treatment.jl new file mode 100644 index 00000000..5b9ca8b3 --- /dev/null +++ b/src/exercises/optim_wastewater_treatment.jl @@ -0,0 +1,337 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "34" +#> title = "6. Optimisation wastewater treatment" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Optimisation wastewater treatment" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ e6c97e37-d062-4b65-96a4-bac0dab220d8 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ f08fa69c-a744-11ef-0e79-3daf5bf297ea +using Markdown, InteractiveUtils + +# ╔═╡ 0407d891-a46d-4deb-a21a-23833acbcb87 +using ModelingToolkit, OrdinaryDiffEq + +# ╔═╡ e48dc930-be03-47b2-b9e3-16e854782aec +using ModelingToolkit: t_nounits as t, D_nounits as D + +# ╔═╡ 75d55180-bc34-4ff9-891f-cf522aab564e +using Turing, StatsPlots, StatsBase, Optim + +# ╔═╡ 6458329f-73dd-4cb0-8da4-90678875a1f0 +using PlutoUI; TableOfContents() + +# ╔═╡ c1bc698d-41ee-45e6-b17d-29f0d53557a1 +md""" +# Exercise: Wastewater treatment - Optimisation +""" + +# ╔═╡ dfe77a8c-a8db-46d7-9a4d-3b00413d383b +md""" +Consider a wastewater treatment plant where wastewater circulates through cylindrical tanks, allowing microorganisms to break down the organic material present. At the top of such a tank with volume $V\;[\mathrm{m^3}]$, wastewater enters at a flow rate $q\;[\mathrm{m^3/h}]$. The concentration of organic material in the inflow is known and equal to $C_{in}\;[\mathrm{kg/m^3}]$. At the bottom of the tank, wastewater and microorganisms leave the tank at the same flow rate $q\;[\mathrm{m^3/h}]$ so that the volume of wastewater in the tank remains constant. + +The concentration of organic material in the tank is denoted as $C\;[\mathrm{kg/m^3}]$ and the concentration of microorganisms is denoted as $X\;[\mathrm{kg/m^3}]$. The microorganisms in the tank break down the organic material at a rate proportional to $r\cfrac{K_s}{K_s+C}\;[\mathrm{m^3\,h^{-1}\,kg^{-1}}]$ with yield coefficient $Y$. The factor $K_s\;[\mathrm{kg/m^3}]$ is the concentration of $C$ where the rate is half its maximum rate and $r\;[\mathrm{m^3\,h^{-1}\,kg^{-1}}]$ is the maximum growth rate coefficient. Furthermore, the microorganisms degrade with a rate coefficient $k_d$. In the middle of the tank, a mixing system ensures that wastewater and microorganisms are thoroughly mixed. This means that the concentration in the outflow is equal to the concentration in the tank: $C_{out} = C$ and $X_{out} = X$. The system of differential equations describing the change in the concentrations $C(t)$ and $X(t)$ is given by: + +$$\cfrac{dC}{dt} = \cfrac{q}{V}\left(C_{in} - C\right) - r\cfrac{K_s}{K_s+C}\,C\,X$$ +$$\cfrac{dX}{dt} = -\cfrac{q}{V}X -k_d\,X + Y\,r\cfrac{K_s}{K_s+C}\,C\,X$$ + +The initial concentrations and the parameter values are summarised in the following tables: + +| $C_0$ | $X_0$ | +|:---------:|:---------:| +| $3.0$ | $0.5$ | + +| $q$ | $V$ | $r$ | $C_{in}$ | $K_s$ | $k_d$ | $Y$ | +|:--------:|:--------:|:--------:|:---------:|:---------:|:---------:|:--------:| +| $5.0$ | $50$ | $0.4$ | $3.0$ | $5.2$ | $0.10$ | $1.2$ | + +The amount of organic waste being broken down by microorganisms depends on the flow rate $q$. First (Part 1), we will simulate the system with the parameters given above. Second (Part 2), we will optimize the value of the flow rate $q$ so that the concentration of organic waste in the tank is at most $0.28\;\mathrm{kg\,m^{-3}}$. +""" + +# ╔═╡ c0c83df7-a9cc-4bde-b6ec-038a423b0d90 +md""" +## Part 1 + +In this part, we will simulate the system with the parameters given above. +""" + +# ╔═╡ bbd50cc5-032a-4219-bcee-91145935a7c4 +md""" +### Implementation of the system +""" + +# ╔═╡ a5f79c20-f62e-4df2-be79-b4f2141ced5e +md""" +Model the system by means of ModelingToolkit. +""" + +# ╔═╡ c9b86375-8750-4451-bdfe-10716b72d685 +md""" +Define the variables and assign them to their default values. +""" + +# ╔═╡ 8708de16-3532-4352-b211-c092f95c82d3 +# @variables missing + +# ╔═╡ 2bfabcef-183d-4c73-a19b-121a3494c150 +md""" +Define the parameters and assign them to their default values. +""" + +# ╔═╡ 68c7e017-dd17-4e8c-9c1a-ff96da009386 +# @parameters missing + +# ╔═╡ 10c73294-a32b-4aa3-80a8-10785e5eab8f +md""" +Set up the equations for the change in $C$ and $X$. +""" + +# ╔═╡ fee917dd-7ab5-4fda-b1b7-87ee61e21f19 +# change_C = missing + +# ╔═╡ 4fd2abc8-24d8-4864-a89d-7a23807aa41d +# change_X = missing + +# ╔═╡ e986a04a-c9a9-44a8-bea3-cf20d863ba3a +md""" +Build the ODE system and name it `sys_ww_treat`. +""" + +# ╔═╡ d732b1ad-b3c3-43bc-8fbf-a6b2b89566d2 +# @mtkbuild missing + +# ╔═╡ 08ebcb95-8603-4579-879e-810b1494b013 +md""" +##### Setting up initial conditions, timespan and parameter values +""" + +# ╔═╡ d248f64e-ebba-4443-9c49-ff0290aa7810 +md""" +Initialize a vector `u0` with the initial conditions: +""" + +# ╔═╡ fe02a755-5b00-4d80-a511-fec115b42964 +# u0 = missing + +# ╔═╡ 67481927-0d03-4da9-af6c-9afa409fc006 +md""" +Set the timespan to 72 hours: +""" + +# ╔═╡ fadd372a-a665-4b16-9b6d-e32cb7f25d7f +# tspan = missing + +# ╔═╡ 734e4d51-95a7-464e-9a23-5ad6c8715d65 +md""" +Initialize a vector `parms` with the parameter values: +""" + +# ╔═╡ 15ce9889-a437-46c8-9062-74b8d234a8bd +# parms = missing + +# ╔═╡ b8a48461-3882-45f6-980c-38d650ac52c7 +md""" +### Creating an ODE problem, solve the problem and plot results +""" + +# ╔═╡ a4c57b64-6a7d-4bd4-8bb2-578923e184d2 +md""" +Create the ODE problem and store it in `oprob`: +""" + +# ╔═╡ b1e18139-5277-4f06-b1f8-b0f5f11c41d8 +# oprob = missing + +# ╔═╡ ad6d8fe6-e62f-4c67-8d63-4ee13b928ad0 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=0.1`. Store the solution in `osol`: +""" + +# ╔═╡ e2ffba9e-aaf2-4540-84cf-8b7297ae9285 +# osol = missing + +# ╔═╡ 70871ee8-b0a4-4a9a-af39-5a63459b55f7 +md""" +Plot the results. Use `ylim=(0, 4)` and `linewidth=2` as options. +""" + +# ╔═╡ 34309734-3751-47e0-a602-d113ffaae510 +# begin +# missing +# plot!([tspan[1], tspan[2]], [0.28, 0.28], +# linestyle=:dash, linewidth=2, linecolor=:green, label="") +# end + +# ╔═╡ a0e735ad-09c2-4aa8-bc41-b294a9d56ea8 +md""" +Check out the end value of the organic waste. +""" + +# ╔═╡ f62898d5-1b8d-4350-8655-78aa3decb2a2 +# missing + +# ╔═╡ 7eb5df9c-a475-4812-81c3-e43484c82242 +md""" +## Part 2 + +In this part, we will optimize the value of the flow rate $q$ so that the concentration of organic waste in the tank is at most $0.28\;\mathrm{kg\,m^{-3}}$. +""" + +# ╔═╡ 5a695734-677f-4bf6-a703-8e22382b7529 +md""" +Declare the Turing model function. Sample the flow rate $q$ prior from an uniform distribution in the range $[0, 5]\;\mathrm{kg\,m^{-3}}$. Suppose therein that the desired end value of the organic waste (i.e. $0.28\;\mathrm{kg\,m^{-3}}$) is normally distributed with mean the end value obtained from the solution and standard deviation $10^{-3}\;\mathrm{kg\,m^{-3}}$. +""" + +# ╔═╡ b6bac48a-4a3d-47e4-90ea-788ca20dadff +# @model function wastewater_treatment_inference() +# q ~ missing +# u0 = missing +# tspan = missing +# params = missing +# oprob = missing +# osol = missing +# C ~ missing +# end + +# ╔═╡ b3a40556-0c00-4f6d-8cd9-c5fca79d8bbf +md""" +Define the desired value for the organic waste with the variable name `C_val`. +""" + +# ╔═╡ 2df409ef-bd95-4ac3-a2b8-c5e17c490eba +# missing + +# ╔═╡ 70cafd87-63f7-4674-ae49-43d422fdeae7 +md"Instantiate the Turing model and condition it with the observed value of $C$" + +# ╔═╡ ef20f8b8-4527-4f02-b449-fa67b68bbf65 +# wastewater_treatment_cond_mod = missing + +# ╔═╡ ee1ffc12-55a1-47ef-ac5b-33148706a09b +md""" +Optimize the prior for $q$. Do this with the `MLE` method and Nelder-Mead. Store the optimization results in `results_mle`. +""" + +# ╔═╡ afc035be-075b-464b-8ba2-20235082f005 +# results_mle = missing + +# ╔═╡ 3ee8121e-3e78-4901-a32d-f04d0c6a0996 +md""" +Get the optimized value for $q$ and assign it to `q_opt`. +""" + +# ╔═╡ 98a157a1-8c20-474d-acb8-00373ee6d224 +# q_opt = missing + +# ╔═╡ ceb146c9-a09a-458b-b7d8-3bb7d3de38e0 +md""" +Set up parameter values with the optimized parameter value. +""" + +# ╔═╡ e275df05-5c77-4c17-ad2e-503574596c31 +# parms_opt = missing + +# ╔═╡ cd515dad-44fb-4af2-b933-805ef76be9b3 +md""" +Create an ODEProblem and solve it. Use `Tsit5()` and `saveat=0.1`. +""" + +# ╔═╡ a4388f06-1223-4815-a557-9b9c3ec232bb +# oprob_opt = missing + +# ╔═╡ ec7bf654-b275-4cfd-a819-d82bdc1be93b +# osol_opt = missing + +# ╔═╡ 82809c26-4cab-405e-8107-a8a43e81f699 +md""" +Plot $C$ and $X$ simulated with the optimized parameter value. Use `ylim=(0, 4)` and `linewidth=2` as options. The dashed line indicates $C = 0.28\;\mathrm{kg\,m^{-3}}$. +""" + +# ╔═╡ 81429279-4190-41d1-a72a-20da0ce90528 +# begin +# missing +# plot!(osol, linestyle=:dash, linewidth=1, label=:none, color=[:orange :blue]) +# hline!([0.28], linestyle=:dash, linewidth=2, color=:orangered, label="C=0.28") +# end + +# ╔═╡ 6589acfd-1d81-4c10-adea-34ca7fa1ab5d +md""" +!!! question + Does the value of $C$ now respect the limit in the concentration? Draw your conclusion. +""" + +# ╔═╡ 7c7d99b9-77b9-4c08-a74e-54eaa7d187ec +md""" +- Conclusion: missing +""" + +# ╔═╡ Cell order: +# ╟─c1bc698d-41ee-45e6-b17d-29f0d53557a1 +# ╠═f08fa69c-a744-11ef-0e79-3daf5bf297ea +# ╠═e6c97e37-d062-4b65-96a4-bac0dab220d8 +# ╠═0407d891-a46d-4deb-a21a-23833acbcb87 +# ╠═e48dc930-be03-47b2-b9e3-16e854782aec +# ╠═75d55180-bc34-4ff9-891f-cf522aab564e +# ╠═6458329f-73dd-4cb0-8da4-90678875a1f0 +# ╟─dfe77a8c-a8db-46d7-9a4d-3b00413d383b +# ╟─c0c83df7-a9cc-4bde-b6ec-038a423b0d90 +# ╟─bbd50cc5-032a-4219-bcee-91145935a7c4 +# ╟─a5f79c20-f62e-4df2-be79-b4f2141ced5e +# ╟─c9b86375-8750-4451-bdfe-10716b72d685 +# ╠═8708de16-3532-4352-b211-c092f95c82d3 +# ╟─2bfabcef-183d-4c73-a19b-121a3494c150 +# ╠═68c7e017-dd17-4e8c-9c1a-ff96da009386 +# ╟─10c73294-a32b-4aa3-80a8-10785e5eab8f +# ╠═fee917dd-7ab5-4fda-b1b7-87ee61e21f19 +# ╠═4fd2abc8-24d8-4864-a89d-7a23807aa41d +# ╟─e986a04a-c9a9-44a8-bea3-cf20d863ba3a +# ╠═d732b1ad-b3c3-43bc-8fbf-a6b2b89566d2 +# ╟─08ebcb95-8603-4579-879e-810b1494b013 +# ╟─d248f64e-ebba-4443-9c49-ff0290aa7810 +# ╠═fe02a755-5b00-4d80-a511-fec115b42964 +# ╟─67481927-0d03-4da9-af6c-9afa409fc006 +# ╠═fadd372a-a665-4b16-9b6d-e32cb7f25d7f +# ╟─734e4d51-95a7-464e-9a23-5ad6c8715d65 +# ╠═15ce9889-a437-46c8-9062-74b8d234a8bd +# ╟─b8a48461-3882-45f6-980c-38d650ac52c7 +# ╟─a4c57b64-6a7d-4bd4-8bb2-578923e184d2 +# ╠═b1e18139-5277-4f06-b1f8-b0f5f11c41d8 +# ╟─ad6d8fe6-e62f-4c67-8d63-4ee13b928ad0 +# ╠═e2ffba9e-aaf2-4540-84cf-8b7297ae9285 +# ╟─70871ee8-b0a4-4a9a-af39-5a63459b55f7 +# ╠═34309734-3751-47e0-a602-d113ffaae510 +# ╟─a0e735ad-09c2-4aa8-bc41-b294a9d56ea8 +# ╠═f62898d5-1b8d-4350-8655-78aa3decb2a2 +# ╟─7eb5df9c-a475-4812-81c3-e43484c82242 +# ╟─5a695734-677f-4bf6-a703-8e22382b7529 +# ╠═b6bac48a-4a3d-47e4-90ea-788ca20dadff +# ╟─b3a40556-0c00-4f6d-8cd9-c5fca79d8bbf +# ╠═2df409ef-bd95-4ac3-a2b8-c5e17c490eba +# ╟─70cafd87-63f7-4674-ae49-43d422fdeae7 +# ╠═ef20f8b8-4527-4f02-b449-fa67b68bbf65 +# ╟─ee1ffc12-55a1-47ef-ac5b-33148706a09b +# ╠═afc035be-075b-464b-8ba2-20235082f005 +# ╟─3ee8121e-3e78-4901-a32d-f04d0c6a0996 +# ╠═98a157a1-8c20-474d-acb8-00373ee6d224 +# ╟─ceb146c9-a09a-458b-b7d8-3bb7d3de38e0 +# ╠═e275df05-5c77-4c17-ad2e-503574596c31 +# ╟─cd515dad-44fb-4af2-b933-805ef76be9b3 +# ╠═a4388f06-1223-4815-a557-9b9c3ec232bb +# ╠═ec7bf654-b275-4cfd-a819-d82bdc1be93b +# ╟─82809c26-4cab-405e-8107-a8a43e81f699 +# ╠═81429279-4190-41d1-a72a-20da0ce90528 +# ╟─6589acfd-1d81-4c10-adea-34ca7fa1ab5d +# ╟─7c7d99b9-77b9-4c08-a74e-54eaa7d187ec diff --git a/src/exercises/probabilistic_selection.jl b/src/exercises/probabilistic_selection.jl new file mode 100644 index 00000000..99b528cd --- /dev/null +++ b/src/exercises/probabilistic_selection.jl @@ -0,0 +1,643 @@ +### A Pluto.jl notebook ### +# v0.20.4 + +#> [frontmatter] +#> order = "43" +#> title = "8. Probability selection" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Probability selection" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ a18638d0-138d-11f0-0571-33060f7da7ba +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ fe265128-33a2-40d1-ab71-bc5c53979a54 +using Turing, StatsPlots + +# ╔═╡ c870d28d-7b2e-48f8-8990-b2cc0943cb09 +using Optim, StatsBase + +# ╔═╡ 2a73d23c-73fc-4845-b359-c6fe22077140 +using PlutoUI + +# ╔═╡ fb07436c-cc5d-4721-8a80-d7f7201721d7 +md"# Model selection" + +# ╔═╡ a397f48e-4228-435e-af13-c2bc71c8cb05 +TableOfContents() + +# ╔═╡ a6635715-52e3-44e7-9c00-0be751f830d6 +md"## Who's that distribution?" + +# ╔═╡ 473c003a-a34b-4e92-8a82-99450514d755 +md""" +You decide to turn your life around and invest all your money into **clams**, or more specifically, **pearl farming**. Before setting up your full-scale farm, you decide to test the pearl-producing capabilities of different species of mollusk. You cultivate 10 different species, wait a year, and collect and measure the resulting pearls. + +You want to compare the species by **fitting a distribution** to the pearl sizes. This way you can compare average size, expected deviation and the probability to get a really big pearl. **However, you don't know what distribution the pearl sizes follow.** +Since they're positive real numbers, 2 good candidates are the `Exponential` and `LogNormal` distributions. +""" + +# ╔═╡ a2dec4e7-7c16-4ae5-974e-8c01ec31507a +md""" +!!! question + For every molluks species, does the data follow an Exponential or a LogNormal distribution? +""" + +# ╔═╡ c4e09491-fd0a-43e7-8e74-04832a045a48 +md""" +![Picture of a black pearl in its shell](https://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Black_pearl_and_his_shell.jpg/1280px-Black_pearl_and_his_shell.jpg) +> Source: Brocken Inaglory (Wikipedia) +""" + +# ╔═╡ aba7efa9-a0c3-4da4-82a2-bb7efc6b9e22 +md"### Data" + +# ╔═╡ c8b56408-0710-4646-bb2b-e336e7f689c2 +# ╠═╡ disabled = true +# ╠═╡ skip_as_script = true +#=╠═╡ +begin + +function generate_point() + firstdistr = rand() < 0.5 + if firstdistr + medist = Exponential(rand(Uniform(0.1, 10))) + else + medist = LogNormal(rand(Uniform(0.1, log(10))), rand(Uniform(0.1, 1.0))) + end + n_samples = rand(Poisson(15)) + samples = rand(medist, n_samples) .|> x -> round(x, digits = 2) + return samples +end + +distr_data = [generate_point() for _ in 1:10]; + +end; + ╠═╡ =# + +# ╔═╡ 19dd461d-cc03-41c9-a3d1-fcd477d08eb0 +distr_data = [[5.23, 2.79, 5.81, 4.36, 7.46, 4.46, 0.83, 6.45, 6.2, 6.53, 6.24, 8.72, 3.15], [1.12, 1.04, 0.09, 0.06, 0.67, 0.33, 0.41, 0.87, 1.23, 4.28, 7.46, 1.21, 0.19, 0.3, 0.59, 1.74, 0.66, 5.97, 0.3, 1.43, 1.11], [0.79, 3.37], [6.84, 11.28, 9.32, 6.27, 6.73, 10.28, 13.69, 8.32, 6.95], [0.48, 8.69, 3.92], [1.53, 1.83, 1.86, 0.87, 1.53, 2.51, 2.14, 1.82, 0.28, 3.57, 0.42, 1.67, 2.39, 4.18], [6.0, 2.37, 14.05, 4.01, 8.51, 5.29, 5.24, 18.01, 2.65, 8.91, 6.37, 2.54], [0.58, 2.41, 12.87, 14.67, 3.97, 13.8, 2.54, 4.7, 17.6, 18.3, 11.16, 0.81, 18.86, 2.3], [1.07, 0.6, 2.24, 0.02, 13.28, 4.88, 0.22, 18.54, 2.81, 2.97, 9.29, 2.98, 23.94, 0.39, 29.25, 1.05, 5.52, 0.39, 4.81, 3.73, 0.49], [8.39, 10.45, 1.93, 12.18, 3.26, 5.12, 8.3, 4.09, 20.41, 0.61, 18.31]]; + +# ╔═╡ 01a530db-5e18-4540-99a9-eecd9e61e1af +md"You can choose the mollusk species here and see the data for its pearl sizes." + +# ╔═╡ 6d39fbde-81c5-4786-99e1-b587bd15f94c +md"Mollusk species" + +# ╔═╡ fce554e2-a6da-4046-ab4e-9153e27aef7a +@bind distr_index Slider(1:10, show_value = true) + +# ╔═╡ 7b1ce376-d796-4913-a488-ee921bd13855 +pearlsizes = distr_data[distr_index] + +# ╔═╡ 712756bc-0408-45a6-931b-eec07b6e052f +histogram(pearlsizes, bins = 0:ceil(maximum(pearlsizes))) + +# ╔═╡ b4de93ea-a919-41c2-bb9c-bc1beccc9ea8 +md"### Model definition" + +# ╔═╡ 5a6f8d49-3841-46ff-a826-88ba0bed868b +md""" +We need to define a model for the two candidate distributions. The likelihood was already given above. For the priors, you can assume the following: +- Exponential model + - μ ~ `Uniform(0, 10)` +- LogNormal model + - μ ~ `Uniform(0, log(10))` + - σ ~ `Uniform(0, 1)` +""" + +# ╔═╡ 88edd0b1-738b-4a86-8995-abdfcc2bcc85 +md""" +!!! note + The `LogNormal` distribution is a bit weird: `LogNormal(μ, σ)` gives the distribution of **the exponential** of a normally distributed value with mean μ and standard deviation σ: + ```math + \begin{gather} + X \sim \text{Normal}(μ, σ) \, , + \\ \Rightarrow \text{exp}(X) \sim \text{LogNormal}(μ, σ) \, . + \end{gather} + ``` + This means that **μ** is not actually the mean of a `LogNormal(μ, σ)`, but something closer to **log(μ)** (it's complicated). Hence the `log(10)` in the prior above. +""" + +# ╔═╡ 05622800-ccd9-4aa8-9a95-a1fbe34aa76c +@model function expon(num_pearls) + μ_exp ~ missing + pearls = zeros(num_pearls) + for i in 1:num_pearls + pearls[i] ~ missing + end +end + +# ╔═╡ c5b042bb-c1d9-4b0e-a2bd-a2c367019da3 +@model function lognorm(num_pearls) + μ_lognorm ~ missing + σ_lognorm ~ missing + pearls = zeros(num_pearls) + for i in 1:num_pearls + pearls[i] ~ missing + end +end + +# ╔═╡ c72cc55c-a5f7-47f5-b5c3-49af9a5db50d +md"Instantiate the models and condition them on the available data." + +# ╔═╡ 88306e52-d975-4bd5-a8f0-7e9293c9fb82 +expmodel = missing + +# ╔═╡ a7cf9228-7515-4d53-b689-bc711283fecc +lognormmodel = missing + +# ╔═╡ 811ee6ce-bb0f-4bcf-95be-6de95ab9166b +md"### Maximum likelihood" + +# ╔═╡ 392ad62d-b7ef-4951-b729-01e41684cbcf +md""" +Determine the maximum likelihood estimation (MLE) of the parameter values given the data, using the `NelderMead()` algorithm. Plot the fitted parameters on the data for a visual comparison. +""" + +# ╔═╡ 3b16c753-a2ba-4b59-a776-b35c4dd2e927 +exp_res = missing + +# ╔═╡ 1ce90419-083a-475a-8334-32d73dafb606 +exp_mean = missing + +# ╔═╡ 654d0ec1-92b0-4220-91a8-607978f1cf4f +lognorm_res = missing + +# ╔═╡ 17e90886-1a41-4c19-b93a-96d8bd7a075f +lognorm_mean = missing + +# ╔═╡ 27883caa-5b08-4f93-8d1a-ae3d85faa9d1 +lognorm_spread = missing + +# ╔═╡ 62f000f3-6f21-4c1e-ab37-29a122b84088 +begin + histogram(pearlsizes, normalize = :pdf) + # add plot of best fit exponential distribution +end + +# ╔═╡ 7dc215e7-83ef-430e-9f99-24cada961964 +begin + histogram(pearlsizes, normalize = :pdf) + # add plot of best fit LogNormal distribution +end + +# ╔═╡ abffd9fb-dd0a-4145-8b7d-8e233e498735 +md"### Bayes factor" + +# ╔═╡ 1adee574-150d-4df6-94e0-6aa20ad92389 +md""" +Compare both models using the Bayes factor $K$. Start off by calculating the model evidence $P(D \mid M)$ of the data $D$ for each model $M$, approximating the integral with a [Riemann sum](https://en.wikipedia.org/wiki/Riemann_sum): +""" + +# ╔═╡ 461c10b2-13f6-442c-ae29-a5f5116a0d01 +md""" +```math +P(D \mid M) =\int_{\theta\in\Theta} P(D \mid M, \theta) \, P(\theta) \, d \theta \approx \sum_{i} P(D \mid M, \theta_i) \, P(\theta_i) \, \Delta \theta_i +``` +""" + +# ╔═╡ feee9ca3-05c5-480b-8cb9-19a3e0c536c1 +md""" +The figure below illustrates the different probabilities involved. The red curve is the product of the two curves above, and the area underneath it is the model evidence we want to calculate. +""" + +# ╔═╡ 259e3755-eac2-4bba-8c15-a54e326f365d +prior_exp(m) = exp(logprior(expmodel, (μ_exp=m,))); + +# ╔═╡ 85a07f1b-df87-4531-ba2a-ccae822379c4 +likelihood_exp(m) = exp(loglikelihood(expmodel, (μ_exp=m,))); + +# ╔═╡ 8fce7eb3-1371-4d3b-b9cb-8730e18d2be4 +posterior_exp(m) = exp(logjoint(expmodel, (μ_exp=m,))) ; + # prior * likelihood: not yet normalized with evidence! + +# ╔═╡ 304b0c2e-7169-403d-baf3-141b0b35f560 +let + xs = 0.1:0.1:15 + ys = [posterior_exp(x) for x in xs] + + p_likelihood = plot(x -> likelihood_exp(x), xlims = (0, 15), + label = "Likelihood: P(D | M, μ)", color = :blue, width = 2 + ) + p_prior = plot(prior_exp, label = "Prior: P(μ)", color = :cyan, width = 2, xlims = (0, 15)) + p_post = plot(xs, ys, label = "Unnormalized posterior: P(D| M)", + color = :red, width = 2, line = :dash, xlims = (0, 15), xlabel = "μ_exp", + ribbon = (ys, zeros(length(xs))), + yticks = round.(0:maximum(ys)/10:maximum(ys), sigdigits = 1) + ) + plot(p_likelihood, p_prior, p_post, ylabel = "density", plottitle = "Evidence", layout = (3, 1)) + +end + +# ╔═╡ 6d39160d-5e5a-4702-bd3f-617d6e7c5513 +Δm = 0.1 + +# ╔═╡ 89547bb1-9027-47c2-a8c0-0212a833879f +begin + evidence_exp = 0.0 + for m in 0.1:Δm:10 + likelihood_per_point = [missing for pearlsize in pearlsizes] + likelihood = missing + prior = missing + evidence_exp += likelihood * prior * Δm + end + println(evidence_exp) +end + +# ╔═╡ 3a0d70d3-92d6-4254-b77b-58b75947548d +Δs = 0.01 + +# ╔═╡ c3fc3e53-3f60-4444-a99c-3a0ef7ce5a94 +begin + evidence_lognorm = 0.0 + for m in 0.1:Δm:log(10) + for s in 0.1:Δs:1.0 + likelihood_per_point = [ + missing + for pearlsize in pearlsizes + ] + likelihood = missing + prior = missing + evidence_lognorm += likelihood * prior * Δm * Δs + end + end + println(evidence_lognorm) +end + +# ╔═╡ 89683590-7f48-4ee8-bc5c-46d37fc0067b +md""" +Now calculate the Bayes factor as follows: +```math +K = \frac{P(M_2 \mid D)}{P(M_1 \mid D)} = \frac{P(D \mid M_2) \, P(M_2)}{P(D \mid M_1) \, P(M_1)} +``` +""" + +# ╔═╡ 8c63219d-e520-40b7-a4b3-c6e2ffc7b17e +P_M_exp = 0.5 + +# ╔═╡ 84f99a01-f3e5-4197-998d-2bdec0d53fb1 +P_M_lognorm = 1 - P_M_exp + +# ╔═╡ 37f977de-f1a9-4a83-85f1-8dadf7910025 +bayes_factor = missing + +# ╔═╡ a7fd0d4d-e0de-4f4b-84a8-64c94a300fba +md""" +Another comparison we can make between the models is calculating whether the first model is the correct one: +```math +\begin{align} +P(M_1 \mid D) &= \frac{P(D \mid M_1) \, P(M_1)}{P(D)} \, , +\\&= \frac{P(D \mid M_1) \, P(M_1)}{P(D \mid M_1) \, P(M_1) + P(D \mid M_2) \, P(M_2)} \, . +\end{align} +``` +""" + +# ╔═╡ ce13fb27-8b5a-4efd-b415-4d0bfd6d3a6a +P_M_exp_cond_D = missing + +# ╔═╡ 5b34c046-bc87-474d-8c20-b5732c0de219 +md"### AIC" + +# ╔═╡ cc4e9632-53ab-48c9-b179-ca2b10cbfabc +md""" +Using the likelihoods calculated above, calculate the Akaike Information Criterion (AIC) for both models: +""" + +# ╔═╡ 964706fe-e48c-4cf6-8e80-484867719bf6 +md""" +```math +\text{AIC} = 2 k - 2 \, \text{log}(L) +``` +""" + +# ╔═╡ 03a7e04d-c16f-468d-ba84-314effa6d838 +md""" +!!! tip + To get your model's best possible **AIC** value, you need the highest possible loglikelihood. By definition, this corresponds with your **MLE**. If `opt_res` is the variable returned by the `optimize` function, you can get the correspondig maximal loglikelihood using `opt_res.lp`. +""" + +# ╔═╡ 2e1fb72c-fea9-4de1-bed5-ee947306bc65 +AIC(num_params, loglikelihood) = missing + +# ╔═╡ 3553ed7d-07a0-418a-8d5f-6791e62d9e10 +AIC_exp = missing + +# ╔═╡ 257f0675-c5fe-45c5-aba0-ac808efb0ead +AIC_lognorm = missing + +# ╔═╡ 1936f7bb-ad6b-4759-b37e-a9b73a8dabc2 +md"### BIC" + +# ╔═╡ 3867fe78-e317-4d89-9a0b-45068597c015 +md""" +Do the same for the (dissapointingly non-Bayesian) Bayesian Information Criterion (BIC): +""" + +# ╔═╡ cd826270-0b8e-4437-931a-5cb6f01a1e56 +md""" +```math +\text{BIC} = k \, \text{log}(n) - 2 \, \text{log}(L) +``` +""" + +# ╔═╡ 01648f73-8b75-4158-a7ea-00e55ece7548 +BIC(num_observations, num_params, loglikelihood) = missing + +# ╔═╡ 1e685af1-6abe-4849-b07b-2ab1da058edc +BIC_exp = missing + +# ╔═╡ 23a7d39c-0c0f-43fb-88be-ddfaba988a2c +BIC_lognorm = missing + +# ╔═╡ 4cf6a2d0-e58c-4b80-abf9-525097d75f35 +md"## Overlapping cells" + +# ╔═╡ a1f2199f-29ee-4908-9ad6-bdebcf043de7 +md""" +When counting cells, overlapping cells are a common cause of errors. Here we will tackle a simplified version of the problem where we try to distinguish whether a point cloud originates from one or two circles. +""" + +# ╔═╡ 5407d952-c5a9-4145-8e4a-4b3bb65e6c19 +md""" +![Overlapping cell picture](https://media.springernature.com/full/springer-static/image/art%3A10.1007%2Fs11334-022-00478-y/MediaObjects/11334_2022_478_Fig1_HTML.png?as=webp) +> **Source:** Efficient detection and partitioning of overlapped red blood cells using image processing approach (Dhar 2022) +""" + +# ╔═╡ 54dc6f54-fb0e-4d80-b32c-c5ce8f8974c6 +md"### Data" + +# ╔═╡ bb19f97a-4778-4fc5-8739-1f03bc5416a8 +cell_data = [[0.68 -1.34 -0.53 0.5 -1.85 0.68 0.57 -1.55 0.16 -0.04 1.06 1.34 1.41 -1.67 -1.56 -0.51; -0.36 -1.73 -0.4 -1.5 -0.97 1.62 -3.71 -1.98 0.9 1.55 -1.82 -4.56 2.46 2.18 -1.23 -1.06], [-0.3 -0.29 0.99 2.58 -0.38 -2.16 -1.51 -0.36 0.9 1.27 -0.3 0.77 -0.6 -0.94; 0.73 -0.63 -1.67 0.39 2.15 0.29 -0.91 -2.4 -0.18 2.23 2.05 1.49 -0.16 0.49], [-3.23 -1.51 -2.78 1.1 2.52 0.76 -1.34 -3.79 0.39 0.76; 0.08 0.63 -0.11 2.2 1.48 2.94 -0.82 -0.87 0.38 2.21], [-1.56 0.53 1.02 -0.53 -2.08 -1.22 -0.12 1.04 -0.95 0.74; -0.18 0.04 -1.19 -0.76 -0.58 -0.69 0.88 -1.1 -0.93 1.72], [0.11 0.57 -2.06 1.59 1.45 1.11 -2.2 1.24 0.89 0.67 -0.17 1.21 -0.89 1.01 -0.01 1.9 1.26 -1.48 0.6 -0.74 1.6; -0.45 -0.56 0.53 -0.45 -2.05 2.68 -1.75 0.35 -0.67 -0.44 -0.4 -0.79 -2.12 -2.59 -1.31 -1.66 0.54 -0.2 -3.03 -0.16 -0.56], [-0.21 0.36 -0.89 -0.83 -0.36 -1.75 -2.84 0.46 1.1 3.34 -1.61 0.08; -0.38 -2.23 0.27 -1.6 -2.72 -1.87 -1.48 -0.1 -0.83 0.26 0.46 0.57], [3.15 -0.1 0.77 1.62 -0.5 0.28 0.66 -0.01 1.93 -0.15 -0.94 -0.42 1.79 0.27 -0.01 1.7 0.96 2.35 1.61; -0.05 0.28 0.06 -1.26 1.64 -0.48 0.42 1.47 1.05 0.03 -0.65 -0.74 0.26 0.89 1.43 -0.83 -1.55 -0.48 1.72], [2.16 1.24 3.64 -1.18 1.11 2.4 1.19 1.14 1.26 1.11 0.95 2.14 1.88 1.5 2.43 0.64 1.84 0.05 -0.83 1.5 4.44; -1.13 -0.33 -0.98 0.34 -3.2 0.41 0.77 0.1 1.33 0.76 -0.73 -2.07 0.64 -1.96 -0.7 -1.34 0.84 -2.28 -0.95 -0.28 0.24], [0.38 2.4 2.14 -0.65 -0.23 1.37 0.7 0.74 -0.17 2.53 -1.42 -0.03; 1.25 -2.24 0.0 1.12 -2.23 0.93 -0.86 0.89 -1.61 0.93 -1.51 1.58], [1.53 0.05 -0.39 -1.14 0.04 0.36 0.78 -3.02 -0.28 -2.49 -0.3 -0.55 -1.58 -0.24; 2.5 1.84 -0.67 -1.69 1.57 0.57 1.96 -1.94 3.22 1.47 0.57 0.45 -0.23 0.93]]; + +# ╔═╡ eb552ae5-f32d-4383-8aae-99999ce42552 +md"You can choose the cell picture and visualize the data here." + +# ╔═╡ d3f98c65-de66-4a3c-b087-c2ef34340110 +md"Picture idx" + +# ╔═╡ 0533c731-82b2-4dab-8c5e-d5913ee0f4f3 +@bind picture_idx Slider(1:length(cell_data), show_value = true) + +# ╔═╡ a4c89c22-636c-4621-ac28-b285cf2ecbef +xs, ys = eachrow(cell_data[picture_idx]); + +# ╔═╡ 272327fd-a587-4c14-80cc-d581ff2d7f27 +scatter(xs, ys, xlims = (-5, 5), ylims = (-5, 5)) + +# ╔═╡ f11ca2fe-dc50-41b5-bf9c-299f1b18a9e2 +md"### Model definition" + +# ╔═╡ 65a23129-7fc0-48aa-a9f0-c9f885b7e4c2 +md""" +The model for one cell is defined as follows: +- The points originate from one pointcloud with a centre (`xm`, `ym`). +- `xm` and `ym` both follow a standard Normal distribution. +- All x-values follow a Normal distribution around `xm` with $σ = 1$. +- All y-values follow a Normal distribution around `ym` with $σ = 1$. +""" + +# ╔═╡ c8da1bc6-4455-4577-98bb-d7dd41ff4f06 +@model function singlecell(n) # n is number of points + xm ~ missing + ym ~ missing + + missing +end + +# ╔═╡ 523d6280-7147-4652-b593-cdd802d80b4e +md""" +The model for two cells is very similar: +- The points originate from one of two pointclouds, one with centre (`xm1`, `ym1`), the other with centre (`xm2`, `ym2`). +- `xm1`, `ym1`, `xm2` and `ym2` all follow standard Normal distributions. +- All x-values follow either a Normal distribution ($σ = 1$) around `xm1` or `xm2`, with equal chance for either. +- The same idea goes for the y-values. +""" + +# ╔═╡ 740afb3b-4bd6-4516-9a2a-7bbe5f19ccf0 +md""" +!!! hint + To model the likelihood, consider the humble `MixtureModel`. +""" + +# ╔═╡ 166f1c0c-1615-47e7-8538-f19cbdaa6923 +@model function doublecell(n) # n is number of points + missing +end + +# ╔═╡ 36093548-5b29-4bd0-959a-befdd4da3de5 +md"Instantiate and condition the models." + +# ╔═╡ a0502199-3e7f-4b50-b383-2e1bb4ff9d41 +singlemodel = missing + +# ╔═╡ 153ccedb-b437-4906-a2ef-1745b0dbf53e +doublemodel = missing + +# ╔═╡ 29e3dc3a-f2c4-45a6-80a0-01bde4d41d98 +md"### Maximum likelihood" + +# ╔═╡ 5f92804b-49b2-4fa3-a10a-38c66feb3ce8 +function plotsinglecell(xm, ym; bounds = 5) + mydist = MvNormal([xm, ym], [1.0 0.0; 0.0 1.0]) + + xs = -bounds:0.1:bounds + ys = -bounds:0.1:bounds + + f(x,y) = pdf(mydist, [x, y]) + contourf(xs, ys, f, xlims = (-bounds, bounds), ylims = (-bounds, bounds), + color = :viridis, aspect_ratio = :equal, legend = false, + title = "Single cell model" + ) +end + +# ╔═╡ 0977b0e1-092a-4934-9df3-674cdf12b367 +function plotdoublecell(xm1, xm2, ym1, ym2; bounds = 5) + mydist = MixtureModel( + [ + MvNormal([xm1, ym1], [1.0 0.0; 0.0 1.0]), + MvNormal([xm2, ym2], [1.0 0.0; 0.0 1.0]), + ] + ) + + xs = -bounds:0.1:bounds + ys = -bounds:0.1:bounds + + f(x,y) = pdf(mydist, [x, y]) + contourf(xs, ys, f, xlims = (-bounds, bounds), ylims = (-bounds, bounds), + color = :viridis, aspect_ratio = :equal, legend = false, + title = "Two cells model" + ) +end + +# ╔═╡ 15b5d7d1-cf91-4024-a7fd-b8b8f0563dff +md""" +Determine the maximum likelihood estimation (MLE) of the parameter values given the data, using the `NelderMead()` algorithm. +""" + + +# ╔═╡ 8942d992-5084-4c2c-9cbc-c442b0381922 +singleres = missing + +# ╔═╡ 99a8a4b4-bbcd-4321-ba41-4f307c04af8e +begin + single_xm = missing + single_ym = missing +end + +# ╔═╡ 51ce7374-c7d8-4a32-815d-1c2ff9ba9970 +doubleres = missing + +# ╔═╡ 5a0860a2-8a61-4abb-acaf-3ea3cad0c286 +begin + double_xm1 = missing + double_xm2 = missing + double_ym1 = missing + double_ym2 = missing +end + +# ╔═╡ 73d259c6-f776-4586-a24f-3ecc368e28ae +md"Visualise the results" + +# ╔═╡ 1a00a07e-db38-4761-b881-7f475881ff4f +begin + plotsinglecell(single_xm, single_ym) + scatter!(xs, ys) +end + +# ╔═╡ 629280c7-b6c8-423d-92e9-815eb76a78f0 +begin + plotdoublecell(double_xm1, double_xm2, double_ym1, double_ym2) + scatter!(xs, ys) +end + +# ╔═╡ 4e28f87c-0953-48c4-8e3a-d7e853cae816 +md"### AIC" + +# ╔═╡ ec4ff409-f28c-4640-8bdb-abe21252afaf +md"Using the MLE results from the previous section, determine the AIC of both models. You can use the implementation from previous exercise." + +# ╔═╡ fef14ffe-3399-48d8-a0cb-998826cdfda4 +AIC_single = missing + +# ╔═╡ 40077482-7c98-4ca6-b441-ed3ebd893369 +AIC_double = missing + +# ╔═╡ Cell order: +# ╟─fb07436c-cc5d-4721-8a80-d7f7201721d7 +# ╠═a18638d0-138d-11f0-0571-33060f7da7ba +# ╠═fe265128-33a2-40d1-ab71-bc5c53979a54 +# ╠═c870d28d-7b2e-48f8-8990-b2cc0943cb09 +# ╠═2a73d23c-73fc-4845-b359-c6fe22077140 +# ╠═a397f48e-4228-435e-af13-c2bc71c8cb05 +# ╟─a6635715-52e3-44e7-9c00-0be751f830d6 +# ╟─473c003a-a34b-4e92-8a82-99450514d755 +# ╟─a2dec4e7-7c16-4ae5-974e-8c01ec31507a +# ╟─c4e09491-fd0a-43e7-8e74-04832a045a48 +# ╟─aba7efa9-a0c3-4da4-82a2-bb7efc6b9e22 +# ╟─c8b56408-0710-4646-bb2b-e336e7f689c2 +# ╟─19dd461d-cc03-41c9-a3d1-fcd477d08eb0 +# ╟─01a530db-5e18-4540-99a9-eecd9e61e1af +# ╟─6d39fbde-81c5-4786-99e1-b587bd15f94c +# ╟─fce554e2-a6da-4046-ab4e-9153e27aef7a +# ╠═7b1ce376-d796-4913-a488-ee921bd13855 +# ╟─712756bc-0408-45a6-931b-eec07b6e052f +# ╟─b4de93ea-a919-41c2-bb9c-bc1beccc9ea8 +# ╟─5a6f8d49-3841-46ff-a826-88ba0bed868b +# ╟─88edd0b1-738b-4a86-8995-abdfcc2bcc85 +# ╠═05622800-ccd9-4aa8-9a95-a1fbe34aa76c +# ╠═c5b042bb-c1d9-4b0e-a2bd-a2c367019da3 +# ╟─c72cc55c-a5f7-47f5-b5c3-49af9a5db50d +# ╠═88306e52-d975-4bd5-a8f0-7e9293c9fb82 +# ╠═a7cf9228-7515-4d53-b689-bc711283fecc +# ╟─811ee6ce-bb0f-4bcf-95be-6de95ab9166b +# ╟─392ad62d-b7ef-4951-b729-01e41684cbcf +# ╠═3b16c753-a2ba-4b59-a776-b35c4dd2e927 +# ╠═1ce90419-083a-475a-8334-32d73dafb606 +# ╠═654d0ec1-92b0-4220-91a8-607978f1cf4f +# ╠═17e90886-1a41-4c19-b93a-96d8bd7a075f +# ╠═27883caa-5b08-4f93-8d1a-ae3d85faa9d1 +# ╠═62f000f3-6f21-4c1e-ab37-29a122b84088 +# ╠═7dc215e7-83ef-430e-9f99-24cada961964 +# ╟─abffd9fb-dd0a-4145-8b7d-8e233e498735 +# ╟─1adee574-150d-4df6-94e0-6aa20ad92389 +# ╟─461c10b2-13f6-442c-ae29-a5f5116a0d01 +# ╟─feee9ca3-05c5-480b-8cb9-19a3e0c536c1 +# ╟─304b0c2e-7169-403d-baf3-141b0b35f560 +# ╠═259e3755-eac2-4bba-8c15-a54e326f365d +# ╠═85a07f1b-df87-4531-ba2a-ccae822379c4 +# ╠═8fce7eb3-1371-4d3b-b9cb-8730e18d2be4 +# ╠═6d39160d-5e5a-4702-bd3f-617d6e7c5513 +# ╠═89547bb1-9027-47c2-a8c0-0212a833879f +# ╠═3a0d70d3-92d6-4254-b77b-58b75947548d +# ╠═c3fc3e53-3f60-4444-a99c-3a0ef7ce5a94 +# ╟─89683590-7f48-4ee8-bc5c-46d37fc0067b +# ╠═8c63219d-e520-40b7-a4b3-c6e2ffc7b17e +# ╠═84f99a01-f3e5-4197-998d-2bdec0d53fb1 +# ╠═37f977de-f1a9-4a83-85f1-8dadf7910025 +# ╟─a7fd0d4d-e0de-4f4b-84a8-64c94a300fba +# ╠═ce13fb27-8b5a-4efd-b415-4d0bfd6d3a6a +# ╟─5b34c046-bc87-474d-8c20-b5732c0de219 +# ╟─cc4e9632-53ab-48c9-b179-ca2b10cbfabc +# ╟─964706fe-e48c-4cf6-8e80-484867719bf6 +# ╟─03a7e04d-c16f-468d-ba84-314effa6d838 +# ╠═2e1fb72c-fea9-4de1-bed5-ee947306bc65 +# ╠═3553ed7d-07a0-418a-8d5f-6791e62d9e10 +# ╠═257f0675-c5fe-45c5-aba0-ac808efb0ead +# ╟─1936f7bb-ad6b-4759-b37e-a9b73a8dabc2 +# ╟─3867fe78-e317-4d89-9a0b-45068597c015 +# ╟─cd826270-0b8e-4437-931a-5cb6f01a1e56 +# ╠═01648f73-8b75-4158-a7ea-00e55ece7548 +# ╠═1e685af1-6abe-4849-b07b-2ab1da058edc +# ╠═23a7d39c-0c0f-43fb-88be-ddfaba988a2c +# ╟─4cf6a2d0-e58c-4b80-abf9-525097d75f35 +# ╟─a1f2199f-29ee-4908-9ad6-bdebcf043de7 +# ╟─5407d952-c5a9-4145-8e4a-4b3bb65e6c19 +# ╟─54dc6f54-fb0e-4d80-b32c-c5ce8f8974c6 +# ╟─bb19f97a-4778-4fc5-8739-1f03bc5416a8 +# ╟─eb552ae5-f32d-4383-8aae-99999ce42552 +# ╟─d3f98c65-de66-4a3c-b087-c2ef34340110 +# ╟─0533c731-82b2-4dab-8c5e-d5913ee0f4f3 +# ╠═a4c89c22-636c-4621-ac28-b285cf2ecbef +# ╟─272327fd-a587-4c14-80cc-d581ff2d7f27 +# ╟─f11ca2fe-dc50-41b5-bf9c-299f1b18a9e2 +# ╟─65a23129-7fc0-48aa-a9f0-c9f885b7e4c2 +# ╠═c8da1bc6-4455-4577-98bb-d7dd41ff4f06 +# ╟─523d6280-7147-4652-b593-cdd802d80b4e +# ╟─740afb3b-4bd6-4516-9a2a-7bbe5f19ccf0 +# ╠═166f1c0c-1615-47e7-8538-f19cbdaa6923 +# ╟─36093548-5b29-4bd0-959a-befdd4da3de5 +# ╠═a0502199-3e7f-4b50-b383-2e1bb4ff9d41 +# ╠═153ccedb-b437-4906-a2ef-1745b0dbf53e +# ╟─29e3dc3a-f2c4-45a6-80a0-01bde4d41d98 +# ╟─5f92804b-49b2-4fa3-a10a-38c66feb3ce8 +# ╟─0977b0e1-092a-4934-9df3-674cdf12b367 +# ╟─15b5d7d1-cf91-4024-a7fd-b8b8f0563dff +# ╠═8942d992-5084-4c2c-9cbc-c442b0381922 +# ╠═99a8a4b4-bbcd-4321-ba41-4f307c04af8e +# ╠═51ce7374-c7d8-4a32-815d-1c2ff9ba9970 +# ╠═5a0860a2-8a61-4abb-acaf-3ea3cad0c286 +# ╟─73d259c6-f776-4586-a24f-3ecc368e28ae +# ╠═1a00a07e-db38-4761-b881-7f475881ff4f +# ╠═629280c7-b6c8-423d-92e9-815eb76a78f0 +# ╟─4e28f87c-0953-48c4-8e3a-d7e853cae816 +# ╟─ec4ff409-f28c-4640-8bdb-abe21252afaf +# ╠═fef14ffe-3399-48d8-a0cb-998826cdfda4 +# ╠═40077482-7c98-4ca6-b441-ed3ebd893369 diff --git a/src/exercises/sens_bitrophic_model.jl b/src/exercises/sens_bitrophic_model.jl new file mode 100644 index 00000000..37d816ee --- /dev/null +++ b/src/exercises/sens_bitrophic_model.jl @@ -0,0 +1,413 @@ +### A Pluto.jl notebook ### +# v0.20.4 + +#> [frontmatter] +#> order = "37" +#> title = "7. Sensitivity bitrophic model" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Sensitivity bitrophic model" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ a3c1055e-b44f-4412-9ca0-f8ec5f972494 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ fd357fe0-0920-11ef-02e4-25a84575c6a2 +using Markdown + +# ╔═╡ 0ddc1082-bf8d-4357-a056-2fa861824b47 +using InteractiveUtils + +# ╔═╡ cce8fcde-069f-49b9-aadd-6abccbcfbace +using Catalyst + +# ╔═╡ b48e4b46-3a07-4ff0-b617-6bc1125f6a1e +using OrdinaryDiffEq, StatsPlots + +# ╔═╡ ddc035a2-3b03-4a54-807c-05a271384ca7 +using ForwardDiff + +# ╔═╡ ceec287b-5622-48d2-9fe1-26b3e84e3f45 +md""" +### Exercise: Bitrophic model - Sensitivity analysis +""" + +# ╔═╡ 2a732f39-0499-4c90-be62-a1e2278e0c8b +md""" +The dynamic relationship between a field crop and a voracious insect population within an ecosystem can be represented by a bitrophic model. Such model typically consists of two variables: the abundance of the field crop, often representing a primary producer such as a plant species, and the population size of the voracious insect, which acts as a consumer feeding on the crop. The differential equations below describe how changes in the crop population affect the growth and behavior of the insect population, and vice versa, under the influence of an insecticide. + +$$\begin{eqnarray*} +\frac{dC}{dt} &= \theta C \left(1-\frac{C}{k}\right)-fCA \\ +\frac{dA}{dt} &= \phi f CA -(1 + p)\, \mu A +\end{eqnarray*}$$ + +Understanding this bitrophic interaction is crucial for predicting the impact of insect predation on crop yields and devising effective strategies for pest management in agriculture and ecological conservation efforts. + +In these equations, $C$ and $A$ are both expressed in $kg/ha$, $\theta=0.2\;d^{-1}$, $k=4000\;kg/ha$, $f=0.001\;ha/(kg\,d)$, the efficiency ratio $\phi=0.2$, and the mortality ratio $\mu=0.1\;d^{-1}$. The crop can be treated with an insecticide which increases the insect's death coefficient by a factor of $p=3$. The factor $p$ depends on the applied insecticide concentration and can therefore be controlled externally. + +At the beginning of a season, $100\;kg$ of the crop and $0.5\;kg$ of insects per $ha$ are present. +""" + +# ╔═╡ 87508f87-42d7-44e7-a0fb-415e6d7190c2 +md""" +Set up a *reaction network* model by analysing the terms in the above differential equations and simulate the evolution of $C$ and $A$ for $200$ days. Next, perform a sensitivity analysis of $C$ and $A$ wrt. the parameters $\theta$, $\phi$ and $p$. +""" + +# ╔═╡ eeb52e5e-f57b-4746-bbfc-7f043fc50e96 +md""" +Set up a *reaction network* model and name it `bitrophic_model`.\ +Hints: +- $C$ is growing (i.e., $C \rightarrow 2C$) at a rate $\theta \left(1-\frac{C}{k}\right)$. +- The insects $A$ eat crops $C$ (i.e., $C + A$) at a rate $f$ resulting in an increase of a factor of $\phi$ more insects (i.e., $(1+\phi)A$). +- The insects $A$ are dying (i.e., $A \rightarrow 0$) at a rate $(1 + p)\, \mu$. +""" + +# ╔═╡ 0b902d89-f0c2-4621-998e-7013931315ae +# bitrophic_model = @reaction_network begin +# missing # Uncomment and complete the instruction +# missing # Uncomment and complete the instruction +# missing # Uncomment and complete the instruction +# end + +# ╔═╡ 92027dcf-9765-4fad-a0a4-f19967ce6bce +md""" +Check out the species and the parameters. +""" + +# ╔═╡ 15dbf491-c5ae-4682-a21d-1a815a957993 +# missing # Uncomment and complete the instruction + +# ╔═╡ 051f1b87-34ec-4e3f-8d4e-c4e42430be8f +# missing # Uncomment and complete the instruction + +# ╔═╡ f11bae81-a855-48ec-90a7-16b4ea1fc30b +md""" +Convert the system to a symbolic differential equations model, name it `osys` and verify, by analyzing the differential equations, that your model is correctly implemented. +""" + +# ╔═╡ 1a19c02c-5d0f-4271-b0e7-7da873030d7e +# osys = missing # Uncomment and complete the instruction + +# ╔═╡ 55eba88f-d4cf-4ccf-b061-51feb3a4290c +md""" +Initialize a vector `u0` with the initial conditions, define the timespan in `tspan` and initialize a vector `param` with the parameter values: +""" + +# ╔═╡ 7769c7a5-5f19-4e1a-8721-41ee03c2024e +# u0 = missing # Uncomment and complete the instruction + +# ╔═╡ 00453a3c-88d1-4cc4-a8f2-d4fa2eed0b66 +# tspan = missing # Uncomment and complete the instruction + +# ╔═╡ 4e1acbdd-65ce-4f5e-b557-633af8a11aa9 +md""" +For clarity, we will use the variables `θ`, `ϕ` and `p` to store the parameter values that are used for the calculation of the sensitivity functions. +""" + +# ╔═╡ 9fda8191-e28d-4d10-b330-c386711a2a73 +θ = 0.2 + +# ╔═╡ ae172c81-539c-4482-9cd2-ab74fd83c26a +ϕ = 0.2 + +# ╔═╡ 28417aea-9c05-4ba0-8b85-b8cccd808238 +p = 3 + +# ╔═╡ 157eae84-9c8a-4d23-9ada-9d53ba520b0a +# params = missing # Uncomment and complete the instruction + +# ╔═╡ eddc01fa-3ab0-4fd0-963f-5704f48778a7 +md""" +Create the ODE problem and store it in `oprob`. Next, solve the ODE problem using `Tsit5()` and `saveat=0.5`, and store the solution in `osol`. Finally plot the results. +""" + +# ╔═╡ 1c185585-7c7a-4073-85f5-8300d3fe19e4 +# oprob = missing; # Uncomment and complete the instruction + +# ╔═╡ 2f1b4426-7235-4caf-84ed-ec6e04eaa34a +# osol = missing # Uncomment and complete the instruction + +# ╔═╡ 63b33638-b967-43a7-b6ff-f6912f9a12e3 +# missing # Uncomment and complete the instruction + +# ╔═╡ fc1ae98d-0126-4eb3-86e6-20342acf0340 +md""" +Interpret your results. Try to answer the following question(s): +""" + +# ╔═╡ 81c6198f-5f69-4d78-b67c-b6e283a6c3b1 +md""" +!!! question + 1. What happens to $C$ and $A$ during the first 30 days? +""" + +# ╔═╡ 9c42da1d-f80a-4ad5-bc4f-caec9a1dadb6 +md"- Answer: missing" + +# ╔═╡ cd1ea360-4fa0-47ee-95ea-5a34dbd0aff9 +md""" +!!! question + 2. Why does $C$ starts to decline around day 40? +""" + +# ╔═╡ 047cc4df-1c6c-4371-a6e9-fe83a402b342 +md"- Answer: missing" + +# ╔═╡ b75a8e32-39c2-41dd-95ff-5cb6de30199b +md""" +!!! question + 3. What happens to $C$ and $A$ from day 50 on, do they finally reach steady state values? +""" + +# ╔═╡ c9caecc4-8d14-4885-b087-54a9226be597 +md"- Answer: missing" + +# ╔═╡ aa07ac11-1d6e-41ea-885d-1a6ae38bd58d +md""" +Write a solution function with as argument a vector of the parameters (that you want the sensitivity on), and that returns the outputs. +""" + +# ╔═╡ 2a858811-9ffd-4dc7-84ba-f7a59421753f +# Uncomment and complete the instruction +# function bitrophic_model_sim(params) + # θ, ϕ, p = missing + # u0 = missing + # tspan = missing + # params = missing + # oprob = missing + # osol = missing + # return missing +# end + +# ╔═╡ e911dc09-c56a-42ed-9a09-9c4bba1aa5f6 +md""" +Make two functions based on the solution function that each returns a single output, hence, one function that returns the output $C$, and another function that returns the output $A$. +""" + +# ╔═╡ 2e43110a-d948-4e6f-9ed9-a35101ba223a +# bitrophic_model_sim_C(params) = missing # Uncomment and complete the instruction + +# ╔═╡ 298604da-3207-40db-8535-f628d17a0f39 +# bitrophic_model_sim_A(params) = missing # Uncomment and complete the instruction + +# ╔═╡ 76b256dc-bdec-4fb8-95c7-b32df03ba8b1 +md""" +Make the time vector. +""" + +# ╔═╡ 3d1f2c90-2e4e-4fa1-af41-9d44d00e5090 +# t_vals = missing # Uncomment and complete the instruction + +# ╔═╡ c2ca4b68-39a6-45f7-a2a1-0f5c4903ed07 +md""" +Compute the two outputs $C$ and $A$ for the given parameter values. +""" + +# ╔═╡ 7bc637be-8238-43f4-b190-b46833fc9d34 +# C_sim = missing # Uncomment and complete the instruction + +# ╔═╡ 127d8761-97d6-4fcb-b633-98ee30ebc617 +# A_sim = missing # Uncomment and complete the instruction + +# ╔═╡ cda34ccc-e018-4e2c-a4d3-8ed3d0c1624b +md""" +Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouputs $C$ and $A$. Hence, you need to call `ForwardDiff.jacobian` twice. +""" + +# ╔═╡ 55f00963-bfb0-420f-ae6f-d20f93f01b1e +# sens_C = missing # Uncomment and complete the instruction + +# ╔═╡ 4d2c77f2-1a5f-437c-adb6-232155f2cafb +# sens_A = missing # Uncomment and complete the instruction + +# ╔═╡ c261b580-2cc7-4aba-af15-1778f9293278 +md""" +Extract the (absolute) sensitivities of the outputs on the different parameters. +""" + +# ╔═╡ 730de2d2-104b-4f4a-96da-4f96b0445f95 +# Uncomment and complete the instruction +# begin +# sens_C_on_θ = missing +# sens_C_on_ϕ = missing +# sens_C_on_p = missing +# end; + +# ╔═╡ 2d8df796-c362-49d9-b938-26f1917e800b +# Uncomment and complete the instruction +# begin +# sens_A_on_θ = missing +# sens_A_on_ϕ = missing +# sens_A_on_p = missing +# end; + +# ╔═╡ 6b3ee543-e3e2-4fe6-925d-1f201243d014 +md""" +Compute the normalized sensitivities. +""" + +# ╔═╡ 89830894-4481-480b-954d-b24971590eaf +# Uncomment and complete the instruction +# begin +# sens_C_on_θ_rel = missing +# sens_C_on_ϕ_rel = missing +# sens_C_on_p_rel = missing +# end; + +# ╔═╡ c2b5c2c4-3aa6-466d-b106-d0d7aef43bdf +# Uncomment and complete the instruction +# begin +# sens_A_on_θ_rel = missing +# sens_A_on_ϕ_rel = missing +# sens_A_on_p_rel = missing +# end; + +# ╔═╡ 3814640e-ed0b-48a1-b4ac-6b7f04d899f3 +md""" +Plot the sensitivity functions of $C$ and $A$ on $\theta$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`). +""" + +# ╔═╡ bbe8980e-441d-4ac6-abc5-88ff77de9f24 +# missing # Uncomment and complete the instruction + +# ╔═╡ c6eb2f89-2737-4eb0-8fec-dd87ea962051 +md""" +Interpret your results. Try to answer the following question(s): +""" + +# ╔═╡ a8257414-2d6a-4913-9f05-6369c62189e8 +md""" +!!! question + 1. In steady state, does $\theta$ have any influence on $C$? Explain why this could be. +""" + + +# ╔═╡ 68264b8a-a89f-465b-bedb-1b85e960a25e +md"- Answer: missing" + +# ╔═╡ f84e6cdd-be44-464e-865b-24feca38df0f +md""" +!!! question + 2. In steady state, why does $\theta$ have a positive effect on $A$? Explain why this could be. +""" + +# ╔═╡ a7d513eb-66b3-4167-b799-74a37718a315 +md"- Answer: missing" + +# ╔═╡ 882f2f6b-eb58-4c8f-877e-76e460900c0c +md""" +Plot the sensitivity functions of $C$ and $A$ on $\phi$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`). +""" + +# ╔═╡ f76e5c58-12fc-4b2d-8718-e15144fbef98 +# missing # Uncomment and complete the instruction + +# ╔═╡ 0bd84cf5-c0a2-4bb2-b5f4-f16da6eaab04 +md""" +Plot the sensitivity functions of $C$ and $A$ on $p$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`). +""" + +# ╔═╡ cb02ebf8-aee5-47a2-9496-6f63fc123147 +# missing # Uncomment and complete the instruction + +# ╔═╡ 75309ee6-17b6-4ebb-b21c-a5e4a2a97510 +md""" +Interpret your results. Try to answer the following question(s): +""" + +# ╔═╡ 08eaa553-7b74-4f02-9328-b23add528c06 +md""" +!!! question + 1. In steady state, does $\phi$ have a positive or negative effect on $C$? Explain why this could be. +""" + +# ╔═╡ a299b51b-1b7b-4420-9f91-bfe1d8c43158 +md"- Answer: missing" + +# ╔═╡ 58afee9f-1157-45b1-99d9-f796b474975a +md""" +!!! question + 2. In steady state, does $p$ have a positive or negative effect on $C$? Explain why this could be. +""" + +# ╔═╡ 24d198c3-9fd2-4ea3-8ab1-c7f027af78be +md"- Answer: missing" + +# ╔═╡ Cell order: +# ╠═fd357fe0-0920-11ef-02e4-25a84575c6a2 +# ╠═0ddc1082-bf8d-4357-a056-2fa861824b47 +# ╠═a3c1055e-b44f-4412-9ca0-f8ec5f972494 +# ╠═cce8fcde-069f-49b9-aadd-6abccbcfbace +# ╠═b48e4b46-3a07-4ff0-b617-6bc1125f6a1e +# ╠═ddc035a2-3b03-4a54-807c-05a271384ca7 +# ╟─ceec287b-5622-48d2-9fe1-26b3e84e3f45 +# ╟─2a732f39-0499-4c90-be62-a1e2278e0c8b +# ╟─87508f87-42d7-44e7-a0fb-415e6d7190c2 +# ╟─eeb52e5e-f57b-4746-bbfc-7f043fc50e96 +# ╠═0b902d89-f0c2-4621-998e-7013931315ae +# ╟─92027dcf-9765-4fad-a0a4-f19967ce6bce +# ╠═15dbf491-c5ae-4682-a21d-1a815a957993 +# ╠═051f1b87-34ec-4e3f-8d4e-c4e42430be8f +# ╟─f11bae81-a855-48ec-90a7-16b4ea1fc30b +# ╠═1a19c02c-5d0f-4271-b0e7-7da873030d7e +# ╟─55eba88f-d4cf-4ccf-b061-51feb3a4290c +# ╠═7769c7a5-5f19-4e1a-8721-41ee03c2024e +# ╠═00453a3c-88d1-4cc4-a8f2-d4fa2eed0b66 +# ╟─4e1acbdd-65ce-4f5e-b557-633af8a11aa9 +# ╠═9fda8191-e28d-4d10-b330-c386711a2a73 +# ╠═ae172c81-539c-4482-9cd2-ab74fd83c26a +# ╠═28417aea-9c05-4ba0-8b85-b8cccd808238 +# ╠═157eae84-9c8a-4d23-9ada-9d53ba520b0a +# ╟─eddc01fa-3ab0-4fd0-963f-5704f48778a7 +# ╠═1c185585-7c7a-4073-85f5-8300d3fe19e4 +# ╠═2f1b4426-7235-4caf-84ed-ec6e04eaa34a +# ╠═63b33638-b967-43a7-b6ff-f6912f9a12e3 +# ╟─fc1ae98d-0126-4eb3-86e6-20342acf0340 +# ╟─81c6198f-5f69-4d78-b67c-b6e283a6c3b1 +# ╠═9c42da1d-f80a-4ad5-bc4f-caec9a1dadb6 +# ╟─cd1ea360-4fa0-47ee-95ea-5a34dbd0aff9 +# ╠═047cc4df-1c6c-4371-a6e9-fe83a402b342 +# ╟─b75a8e32-39c2-41dd-95ff-5cb6de30199b +# ╠═c9caecc4-8d14-4885-b087-54a9226be597 +# ╟─aa07ac11-1d6e-41ea-885d-1a6ae38bd58d +# ╠═2a858811-9ffd-4dc7-84ba-f7a59421753f +# ╟─e911dc09-c56a-42ed-9a09-9c4bba1aa5f6 +# ╠═2e43110a-d948-4e6f-9ed9-a35101ba223a +# ╠═298604da-3207-40db-8535-f628d17a0f39 +# ╟─76b256dc-bdec-4fb8-95c7-b32df03ba8b1 +# ╠═3d1f2c90-2e4e-4fa1-af41-9d44d00e5090 +# ╟─c2ca4b68-39a6-45f7-a2a1-0f5c4903ed07 +# ╠═7bc637be-8238-43f4-b190-b46833fc9d34 +# ╠═127d8761-97d6-4fcb-b633-98ee30ebc617 +# ╟─cda34ccc-e018-4e2c-a4d3-8ed3d0c1624b +# ╠═55f00963-bfb0-420f-ae6f-d20f93f01b1e +# ╠═4d2c77f2-1a5f-437c-adb6-232155f2cafb +# ╟─c261b580-2cc7-4aba-af15-1778f9293278 +# ╠═730de2d2-104b-4f4a-96da-4f96b0445f95 +# ╠═2d8df796-c362-49d9-b938-26f1917e800b +# ╟─6b3ee543-e3e2-4fe6-925d-1f201243d014 +# ╠═89830894-4481-480b-954d-b24971590eaf +# ╠═c2b5c2c4-3aa6-466d-b106-d0d7aef43bdf +# ╟─3814640e-ed0b-48a1-b4ac-6b7f04d899f3 +# ╠═bbe8980e-441d-4ac6-abc5-88ff77de9f24 +# ╟─c6eb2f89-2737-4eb0-8fec-dd87ea962051 +# ╟─a8257414-2d6a-4913-9f05-6369c62189e8 +# ╠═68264b8a-a89f-465b-bedb-1b85e960a25e +# ╟─f84e6cdd-be44-464e-865b-24feca38df0f +# ╠═a7d513eb-66b3-4167-b799-74a37718a315 +# ╟─882f2f6b-eb58-4c8f-877e-76e460900c0c +# ╠═f76e5c58-12fc-4b2d-8718-e15144fbef98 +# ╟─0bd84cf5-c0a2-4bb2-b5f4-f16da6eaab04 +# ╠═cb02ebf8-aee5-47a2-9496-6f63fc123147 +# ╟─75309ee6-17b6-4ebb-b21c-a5e4a2a97510 +# ╟─08eaa553-7b74-4f02-9328-b23add528c06 +# ╠═a299b51b-1b7b-4420-9f91-bfe1d8c43158 +# ╟─58afee9f-1157-45b1-99d9-f796b474975a +# ╠═24d198c3-9fd2-4ea3-8ab1-c7f027af78be diff --git a/src/exercises/sens_fermenter_monod.jl b/src/exercises/sens_fermenter_monod.jl new file mode 100644 index 00000000..55fb2d38 --- /dev/null +++ b/src/exercises/sens_fermenter_monod.jl @@ -0,0 +1,678 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "36" +#> title = "7. Sensitivity fermenter monod" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Sensitivity fermenter monod" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 3ef93246-657d-4e77-9bf0-8380c64bfcfd +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 55cdebd2-0881-11ef-2722-91de1447877a +using Markdown, InteractiveUtils + +# ╔═╡ a355b0ba-baaf-49f4-a5dc-965364a884f0 +using Catalyst, OrdinaryDiffEq + +# ╔═╡ 00fd6d49-f561-42e9-9413-d33af92f83dc +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 7ae714c4-d25d-4f9f-ab3d-cc067db9c156 +using ForwardDiff + +# ╔═╡ dfce0717-e33e-4b6b-bb5c-d8754a74aba4 +using Turing + +# ╔═╡ 31d294d1-3a1f-41db-abff-54f2a67c7ed9 +md""" +# Exercise: Fermenter - Monod kinetics - Sensitivity analysis +""" + +# ╔═╡ 5ffe7dcb-620d-4f22-95fe-2f77cda6fbe7 +md""" +In one of the previous practica, we were introduced to a fermenter in which biomass $X$ [$g/L$] grows by breaking down substrate $S$ [$g/L$]. The reactor is fed with an inlet flow rate $Q_{in}$ [$L/h$], which consists of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. This process was modelled using Monod kinetics, resulting in the model below: + +$$\begin{eqnarray*} +S + X \xrightarrow[\quad\quad]{k} (1 + Y) \, X \quad\quad\quad\quad \textrm{with} \quad k = \cfrac{\mu_{max}}{S + K_s} \, . +\end{eqnarray*}$$ +""" + +# ╔═╡ d635d577-4d6d-40d7-a84c-3871981d59a4 +md""" +Suppose that at $t=0$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concentration of $0.0005\;g/L$. The (default) parameter values are $Q = 2.0$, $V = 40.0$ and $Y = 0.67$. The parameters $\mu_{max}$, $K_s$ and $S_{in}$ will be assigned later. +""" + +# ╔═╡ 6ec6da23-853b-4129-94cf-67b5cadb1f95 +md""" +The *reaction network object* model for this problem could be defined as: +""" + +# ╔═╡ 935ca610-7a7a-4692-8908-fc26abb880b4 +fermenter_monod = @reaction_network begin + @species S(t)=0.0 X(t)=0.0005 + @parameters Q=2.0 V=40.0 Y=0.67 Sin μmax Ks + μmax/(S + Ks), S + X --> (1 + Y)*X + # Alternative: + # mm(S, μmax, Ks)*X, S => Y*X + Q/V, (S, X) --> ∅ + Q/V*Sin, ∅ --> S +end + +# ╔═╡ 79e6056a-881c-442f-8989-5bc284d3d777 +md""" +which resulted in the following differential equations: +""" + +# ╔═╡ fa93e2c3-8b43-418e-ba24-406645b2e397 +md""" +$$\begin{eqnarray*} +\cfrac{dS}{dt} &=& \cfrac{Q}{V} \left(S_{in} - S \right) - \mu_{max}\cfrac{S}{S + K_s} X\\ +\cfrac{dX}{dt} &=& -\cfrac{Q}{V} X + Y \mu_{max}\cfrac{S}{S + K_s} X +\end{eqnarray*}$$ +""" + +# ╔═╡ 06730f54-7293-43f2-b772-84eec3e5528a +md""" +Convert the system to a symbolic differential equation model and verify, by analyzing the differential equation, that your model is correctly implemented. + +Keep in mind that `mm(S, μmax, Ks)` stands for $\mu_{max} \, \cfrac{S}{S + K_s}$. +""" + +# ╔═╡ 7f8b7a2e-bc65-4b51-ad59-bd7ac98604dd +# osys = missing + +# ╔═╡ 1ee7359c-2757-44cd-8650-09b762cf30d0 +md""" +## Goals of this exercise +""" + +# ╔═╡ 55f1d688-0c53-481b-9965-5e92ca87ad83 +md""" +Compute the following in a timespan of `(0.0, 100.0)`$\,h$: + +- The sensitivities of $S$ and $X$ w.r.t. $\mu_{max}$, $K_s$ and $S_{in}$. + +Plot the following: +- A figure with the sensitivity functions of $S$ and $X$ w.r.t. $S_{in}$. +- A figure with the sensitivity functions of $S$ w.r.t. $\mu_{max}$, $K_s$ and $S_{in}$. +- A figure with the sensitivity functions of $X$ w.r.t. $\mu_{max}$, $K_s$ and $S_{in}$. + +Interpret your results. + +Use the following parameter values $\mu_{max}$ = `0.40`, $K_s$ = `0.015` and $S_{in}$ = `0.022` for the calculations of the sensitivities. +""" + +# ╔═╡ 6c4e3c09-4b84-4f5c-8739-2ac18e6f2af6 +md""" +Initialize a vector `u0` with the initial conditions, set the timespan and initialize a vector `parms` with the parameter values: +""" + +# ╔═╡ 2ee277e5-ce4a-4ade-be0e-9bba7a4dc08c +# u0 = missing # in principle not necessary since we will use the default + +# ╔═╡ 3fdc6b17-cdeb-4dc5-8886-9d3a62caac8d +# tspan = missing + +# ╔═╡ 201dfb54-2056-4846-a64c-ff4951dc084d +md""" +For practical reasons, we will define the time step size as `dt = 0.5`. +""" + +# ╔═╡ 5627068f-6442-43bb-bacb-bcba917372f3 +# dt = missing + +# ╔═╡ 0139da85-02e3-4021-9b39-84af7e68d428 +md""" +For practical reasons, we will use the variables `μmax_val`, `Ks_val`, and `Sin_val` to store the parameter values that are used for the calculation of the sensitivity functions. +""" + +# ╔═╡ 0f995929-4d2b-4a7a-8da1-04e4d501385f +begin + # μmax_val = missing + # Ks_val = missing + # Sin_val = missing +end; + +# ╔═╡ 79b0eb65-5a0f-40b3-aa97-4088421c562e +# parms = missing # no need to include Q, V, and Y since we will use the default + +# ╔═╡ af882cf4-51fd-45ff-9b05-cfd52d6467b1 +md""" +## Preliminary simulation +""" + +# ╔═╡ f0f4fa14-6f99-4f21-a743-be61e08444a7 +md""" +Create the ODE problem and store it in `oprob`. Next, solve the ODE problem using `Tsit5()` and `saveat=dt`, and store the solution in `osol`. Finally plot the results. +""" + +# ╔═╡ 8b2f23f6-80b2-4e63-942e-e5cd17d8ba72 +# oprob = missing + +# ╔═╡ 89a31c32-88a4-479f-a688-ffcb75ee8e91 +# osol = missing + +# ╔═╡ 51a9b7e6-8ad9-477d-9596-ffd614df2c79 +# missing + +# ╔═╡ 570ebda9-b187-42ab-a761-bed0fa3ce097 +md""" +## Local Sensitivity Analysis (LSA) +""" + +# ╔═╡ 0343674d-32d1-49e1-a7c1-47196ae760ed +md""" +### Setting up function +""" + +# ╔═╡ 693844d0-3858-4861-bae0-b47e78809f17 +md""" +Write a solution function with as argument a vector of the parameters (that you want the sensitivity on), and that returns the outputs. +""" + +# ╔═╡ 9622f7ca-f71a-4ad9-a309-d7d10a1c3e3b +# function fermenter_monod_sim(parms) +# missing +# ... +# end + +# ╔═╡ 4a5971b1-f4d0-43b6-805f-e17f5052ae92 +md""" +Make two functions based on the solution function that each returns a single output, hence, one function that returns the output $S$, and another function that returns the output $X$. +""" + +# ╔═╡ f40c6402-3c28-4a7d-b629-83507a9f29bd +# fermenter_monod_sim_S(parms) = missing + +# ╔═╡ 3ae5bd00-2e06-4789-aab3-d897824d5e29 +# fermenter_monod_sim_X(params) = missing + +# ╔═╡ 6913fb1b-1986-4b37-819e-a6a7bd91f1a5 +md""" +### Compute the outputs +""" + +# ╔═╡ 4bd2bcca-9c42-4333-b062-2aaa9f7be3fe +md""" +Make the time vector. +""" + +# ╔═╡ fbd98975-aa32-46ae-8db0-0e65cdf48309 +# t_vals = missing + +# ╔═╡ 93791eb3-1eaa-4146-90b5-c4811fb3485b +md""" +Compute the two outputs $S$ and $X$ for the given parameter values. +""" + +# ╔═╡ dc0557d6-81b9-4759-8ed7-3129f60c6dc3 +# S_sim = missing + +# ╔═╡ 95bc683c-f6e6-4b42-b90b-b5a863edd4d5 +# X_sim = missing + +# ╔═╡ 35717c5a-9895-4e62-a890-03ef04aa07b9 +md""" +### Compute the sensitivities +""" + +# ╔═╡ fa970c0e-fb3b-486f-bbc1-345d44f8f0da +md""" +Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouputs $S$ and $X$. Hence, you need to call `ForwardDiff.jacobian` twice. +""" + +# ╔═╡ 07f2b8f8-5162-4077-a8a5-68fdec646644 +md""" +#### Absolute sensitivities +""" + +# ╔═╡ 64354302-f4cc-4592-9302-5db0f5bccb2e +# sens_S = missing + +# ╔═╡ 49a94b9a-a543-495e-b4f1-c8579e59304d +# sens_X = missing + +# ╔═╡ 9cace6c1-e678-4dd7-8705-92a55eb32fa9 +md""" +Extract the (absolute) sensitivities of the outputs on the different parameters. +""" + +# ╔═╡ a6dc2b60-6a0a-4140-892e-02cde8dc79d3 +# begin +# sens_S_on_μmax = missing +# sens_S_on_Ks = missing +# sens_S_on_Sin = missing +# end + +# ╔═╡ f806c243-9032-46b7-add3-4714344691c7 +# begin +# sens_X_on_μmax = missing +# sens_X_on_Ks = missing +# sens_X_on_Sin = missing +# end + +# ╔═╡ f4d0aaaa-bb3f-4335-901a-dbbcde643c78 +md""" +#### Normalized sensitivities +""" + +# ╔═╡ 5bf3a62d-d2aa-4653-8ee8-e90caa9504e8 +md""" +Compute the normalized sensitivities. +""" + +# ╔═╡ 76846731-929c-408f-a3de-970581c497e9 +# begin +# sens_S_on_μmax_rel = missing +# sens_S_on_Ks_rel = missing +# sens_S_on_Sin_rel = missing +# end + +# ╔═╡ b6c57444-547c-4e82-8526-6a30566e07c5 +# begin +# sens_X_on_μmax_rel = missing +# sens_X_on_Ks_rel = missing +# sens_X_on_Sin_rel = missing +# end + +# ╔═╡ 7f0b9e63-2856-4601-9f14-c20c6b1b3707 +md""" +### Plotting + questions +""" + +# ╔═╡ 5388c2a7-5a11-4da8-be09-46045cde8a4e +md""" +Plot the sensitivity functions of $S$ and $X$ on $S_{in}$. Provide a suitable title (`title="..."`), labels (`label=["..." "..."]`) and an x-label (`xlabel="..."`), and set the line width to 2 (`linewidth=...`). +""" + +# ╔═╡ db840c76-a6c6-49fb-a0bb-d9149f947bc0 +# missing + +# ╔═╡ d41375ef-6958-4705-a417-4c6a491232ee +md""" +!!! questions + Interpret your results. Try to answer the following question(s): + - Which output variable $S$ or $X$ is most sensitive to $S_{in}$ in steady state? + - Why is the sensitivity function of $S$ on $S_{in}$ at first positive but then becomes zero? +""" + +# ╔═╡ 39c36eb4-5f15-4577-9b84-116e29d7891d +md""" +Answers: +- missing +- missing +""" + +# ╔═╡ be89600a-4927-4afc-9813-d8a70adb2852 +md""" +Plot the sensitivity functions of $S$ on $\mu_{max}$, $K_s$ and $S_{in}$. Provide a suitable title (`title="..."`), labels (`label=["..." "..." "..."]`) and an x-label (`xlabel="..."`), and set the line width to 2 (`linewidth=...`). +""" + +# ╔═╡ c0223da4-9959-48d0-b607-633b2e82986c +# missing + +# ╔═╡ ff86a29f-9308-473b-aa1c-dfd4af8179c7 +md""" +!!! questions + Interpret your results. Try to answer the following question(s): + - Which parameter $\mu_{max}$, $K_s$ or $S_{in}$ affects the output $S$ the most in steady state? + - Why is the sensitivity function of $S$ w.r.t. $K_s$ positive? + - Why is the sensitivity function of $S$ w.r.t. $\mu_{max}$ negative? +""" + + +# ╔═╡ 430494da-402e-409d-85fc-c87cdfa3b427 +md""" +Answers: +- missing +- missing +""" +#= +- It seems like μmax is affecting S the most in steady state, and its influence is negative; hence, the larger μmax, the smaller S. +- The sensitivity function of S w.r.t. Ks is positive, because the larger Ks, the smaller the reaction rate r (S => Y*X). Hence, less X will be produced; so less S will be consumed. Remember that r = μmax*S*X/(S + Ks) and Ks is in the denominator. +- The sensitivity function of S w.r.t. μmax is negative, because the larger μmax, the larger the reaction rate r (S => Y*X). Hence, more X will be produced; so more S will be consumed. Remember that r = μmax*S*X/(S + Ks) and μmax is in the numerator. +=# + +# ╔═╡ 16a84fdb-8ce2-45b9-bfb7-7f4e1284a1d7 +md""" +Plot the sensitivity functions of $X$ on $\mu_{max}$, $K_s$ and $S_{in}$. Provide a suitable title (`title="..."`), labels (`label=["..." "..." "..."]`) and an x-label (`xlabel="..."`), and set the line width to 2 (`linewidth=...`). +""" + +# ╔═╡ 53134149-0bf7-41c1-9b35-e5037744211f +# missing + +# ╔═╡ 355ca6a7-466b-4969-ab48-28e2257f9810 +md""" +!!! questions + Interpret your results. Try to answer the following question(s): + - Which parameter $\mu_{max}$, $K_s$ or $S_{in}$ affects the output $X$ the most in steady state? + - Why is the sensitivity function of $X$ w.r.t. $K_s$ negative? + - Why is the sensitivity function of $X$ w.r.t. $\mu_{max}$ positive? +""" + +# ╔═╡ 9a18a8bd-27d3-4771-a844-f6b65c7c8918 +md""" +Answers: +- missing +- missing +- missing +""" +#= +- It seems like Sin is affecting X the most in steady state, and its influence is positive; hence, the larger Sin, the larger X. +- The sensitivity function of X w.r.t. Ks is negative, because the larger Ks, the smaller the reaction rate r (S => Y*X). Hence, less X will be produced. Remember that r = μmax*S*X/(S + Ks) and Ks is in the denominator. +- The sensitivity function of X w.r.t. μmax is positive, because the larger μmax, the larger the reaction rate r (S => Y*X). Hence, more X will be produced. Remember that r = μmax*S*X/(S + Ks) and μmax is in the numerator. +=# + +# ╔═╡ 6536f76a-0f6d-4155-ab3b-38e2e3189886 +md""" +## Monte Carlo error propagation +""" + +# ╔═╡ 9fd59148-6993-4598-af95-ac85b6a4bb45 +md""" +Let's take a look at how the uncertainty propagates in your model by using Monte Carlo simulations. Based on some literature search you can assume that the parameter `Ks` is normally distributed around the original value with a standard deviation of 20%. +""" + +# ╔═╡ 5a559bc2-3d55-499c-93cf-4b0845ffc4b0 +md""" +Start with making a Turing model in which you implement the prior and return the solution of the solved problem. +""" + +# ╔═╡ ecc28b8e-0eaf-489f-8104-7c6383d4e01f +md""" +!!! note + Instead of creating a new ODEProblem everytime, you can simply remake an old ODEProblem by using `new_prob = remake(oprob, p = [:Ks => Ks])`. +""" + +# ╔═╡ 26ae811d-11fd-44d8-b328-79381fa7472c +# @model function monod_deviation() +# Ks_dev ~ missing # 20% standard deviation +# new_prob = missing +# return missing +# end + +# ╔═╡ 62791c48-5e38-46fe-8336-3a21003f3fe3 +md""" +Now get sample 100 solutions from your monod_deviation model. +""" + +# ╔═╡ 9e049653-347a-4198-90fd-23f15c404d0c +# missing + +# ╔═╡ f11827e5-de69-4a0c-b385-2c6ea031bb79 +md""" +We can now visualise the results of our Monte Carlo simulation by looping through our solutions and plotting them. It is advised to put `label = false` (avoids 100 labels), `color=:gray` (neutral color) and `alpha=0.4` (makes it more transparent) and `linestyle=:dot`. +""" + +# ╔═╡ 9d0f8147-3c82-4ace-a408-20e32b4d8ea0 +# begin +# p1 = plot(title="Monte Carlo of logistic growth") # make an empty plot +# for missing in missing +# missing +# end +# plot!(osol, label = "Original simulation", linewidth = 3) # Plots original result +# p1 +# end + +# ╔═╡ 30c270f1-cbd5-401b-92db-7123efb01db6 +md""" +As can be seen from the graph, the uncertainty is rather high, especially if we want to carefully monitor the concentration of biomass through time. Note that, since we have a sample of values for $S$ and $X$ at every time step, it is also perfectly possible here to quantify the uncertainty by calculating the standard deviation at every time step, but the graph already gives a clear indication. +""" + +# ╔═╡ 16098df3-35c0-4d06-a482-35137fd53b97 +md""" +Now create a vector x1 that stores the values of the biomass at the end of the simulations and make a histogram of it. Put `xlims = [0.010, 0.015]`. +""" + +# ╔═╡ de5736d4-1c2f-4b0c-a84b-6d8b07d29a0a +# missing + +# ╔═╡ 1b1d0997-b854-4445-8c19-e73db6784a9b +# missing + +# ╔═╡ 43aaf563-f799-43e4-9f4c-8a19be1586dd +md""" +### Determininig optimal measurements +""" + +# ╔═╡ f7f1f035-b48e-4108-bb84-0c00ab68a93a +md""" +As a process operator, you may want to reduce the uncertainty in biomass simulations. However, since taking measurements can be costly, it is important to perform them at the most informative time points. + +Local sensitivity analysis is a useful tool in this context, as it indicates when the model output is most sensitive to changes in parameter values. At these time points, accurate measurements provide the greatest insight into the true parameter values. + +Start off by identifying the time at which the biomass concentration is most sensitive to variations in `Ks`. +""" + +# ╔═╡ 8dbadbc6-acd9-42f5-abf4-81fe6aef7fb7 +md""" +!!! note + You can find the index of the maximum value in a vector with the `argmax()` function. For example `argmax([1, 2, 5, 4])` will give you 3. +""" + +# ╔═╡ ddfb530a-ba79-43c0-973a-4deb8cad1d33 +# begin +# t_star_idx = missing +# t_star = t_vals[t_star_idx] + +# plot(t_vals, abs.(sens_X_on_Ks_rel), +# title="Sensitivity of Ks wrt. X", xlabel="Time (h)", +# ylabel="normalized sensitivity") +# vline!([t_star], label="t* = $(round(t_star, digits=2)) h", linestyle=:dash, color=:red) +# end + +# ╔═╡ 8359ea0e-a916-48f4-a07a-c905c3efaf15 +begin + μmax_real = 0.42 + Ks_real = 0.0165 + Sin_real = 0.0207 + real_solution = solve(remake(oprob, p = [:μmax => μmax_real, :Ks => Ks_real, + :Sin => Sin_real])); +end; + +# ╔═╡ 9654dea2-e13e-4aa9-8a5e-c3c368c26e24 +md""" +We have defined a function `real_solution(t)` in this notebook that returns a hypothetical measurement (`S_meas, X_meas`) at the given timepoint `t`. Use this function to get a measurement at the optimal time (`t_star`). +""" + +# ╔═╡ 77eab4ec-5012-40dd-b702-9fb48982ff38 +# S_meas, X_meas = real_solution(t_star) + +# ╔═╡ 3e93a001-9401-4a1f-b283-c79fd0c43434 +md""" +#### Conditioning on the measurements +""" + +# ╔═╡ 684f9163-ac84-49be-beb1-66d7bdc21da8 +md""" +Now we can use this measurement to calibrate our model. In this part we will use Monte Carlo Markov chain to show the decrease in uncertainty. + +We start by creating a Turing model, for which you can use the priors from the first part of this exercise. For the standard deviation `σ_X` you can assume a value of 0.0002 and a value of 0.00055 for `σ_S`. + +Make sure to truncate the domain of your parameters to a reasonable domain or you might run into issues with the solver. +""" + +# ╔═╡ 4fe68607-d67e-4e71-865a-b10a06e5908d +# @model function monod_meas() +# σ_X = missing +# σ_S = missing + +# Ks_dev ~ missing + +# sol = missing + +# S_pred, X_pred = sol(t_meas) + +# X_meas ~ missing +# S_meas ~ missing +# end + +# ╔═╡ 11e105de-270c-434a-bfb4-9118dfc9e461 +# monod_cond_model = missing + +# ╔═╡ e46afb98-16de-4d19-86b4-b920b6b929c5 +md""" +### MCMC +""" + +# ╔═╡ b8cd84de-57e4-4d8b-92da-e7c5b6d627bd +# monod_chain = missing + +# ╔═╡ e76a7bf7-4d8d-4f2f-8272-b63e49cd4ae9 +md""" +Plot the chain to check if it properly converged to the posterior distribution. +""" + +# ╔═╡ 834c3500-7556-4999-b465-08a8a07c7f28 +# missing + +# ╔═╡ eeaf3255-95c8-4813-bdd7-38635e546606 +md""" +Now, get the sampled posterior solutions and afterwards plot the resulting simulations. +""" + +# ╔═╡ a66b6d91-e529-4c20-b5d6-91830eae1437 +# missing + +# ╔═╡ c4192ff2-fca0-4ab1-8ac1-ce9771bc69d3 +md""" +Plot the resulting Monte Carlo simulation with the original simulations. +""" + +# ╔═╡ 54f20cff-21fc-4169-9f47-5e670b7ec621 +# begin +# p = plot(ylabel = "concentration (g/l)", xlabel = "Time (hours)") +# for missing in missing +# missing +# end +# plot!(osol, label = ["Original simulation S" "Original simulation X"], linewidth = 3) + +# p +# end + +# ╔═╡ 988aab6d-7193-4f3d-bd08-c3c36c920f81 +md""" +Lastly, create a histogram of the value of X at the end of the simulations based on your posterior distribution of Ks. Put the `xlims = [0.010, 0.015]` to compare with the prior distribution. +""" + +# ╔═╡ 2f55a771-0d37-48e5-a9d9-24693bf1d177 +# missing + +# ╔═╡ 18059d0c-cf37-44a1-86a8-859ac66a66f0 +# missing + +# ╔═╡ Cell order: +# ╟─31d294d1-3a1f-41db-abff-54f2a67c7ed9 +# ╠═55cdebd2-0881-11ef-2722-91de1447877a +# ╠═3ef93246-657d-4e77-9bf0-8380c64bfcfd +# ╠═a355b0ba-baaf-49f4-a5dc-965364a884f0 +# ╠═00fd6d49-f561-42e9-9413-d33af92f83dc +# ╠═7ae714c4-d25d-4f9f-ab3d-cc067db9c156 +# ╠═dfce0717-e33e-4b6b-bb5c-d8754a74aba4 +# ╟─5ffe7dcb-620d-4f22-95fe-2f77cda6fbe7 +# ╟─d635d577-4d6d-40d7-a84c-3871981d59a4 +# ╟─6ec6da23-853b-4129-94cf-67b5cadb1f95 +# ╠═935ca610-7a7a-4692-8908-fc26abb880b4 +# ╟─79e6056a-881c-442f-8989-5bc284d3d777 +# ╟─fa93e2c3-8b43-418e-ba24-406645b2e397 +# ╟─06730f54-7293-43f2-b772-84eec3e5528a +# ╠═7f8b7a2e-bc65-4b51-ad59-bd7ac98604dd +# ╟─1ee7359c-2757-44cd-8650-09b762cf30d0 +# ╟─55f1d688-0c53-481b-9965-5e92ca87ad83 +# ╟─6c4e3c09-4b84-4f5c-8739-2ac18e6f2af6 +# ╠═2ee277e5-ce4a-4ade-be0e-9bba7a4dc08c +# ╠═3fdc6b17-cdeb-4dc5-8886-9d3a62caac8d +# ╟─201dfb54-2056-4846-a64c-ff4951dc084d +# ╠═5627068f-6442-43bb-bacb-bcba917372f3 +# ╟─0139da85-02e3-4021-9b39-84af7e68d428 +# ╠═0f995929-4d2b-4a7a-8da1-04e4d501385f +# ╠═79b0eb65-5a0f-40b3-aa97-4088421c562e +# ╟─af882cf4-51fd-45ff-9b05-cfd52d6467b1 +# ╟─f0f4fa14-6f99-4f21-a743-be61e08444a7 +# ╠═8b2f23f6-80b2-4e63-942e-e5cd17d8ba72 +# ╠═89a31c32-88a4-479f-a688-ffcb75ee8e91 +# ╠═51a9b7e6-8ad9-477d-9596-ffd614df2c79 +# ╟─570ebda9-b187-42ab-a761-bed0fa3ce097 +# ╟─0343674d-32d1-49e1-a7c1-47196ae760ed +# ╟─693844d0-3858-4861-bae0-b47e78809f17 +# ╠═9622f7ca-f71a-4ad9-a309-d7d10a1c3e3b +# ╟─4a5971b1-f4d0-43b6-805f-e17f5052ae92 +# ╠═f40c6402-3c28-4a7d-b629-83507a9f29bd +# ╠═3ae5bd00-2e06-4789-aab3-d897824d5e29 +# ╟─6913fb1b-1986-4b37-819e-a6a7bd91f1a5 +# ╟─4bd2bcca-9c42-4333-b062-2aaa9f7be3fe +# ╠═fbd98975-aa32-46ae-8db0-0e65cdf48309 +# ╟─93791eb3-1eaa-4146-90b5-c4811fb3485b +# ╠═dc0557d6-81b9-4759-8ed7-3129f60c6dc3 +# ╠═95bc683c-f6e6-4b42-b90b-b5a863edd4d5 +# ╟─35717c5a-9895-4e62-a890-03ef04aa07b9 +# ╟─fa970c0e-fb3b-486f-bbc1-345d44f8f0da +# ╟─07f2b8f8-5162-4077-a8a5-68fdec646644 +# ╠═64354302-f4cc-4592-9302-5db0f5bccb2e +# ╠═49a94b9a-a543-495e-b4f1-c8579e59304d +# ╟─9cace6c1-e678-4dd7-8705-92a55eb32fa9 +# ╠═a6dc2b60-6a0a-4140-892e-02cde8dc79d3 +# ╠═f806c243-9032-46b7-add3-4714344691c7 +# ╟─f4d0aaaa-bb3f-4335-901a-dbbcde643c78 +# ╟─5bf3a62d-d2aa-4653-8ee8-e90caa9504e8 +# ╠═76846731-929c-408f-a3de-970581c497e9 +# ╠═b6c57444-547c-4e82-8526-6a30566e07c5 +# ╟─7f0b9e63-2856-4601-9f14-c20c6b1b3707 +# ╟─5388c2a7-5a11-4da8-be09-46045cde8a4e +# ╠═db840c76-a6c6-49fb-a0bb-d9149f947bc0 +# ╟─d41375ef-6958-4705-a417-4c6a491232ee +# ╠═39c36eb4-5f15-4577-9b84-116e29d7891d +# ╟─be89600a-4927-4afc-9813-d8a70adb2852 +# ╠═c0223da4-9959-48d0-b607-633b2e82986c +# ╟─ff86a29f-9308-473b-aa1c-dfd4af8179c7 +# ╟─430494da-402e-409d-85fc-c87cdfa3b427 +# ╟─16a84fdb-8ce2-45b9-bfb7-7f4e1284a1d7 +# ╠═53134149-0bf7-41c1-9b35-e5037744211f +# ╟─355ca6a7-466b-4969-ab48-28e2257f9810 +# ╟─9a18a8bd-27d3-4771-a844-f6b65c7c8918 +# ╟─6536f76a-0f6d-4155-ab3b-38e2e3189886 +# ╟─9fd59148-6993-4598-af95-ac85b6a4bb45 +# ╟─5a559bc2-3d55-499c-93cf-4b0845ffc4b0 +# ╟─ecc28b8e-0eaf-489f-8104-7c6383d4e01f +# ╠═26ae811d-11fd-44d8-b328-79381fa7472c +# ╟─62791c48-5e38-46fe-8336-3a21003f3fe3 +# ╠═9e049653-347a-4198-90fd-23f15c404d0c +# ╟─f11827e5-de69-4a0c-b385-2c6ea031bb79 +# ╠═9d0f8147-3c82-4ace-a408-20e32b4d8ea0 +# ╟─30c270f1-cbd5-401b-92db-7123efb01db6 +# ╟─16098df3-35c0-4d06-a482-35137fd53b97 +# ╠═de5736d4-1c2f-4b0c-a84b-6d8b07d29a0a +# ╠═1b1d0997-b854-4445-8c19-e73db6784a9b +# ╟─43aaf563-f799-43e4-9f4c-8a19be1586dd +# ╟─f7f1f035-b48e-4108-bb84-0c00ab68a93a +# ╟─8dbadbc6-acd9-42f5-abf4-81fe6aef7fb7 +# ╠═ddfb530a-ba79-43c0-973a-4deb8cad1d33 +# ╟─8359ea0e-a916-48f4-a07a-c905c3efaf15 +# ╟─9654dea2-e13e-4aa9-8a5e-c3c368c26e24 +# ╠═77eab4ec-5012-40dd-b702-9fb48982ff38 +# ╟─3e93a001-9401-4a1f-b283-c79fd0c43434 +# ╟─684f9163-ac84-49be-beb1-66d7bdc21da8 +# ╠═4fe68607-d67e-4e71-865a-b10a06e5908d +# ╠═11e105de-270c-434a-bfb4-9118dfc9e461 +# ╟─e46afb98-16de-4d19-86b4-b920b6b929c5 +# ╠═b8cd84de-57e4-4d8b-92da-e7c5b6d627bd +# ╟─e76a7bf7-4d8d-4f2f-8272-b63e49cd4ae9 +# ╠═834c3500-7556-4999-b465-08a8a07c7f28 +# ╟─eeaf3255-95c8-4813-bdd7-38635e546606 +# ╠═a66b6d91-e529-4c20-b5d6-91830eae1437 +# ╟─c4192ff2-fca0-4ab1-8ac1-ce9771bc69d3 +# ╠═54f20cff-21fc-4169-9f47-5e670b7ec621 +# ╟─988aab6d-7193-4f3d-bd08-c3c36c920f81 +# ╠═2f55a771-0d37-48e5-a9d9-24693bf1d177 +# ╠═18059d0c-cf37-44a1-86a8-859ac66a66f0 diff --git a/src/exercises/sens_insuline.jl b/src/exercises/sens_insuline.jl new file mode 100644 index 00000000..93484dd1 --- /dev/null +++ b/src/exercises/sens_insuline.jl @@ -0,0 +1,532 @@ +### A Pluto.jl notebook ### +# v0.20.4 + +#> [frontmatter] +#> order = "38" +#> title = "7. Sensitivity insuline" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Sensitivity insuline" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 7bc363b0-9415-4954-807f-81a308bde531 +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 52b28a4c-b0bb-11ef-2841-17ecfb596676 +using StatsPlots, PlutoUI, OrdinaryDiffEq, ForwardDiff, Catalyst + +# ╔═╡ bfcc4b4e-073e-401e-851c-d01ef828028a +md""" +### Exercise: The minimal glucose model and dynamic compensation - sensitivity + +The Minimal Model of Glucose Regulation is a mathematical model used to describe how the body regulates glucose (sugar) levels in the blood. It was developed by Richard Bergman and Claudio Cobelli in the late 1970s and has become a cornerstone in diabetes research. + +We will use this exercise to study insulin sensitivity. + +The basic model considers only the concentration of glucose $G(t)$ in $mmol/L$ and the concentration of insulin $I(t)$ in $mmol/L$: + +- Glucose is added to a system with a zeroth-order rate of $m$ (later $m(t)$ if we model a non-fixed input). +- Glucose is removed from the blood with a rate of $sGI$, where $s$ is the insulin sensitivity. +- Insulin decays according to first-order kinetics with a rate parameter $\gamma$ +- $\beta$-cells produce insulin as a response to higher glucose concentrations. This is according to a saturated process, so it is well approximated using a Hill function ($n=2$). The rate of insulin production is given by $qBf(G)$, with $B$ the amount of $\beta$-cells, $q$ the maximal rate of insulin production/unit of cells and $f(G)$ the Hill function. +""" + +# ╔═╡ a5789d9d-a690-4c6b-8b8d-fbfe63f659d0 +md""" +The Hill function is defined as: + +$$hill(X, v, K, n) = \cfrac{v\,X^n}{X^n + K^n}$$ + +In order to have an idea of how it looks like, lets define it as `f_insuline(G)` for $v=1$, $K=5$ and $n=2$: +""" + +# ╔═╡ e93bc1a5-e0b1-425c-b069-d5ce80756e60 +f_insulin(G) = hill(G, 1, 5, 2) + +# ╔═╡ be33a8a3-a3f5-448d-9390-16f7c4ca099d +md""" +The following plot gives a fairly realistic response of insulin production as a function of glucose concentration in the blood. +""" + +# ╔═╡ 2ac43184-a393-4d39-9c3b-c299c3371b09 +plot(f_insulin, 0, 30, xlab="G (mmol/L)", ylab="f(G)", title="Insulin production rate") + +# ╔═╡ 75a5b5d9-f8d7-4728-83dc-03dde502dcfb +md"Below is a reaction network implementing this model. All parameters are set to 1.0 for didactic purposes." + +# ╔═╡ f62c2b4a-a9e8-472b-b07b-44072da08ee8 +glucose_insuline_circuit = @reaction_network begin + @parameters q=1.0 s=1.0 γ=1.0 m=1.0 B=1.0 Ks=1.0 + m, 0 --> G + s * I, G --> 0 + B * hill(G, q, Ks, 2), 0 --> I + γ, I --> 0 +end + +# ╔═╡ 882e106f-d3a5-4ccb-ae65-cfb5e4e5de57 +md""" +Convert the system to a symbolic differential equation model and inspect the differential equations. You do not need to make a new variable, just call `convert` with the right arguments. +""" + +# ╔═╡ 99ccbd1b-b9e4-4afc-8d9a-8c1055bcce0f +# missing # Uncomment and complete the instruction + +# ╔═╡ 3c1465d2-0886-4b74-baee-55c7dad41f9a +md""" +Simulate the system over a time interval of $0.0$ to $10.0$ hours with $m=1.0$ for various initial glucose concentrations (e.g., between $0.1$ and $5.0$) by means of the variable `G0` bound to the slider just here below. Use an initial insuline concentration of $0.0$. +""" + +# ╔═╡ 5bac69d1-0bd4-41bd-b15d-5ca6292a31e4 +@bind G0 Slider(0.1:0.1:5.0, default=1.0, show_value=true) + +# ╔═╡ 6ccd5ecc-5579-4ce5-9425-d1bbae0bc5ba +G0 + +# ╔═╡ afc40964-0a97-43a6-ab27-5fe96ecd319b +# Putting a semi-colon (;) after an instruction will hide its return value. +# oprob1 = missing; # Uncomment and complete the instruction + +# ╔═╡ f050b32e-1a14-4d5e-9fb3-5dbc4dfbf79c +# sol1 = missing; # Uncomment and complete the instruction + +# ╔═╡ cfbbfe48-a2c3-4691-9fac-b0a18f99222c +md""" +Plot the results. Use thereby `ylim=(0.0, 5.5)`. +""" + +# ╔═╡ 7e35b56b-44a0-46df-a812-f5fb437b672e +# missing # Uncomment and complete the instruction + +# ╔═╡ 68bdcedb-42b7-4fa8-8252-c2fbaa6e0187 + + +# ╔═╡ 8c7784df-1f6d-41e3-8548-7311710e599e +md""" +!!! question "Question" + What are the steady state concentrations for the two species? Does this depend on initial glucose levels (given enough time)? +""" + +# ╔═╡ 0133ab41-2c8c-4147-9594-1ad238833a9e +md"- Answer: missing" + +# ╔═╡ 479e0f11-e18e-4523-8d24-b2026acdeaef +md""" +Check out the final glucose and insuline concentrations (at the end time). +""" + +# ╔═╡ 824677b4-6533-453f-909f-bf04fe4bf5d9 +# missing # Uncomment and complete the instruction + +# ╔═╡ cef7f30d-43ef-4f2c-8bf3-35c140d237ce +md""" +Create a vector named `u1_guess` with the previous final values. +""" + +# ╔═╡ 9d3ba8bd-e9c3-4bb6-957d-c7f8211a2cda +# u1_guess = missing # Uncomment and complete the instruction + +# ╔═╡ 3f5e2195-da93-40fb-88ec-9e85a7bb6c24 +md""" +Calculate the steady-state values of glucose and insuline. +""" + +# ╔═╡ 3dae761f-5fb6-4f1c-a440-d2c21a6edb17 +# eq = missing # Uncomment and complete the instruction + +# ╔═╡ d48a6bfc-b533-41ca-a0de-df0af5738536 +# Geq = missing + +# ╔═╡ 087015da-d85d-4ebb-861a-b89e93a4055c +# Ieq = missing + +# ╔═╡ 314aa77c-5906-4772-94c5-f0b7cf648de0 +md""" +Check ou the steady state values for glucose and insulin. +""" + +# ╔═╡ 3b56f88c-0ca8-4f33-8516-11e639cb6a1a +# (missing, missing) # Uncomment and complete the instruction + +# ╔═╡ aab02fef-6d84-4f53-aff5-6f94bf2bd460 +md""" +Now simulate the system but rather than with $m$ being a constant glucose input, we give in a pulse of glucose (i.e., drinking a soda) with a peak at $t=5$h. **Note that our parameter now depends on the time!** +""" + +# ╔═╡ 8a5ce760-55bb-4610-9226-32c3d8376748 +glucose_pulse(t) = .5 + exp(-(t-5)^2) + +# ╔═╡ a2fb8c21-c594-44f3-92df-99e6d3afeba1 +plot(glucose_pulse, 0, 10, label="G [mmol/L]", xlabel="t") + +# ╔═╡ d95198d9-fb96-4389-bf66-f039a673d1a2 +md""" +For purpose of solving the ODE problem we will need to define $t$ as a (default) time variable with the command below. +""" + +# ╔═╡ 8bb8feaa-63e7-4777-ab79-92d98f84142b +t = default_t(); + +# ╔═╡ d7d91377-2b6b-4daa-aeb6-d0b20c20cd34 +md""" +Redo the ODE problem but now with `:m=>glucose_pulse(t)` as parameter. Use an initial value of $0.0$ for both glucose and insuline concentrations. +""" + +# ╔═╡ 6890e3ec-0875-423e-89c1-988411c869e3 +# oprob2 = missing; # Uncomment and complete the instruction + +# ╔═╡ cfcd040b-f762-4cf4-8b9c-1d77ac2837cd +md""" +Solve the new ODE problem using `Tsit5()` and `saveat=0.01`. +""" + +# ╔═╡ fb439f9d-ee88-43cd-b91b-44ed8013b27d +# sol2 = missing; # Uncomment and complete the instruction + +# ╔═╡ 83595165-3d71-43b3-93c0-4ba880e0b86e +md""" +Plot the results. +""" + +# ╔═╡ 095e359a-2ec4-4637-8477-93510bef23ed +# missing # Uncomment and complete the instruction + +# ╔═╡ 31aaced7-1fe4-4117-80f3-0fa70035822a +md""" +Up to now, we set $s$, the insulin sensitivity to $1$. This parameter represents how sensitive the body is to insuline in taking up glucose. Aging and obesity increase glucose resistance ($1/s$), resulting in diabetes! Explore the effect of this parameter on your plots below. +""" + +# ╔═╡ 53addc28-7c87-4c79-ad8f-4a41a3ca5f19 +md""" +We make a slider so that s can get values between 0.1 and 5.0 in step of 0.1. +""" + +# ╔═╡ 69e21e9b-7d0e-44e3-8b33-ee7102dadadf +@bind s Slider(0.1:0.1:5.0, default=1, show_value=true) + +# ╔═╡ b4073e81-6db8-4be6-a2d1-afa01640dbcf +s + +# ╔═╡ 4d58a832-9157-42fe-b578-c3fc29f01ea8 +md""" +Below we have made a function that computes/shows the steady state glucose concentration after $100$ hours. Initial values for $G$ and $I$ are $0.0$ and $m$ was set to $0.5$. +""" + +# ╔═╡ 7cf11e6f-7ef4-478f-8cc7-eae93374cc58 +function glucose_steady_state(s) + oprob = ODEProblem(glucose_insuline_circuit, [:G=>0.0, :I=>0.0], (0., 100.), [:m=>0.5, :s=>s]) + sol = solve(oprob, Tsit5(), saveat=0.01) + return sol[:G][end] # final (steady state) glucose concentration is returned +end + +# ╔═╡ 581b82c2-0957-4d20-a664-6ff80b5c40d9 +md""" +Calling the function results in the final (steady state) glucose concentration for a specific valu of $s$ set by the slider above. +""" + +# ╔═╡ 5eda8c4a-2baa-4987-b408-dbe7383abe95 +glucose_steady_state(s) + +# ╔═╡ 1c22a730-776e-4508-bdee-d8861c8949e4 +md""" +Below is a plot of the steady state value of $G$ as a function of $s$. +""" + +# ╔═╡ 34788b1d-1530-4b88-8690-03fe9af52a8b +plot(glucose_steady_state, 0.1, 5, xlabel="s", ylabel="Gss", label="Gss", ylim=(0, 5)) + +# ╔═╡ f616da2f-b51e-4328-bd3c-47e6d7377cbb +md""" +Use automatic differentiation with `ForwardDiff.derivative(..., ...)` to compute the absolute and relative sensitivity index. Is this system sensitive to the insuline sensitivity $s$? +""" + +# ╔═╡ 9be6f5b3-d280-4222-ad64-4b4a7b682310 +md""" +Calculate the absolute sensitivity. +""" + +# ╔═╡ 97636a73-4732-4377-b760-c36cef13904b +# Uncomment and complete the instruction +# sens_G(s) = ForwardDiff.derivative(..., ...) + +# ╔═╡ e0318ae7-d27d-4d73-bf44-632d813e8aa5 +md""" +Display the absolute sensitivity for the current $s$ value (cf. slider). +""" + +# ╔═╡ a67d70ee-c8b5-4f22-926c-f48c5dcf9815 +# missing # Uncomment and complete the instruction + +# ╔═╡ 407437a0-36b1-452f-910c-f244aeeedc4c +md""" +Calculate the normalized (total relative) sensitivity. +""" + +# ╔═╡ 1bb5834a-6a4e-40ec-82c1-f2c505ecfeb8 +# sens_G_rel(s) = missing # Uncomment and complete the instruction + +# ╔═╡ 66105045-a8f6-4513-a255-a9e1b0c78d37 +md""" +Display the normalized (total relative) sensitivity for the current $s$ value (cf. slider). +""" + +# ╔═╡ a3156073-07bd-4f13-be5e-d19483fbad28 +# missing # Uncomment and complete the instruction + +# ╔═╡ 0ae57436-0b8c-41ce-9d9d-8c48ed59b820 +md""" +We see that the final glucose concentration is highly dependent on $s$! This seems to be a flaw in the model, as we can imagine that the physiological parameters can greatly differ from person to person (for example, a person can have a large pancreas). The final glucose concentration should not depend on the insuline sensitivity $s$. + +A mechanism that stabilizes this is called *dynamic compensation*. Simply put, we have assumed here that the amount of beta cells ($B$) is fixed. However, in practice, these cells are capable of dividing, growing, and thus producing more insulin. Their growth rate depends on the concentration of glucose, creating an additional feedback loop that stabilizes the physiological circuit. + +```julia +μ(G), B --> 2B # dynamic compensation +``` + +""" + +# ╔═╡ 92d689a4-9f2b-4a0f-81a5-6194af06940c +md""" +Growth rate function depending on the glucose concentration. +""" + +# ╔═╡ 87d9c826-56af-409a-a884-d784cfa16a64 +μ(G) = 0.3atan(0.5(G-1)) + +# ╔═╡ 2f784fff-90fc-4273-8b9a-db4593849652 +md""" +The growth rate of the $\beta$-cells follows a sigmoid shape, being negative when $G$ is smaller than a threshold and positive if $G$ exceeds this threshold. This curve is plotted below. +""" + +# ╔═╡ 4d95e77b-fab0-454b-8319-1fe83e12f9e9 +plot(μ, xlim=(0, 30), xlab="G", label="μ(G)", title="Glucose-dependent growth rate") + +# ╔═╡ ea46f5f9-47a8-4641-a7e3-08a1a29cd04f +md""" +Add dynamic compensation to the model and show that this greatly reduces the sentitivty w.r.t. $s$. +""" + +# ╔═╡ 870e9229-374a-40b8-97f7-1a84d1a857e9 +md""" +Robust version of a *reaction network object* with dynamic compensation. +```julia +glucose_insuline_circuit_robust = @reaction_network begin + @parameters q=1 s=1 γ=1 m=1 Ks=1.0 + @species B(t)=1 + m, 0 --> G + s * I, G --> 0 + B * hill(G, q, Ks, 2), 0 --> I + γ, I --> 0 + μ(G), B --> 2B +end +``` +""" + +# ╔═╡ a8814cfd-8fd0-4628-b1ae-80211386a402 +md""" +Create the aforementioned robust version of a *reaction network object*. +""" + +# ╔═╡ 246a06ba-ef48-47ba-aef0-51f07ceb96e4 +# Uncomment and complete the instruction +# glucose_insuline_circuit_robust = @reaction_network begin +# @parameters missing +# @species missing +# missing +# ... +# end + +# ╔═╡ 69965b61-043b-44a0-998b-f5f34deefdd2 +md""" +Convert the system to a symbolic differential equation model and inspect the differential equations. You do not need to make a new variable, just call `convert` with the right arguments. +""" + +# ╔═╡ 2f992357-4608-40e6-9954-8306f31e9768 +# missing # Uncomment and complete the instruction + +# ╔═╡ f87451c3-1707-4f13-a373-d8949ce4ca08 +md""" +Simulate the system over a time interval of $0$ to $10$ hours with default parameter values, and initial glucose and insuline concentrations of $5.0$ and $0.0$, repectively. Use `Tsit5()` and `saveat=0.01` to solve. +""" + +# ╔═╡ 93457418-a032-48b6-9c63-94ce49645c68 +# oprob_robust = missing; # Uncomment and complete the instruction + +# ╔═╡ cac1644a-7604-4716-85c8-63b657ff95b2 +# sol_robust = missing; # Uncomment and complete the instruction + +# ╔═╡ 06645013-b63b-49b4-822b-f08059f3a394 +md""" +Plot the results. Use thereby `ylim=(0.0, 5.5)`. +""" + +# ╔═╡ de7919d3-04a8-4b4f-ad76-2f835275fc6b +# missing # Uncomment and complete the instruction + +# ╔═╡ 318e83fc-340b-4fd1-9e72-dbfdb891ff5f +md""" +Implement a function that computes/shows the steady state glucose concentration after $100$ hours. Set initial values for $G$ and $I$ to $0.0$ and set $m$ to $0.5$. + +- Tip: copy the *body* of the former function `glucose_steady_state` and adapt. +""" + +# ╔═╡ bcf597ef-c57f-402d-8209-511a7fc16437 +# Uncomment and complete the instruction +# function glucose_steady_state_robust(s) +# oprob = missing +# sol = missing +# return missing +# end + +# ╔═╡ 5c2d0d0c-7bce-4696-9807-e793b6f8033e +md""" +Create a new slider object with a range between $0.1$ and $5.0$ and step size $0.1$, and bind it to the new variable `s_robust`. + +- Tip: copy the previous slider and adapt. +""" + +# ╔═╡ 8ef6d8a4-be72-49dc-be44-0e4eb06336be +# missing # Uncomment and complete the instruction + +# ╔═╡ fd56b95e-59a3-46e7-8216-e50181c22a58 +md""" +Call the function `glucose_steady_state_robust` with `s_robust` as argument and observe the new steady state glucose concentration for different insulin sensitivity values. +""" + +# ╔═╡ daf82017-c427-480c-94d2-938e675004f0 +# missing # Uncomment and complete the instruction + +# ╔═╡ b95899e9-3af9-4fc5-820b-f72164beb5db +md""" +Plot of the new steady state value of $G$ as a function of $s$ (in the range $[0.1, 5.0]$). You might need to use `ylim=(0.98, 1.02)`. +""" + +# ╔═╡ 4adeac81-8724-4a2c-a004-17be8f92f95e +# missing # Uncomment and complete the instruction + +# ╔═╡ 041f6717-2709-47cb-ad54-8ef6b1a61fcc +md""" +Use automatic differentiation with `ForwardDiff.derivative(..., ...)` to compute the normalized (total relative) sensitivity index. Is this new system sensitive to the insuline sensitivity $s$? + +- Answer: missing +""" + +# ╔═╡ 2180719e-ad6b-4b63-a835-3c30b811d35a +# sens_G_rel_robust(s) = ... # Uncomment and complete the instruction + +# ╔═╡ d4af98b9-40dc-486b-bbc0-9538a193cf19 +md""" +Display the new normalized (total relative) sensitivity for the current $s$ value (cf. slider). +""" + +# ╔═╡ 6ca12c8b-a535-43ca-9d6a-86e324eac51e +sens_G_rel_robust(s_robust) + +# ╔═╡ Cell order: +# ╠═52b28a4c-b0bb-11ef-2841-17ecfb596676 +# ╠═7bc363b0-9415-4954-807f-81a308bde531 +# ╟─bfcc4b4e-073e-401e-851c-d01ef828028a +# ╟─a5789d9d-a690-4c6b-8b8d-fbfe63f659d0 +# ╠═e93bc1a5-e0b1-425c-b069-d5ce80756e60 +# ╟─be33a8a3-a3f5-448d-9390-16f7c4ca099d +# ╠═2ac43184-a393-4d39-9c3b-c299c3371b09 +# ╟─75a5b5d9-f8d7-4728-83dc-03dde502dcfb +# ╠═f62c2b4a-a9e8-472b-b07b-44072da08ee8 +# ╟─882e106f-d3a5-4ccb-ae65-cfb5e4e5de57 +# ╠═99ccbd1b-b9e4-4afc-8d9a-8c1055bcce0f +# ╟─3c1465d2-0886-4b74-baee-55c7dad41f9a +# ╠═5bac69d1-0bd4-41bd-b15d-5ca6292a31e4 +# ╠═6ccd5ecc-5579-4ce5-9425-d1bbae0bc5ba +# ╠═afc40964-0a97-43a6-ab27-5fe96ecd319b +# ╠═f050b32e-1a14-4d5e-9fb3-5dbc4dfbf79c +# ╟─cfbbfe48-a2c3-4691-9fac-b0a18f99222c +# ╠═7e35b56b-44a0-46df-a812-f5fb437b672e +# ╠═68bdcedb-42b7-4fa8-8252-c2fbaa6e0187 +# ╟─8c7784df-1f6d-41e3-8548-7311710e599e +# ╠═0133ab41-2c8c-4147-9594-1ad238833a9e +# ╟─479e0f11-e18e-4523-8d24-b2026acdeaef +# ╠═824677b4-6533-453f-909f-bf04fe4bf5d9 +# ╟─cef7f30d-43ef-4f2c-8bf3-35c140d237ce +# ╠═9d3ba8bd-e9c3-4bb6-957d-c7f8211a2cda +# ╟─3f5e2195-da93-40fb-88ec-9e85a7bb6c24 +# ╠═3dae761f-5fb6-4f1c-a440-d2c21a6edb17 +# ╠═d48a6bfc-b533-41ca-a0de-df0af5738536 +# ╠═087015da-d85d-4ebb-861a-b89e93a4055c +# ╟─314aa77c-5906-4772-94c5-f0b7cf648de0 +# ╠═3b56f88c-0ca8-4f33-8516-11e639cb6a1a +# ╟─aab02fef-6d84-4f53-aff5-6f94bf2bd460 +# ╠═8a5ce760-55bb-4610-9226-32c3d8376748 +# ╠═a2fb8c21-c594-44f3-92df-99e6d3afeba1 +# ╟─d95198d9-fb96-4389-bf66-f039a673d1a2 +# ╠═8bb8feaa-63e7-4777-ab79-92d98f84142b +# ╟─d7d91377-2b6b-4daa-aeb6-d0b20c20cd34 +# ╠═6890e3ec-0875-423e-89c1-988411c869e3 +# ╟─cfcd040b-f762-4cf4-8b9c-1d77ac2837cd +# ╠═fb439f9d-ee88-43cd-b91b-44ed8013b27d +# ╟─83595165-3d71-43b3-93c0-4ba880e0b86e +# ╠═095e359a-2ec4-4637-8477-93510bef23ed +# ╟─31aaced7-1fe4-4117-80f3-0fa70035822a +# ╟─53addc28-7c87-4c79-ad8f-4a41a3ca5f19 +# ╠═69e21e9b-7d0e-44e3-8b33-ee7102dadadf +# ╠═b4073e81-6db8-4be6-a2d1-afa01640dbcf +# ╟─4d58a832-9157-42fe-b578-c3fc29f01ea8 +# ╠═7cf11e6f-7ef4-478f-8cc7-eae93374cc58 +# ╟─581b82c2-0957-4d20-a664-6ff80b5c40d9 +# ╠═5eda8c4a-2baa-4987-b408-dbe7383abe95 +# ╟─1c22a730-776e-4508-bdee-d8861c8949e4 +# ╠═34788b1d-1530-4b88-8690-03fe9af52a8b +# ╟─f616da2f-b51e-4328-bd3c-47e6d7377cbb +# ╟─9be6f5b3-d280-4222-ad64-4b4a7b682310 +# ╠═97636a73-4732-4377-b760-c36cef13904b +# ╟─e0318ae7-d27d-4d73-bf44-632d813e8aa5 +# ╠═a67d70ee-c8b5-4f22-926c-f48c5dcf9815 +# ╟─407437a0-36b1-452f-910c-f244aeeedc4c +# ╠═1bb5834a-6a4e-40ec-82c1-f2c505ecfeb8 +# ╟─66105045-a8f6-4513-a255-a9e1b0c78d37 +# ╠═a3156073-07bd-4f13-be5e-d19483fbad28 +# ╟─0ae57436-0b8c-41ce-9d9d-8c48ed59b820 +# ╟─92d689a4-9f2b-4a0f-81a5-6194af06940c +# ╠═87d9c826-56af-409a-a884-d784cfa16a64 +# ╟─2f784fff-90fc-4273-8b9a-db4593849652 +# ╠═4d95e77b-fab0-454b-8319-1fe83e12f9e9 +# ╟─ea46f5f9-47a8-4641-a7e3-08a1a29cd04f +# ╟─870e9229-374a-40b8-97f7-1a84d1a857e9 +# ╟─a8814cfd-8fd0-4628-b1ae-80211386a402 +# ╠═246a06ba-ef48-47ba-aef0-51f07ceb96e4 +# ╟─69965b61-043b-44a0-998b-f5f34deefdd2 +# ╠═2f992357-4608-40e6-9954-8306f31e9768 +# ╟─f87451c3-1707-4f13-a373-d8949ce4ca08 +# ╠═93457418-a032-48b6-9c63-94ce49645c68 +# ╠═cac1644a-7604-4716-85c8-63b657ff95b2 +# ╟─06645013-b63b-49b4-822b-f08059f3a394 +# ╠═de7919d3-04a8-4b4f-ad76-2f835275fc6b +# ╟─318e83fc-340b-4fd1-9e72-dbfdb891ff5f +# ╠═bcf597ef-c57f-402d-8209-511a7fc16437 +# ╟─5c2d0d0c-7bce-4696-9807-e793b6f8033e +# ╠═8ef6d8a4-be72-49dc-be44-0e4eb06336be +# ╟─fd56b95e-59a3-46e7-8216-e50181c22a58 +# ╠═daf82017-c427-480c-94d2-938e675004f0 +# ╟─b95899e9-3af9-4fc5-820b-f72164beb5db +# ╠═4adeac81-8724-4a2c-a004-17be8f92f95e +# ╟─041f6717-2709-47cb-ad54-8ef6b1a61fcc +# ╠═2180719e-ad6b-4b63-a835-3c30b811d35a +# ╟─d4af98b9-40dc-486b-bbc0-9538a193cf19 +# ╠═6ca12c8b-a535-43ca-9d6a-86e324eac51e diff --git a/src/exercises/sens_intro.jl b/src/exercises/sens_intro.jl new file mode 100644 index 00000000..19899bd0 --- /dev/null +++ b/src/exercises/sens_intro.jl @@ -0,0 +1,854 @@ +### A Pluto.jl notebook ### +# v0.19.46 + +#> [frontmatter] +#> order = "35" +#> title = "7. Sensitivity intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Sensitivity intro" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ e1e7bc8e-7264-4cbc-98d2-aa73679fa2df +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 5f4fea06-0632-11ef-102e-21f5606d2056 +using Markdown + +# ╔═╡ 489b5399-fe4c-481a-834f-0101bbe28cea +using InteractiveUtils + +# ╔═╡ 22489bd4-ab64-4bf9-ad03-5372ea273935 +using Catalyst + +# ╔═╡ cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 +using OrdinaryDiffEq, StatsPlots + +# ╔═╡ 65885bbe-7b73-4efd-a8e7-984a34b80548 +using ForwardDiff + +# ╔═╡ 427b509f-d08e-4d93-99ba-a79f9c244b28 +md""" +# Introduction to sensitivity analysis +""" + +# ╔═╡ 49e7085c-3691-4182-9630-68dc9371ad18 +md""" +## Goal of this practicum +""" + +# ╔═╡ c41894dd-0f6c-483f-b19f-dbf8148f776f +md""" +Sensitivity functions indicate how sensitive the model output is to a change in parameter values. When a model output is very sensitive to a certain parameter, a small change in the value of this parameter will have a large influence on the value of the model output. Sensitivity functions thus provide important information about the model and are implicitely used to estimate parameters and explicitely in the context of optimal experimental design. +""" + +# ╔═╡ 7b58f2f9-cb93-4a20-bf90-62a9006b57d6 +md""" +The sensitivity function that measures how sensitive output $y_j$ is to changes in parameter $\theta_i$ is given by the partial derivative + +$$\cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j}\tag{1}$$ +""" + +# ╔═╡ abd70f04-1bf8-454f-869a-0d8082d68453 +md""" +Try to understand why the above expression $(1)$ does indeed give us the information we were promised in the first paragraph. How will we be able to see from the value determined by the above expression $(1)$ that whether or not output $y_j$ is sensitive to a change in $\theta_i$? +""" + +# ╔═╡ e76aedec-69f2-4301-b015-6960e4503c42 +md""" +Answer: missing +""" + +# ╔═╡ a6a97279-0b64-45c3-8312-c22b1a8425d0 +md""" +Sometimes expression $(1)$ can be evaluated analytically. Usually, however, we will have to approximate the partial derivative numerically. Expression $(1)$ can be made more specific: + +$$\cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j} \approx \cfrac{\hat{y}_i(\theta_j+\Delta\theta_j)-\hat{y}_i(\theta_j)}{\Delta\theta_j}$$ +""" + +# ╔═╡ 25d3ef62-382c-455c-92bb-fadfc650c5a6 +md""" +Thus, to calculate the sensitivity function numerically, the model is evaluated for the parameter values $\theta_i$ and $\theta_i+\Delta\theta_i$ and the difference between these evaluations is taken. +""" + +# ╔═╡ 2a544bef-d7fe-4300-bc59-69e6f7429304 +md""" +Since quantity $(1)$ is dependent on the units, a normalized variant is often used: + +$$\cfrac{\partial \hat{y}_i(\theta)}{\partial \theta_j} \cdot \cfrac{\theta_j}{\hat{y}_i} \tag{2}$$ + +The interpretation of $(2)$ is how much the output changes per cent if the parameter is increased by one per cent. It assumes positive model outputs and parameters, which is often the case for biochemical models. + +Using the normalized variant allows you to compare all possible sensitivity functions with each other. +""" + +# ╔═╡ 7107a3a9-15ef-488e-8709-52a6444d0e1c +md""" +We now calculate and interpret sensitivity functions for some given models. To illustrate the concepts, we first consider three simple models modelling the growth of grass. +""" + +# ╔═╡ 04e95855-7c4a-4d2c-b836-c5dde291adad +md""" +## Grass growth models +""" + +# ╔═╡ 4673bfdf-8f4a-42bd-a026-21a66d800f2b +md""" +In this notebook, three different models will be used, each modelling the yield of grass in a grassland: + +- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ +- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ +- Gompertz growth model: $\cfrac{dW}{dt} = \left( \mu - D \ln(W) \right) W$ + +with output $W$ the grass yield, and $W_f$, $\mu$ and $D$ parameters. The table below show some typical values for the parameters: + +| | $\mu$ | $W_f$ | $D$ | +|:----------- |:----------:|:-----------:|:------------:| +| Logistic | 0.07 | 10.0 | | +| Exponential | 0.02 | 10.0 | | +| Gompertz | 0.09 | | 0.04 | + +We will use an initial condition of $W_0 = 2.0$ for each and a simulation time of $100$ days. +""" + +# ╔═╡ 8d55bc42-1f8f-4bc1-b9fa-e8152fc125ce +md""" +We will illustrate how to compute the local sensitivity functions for the logistic model. The same will be left as exercises below for the exponential and gompertz models. + +**Important:** +- We will use consequently `_log`, `_exp` and `_gom` appended to relevant variables names in order to indicate their model origin **and** to prevent cell-disabling that occurs when using the same variables names in these Notebooks. +""" + +# ╔═╡ 676119ce-f4fe-41f9-8121-b2c21f0dd28c +md""" +### Modelling logistic growth + +We will start by modelling our system and simulating using the aforementioned parameters values, initial condition and timespan. +""" + +# ╔═╡ 5492210b-6ee6-4baf-b37e-d21358cdeb60 +md""" +Implementation of the system: +""" + +# ╔═╡ 18eece66-46fa-4458-aaed-f4c8fa002c20 +growth_log = @reaction_network begin + @species W(t)=2.0 # default initial condition + @parameters μ=0.07 Wf=10.0 # default parameter values + #μ*W, 0 --> W + #μ/Wf*W, W --> 0 + μ*(1-W/Wf), W --> 2W +end + +# ╔═╡ 8bb0b24b-6cce-49cd-a625-5f375b92d9b7 +md""" +Convert the *reaction model* to check that we work with the correct differential equation: +""" + +# ╔═╡ ba181db8-d176-4b83-9168-d5939ffe9661 +osys_log = convert(ODESystem, growth_log) + +# ╔═╡ cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 +md""" +Setting initial conditions, timespan and parameter values: +""" + +# ╔═╡ cbb2ca49-b019-495b-9310-83fcc00cad26 +u0_log = [:W => 2.0] + +# ╔═╡ 0b3d35bb-c5b0-44b7-94b3-06fa571d339e +tspan = (0.0, 100.0) # this will be the same for the three models + +# ╔═╡ be565a3c-31b6-4df1-b73b-08f308a8c09b +md""" +For the sake of clarity, we will use the variables `μ_log` and `Wf_log` to store the parameter values. +""" + +# ╔═╡ f62806d1-77e1-470b-9711-33a924c788cc +μ_log = 0.07 + +# ╔═╡ 546ed163-26a7-4235-982f-7568ed609488 +Wf_log = 10.0 + +# ╔═╡ 0da53fa2-5a42-46e6-8bd8-45d6aa903d46 +params_log = [:μ => μ_log, :Wf => Wf_log] + +# ╔═╡ b1298f40-4696-49d0-ac94-896e0cdbc996 +md""" +Creating and solving the ODEProblem and plotting results: +""" + +# ╔═╡ 270647d2-1371-4272-8bc1-3a6ad77bc716 +oprob_log = ODEProblem(growth_log, u0_log, tspan, params_log) +# Also possible: +# oprob_log = ODEProblem(growth_mod_log, [], tspan, []) + +# ╔═╡ ac235d86-1d93-4944-aa89-1b4fd38f0e6e +osol_log = solve(oprob_log, Tsit5(), saveat=0.5) + +# ╔═╡ 3e13efa1-9bc6-456f-8e62-ecd3165e2a65 +plot(osol_log) + +# ╔═╡ 79fe5411-fb78-490e-a9de-1868d1261aa6 +md""" +### Local Sensitivity Analysis (LSA) +""" + +# ╔═╡ c57938c8-566a-4502-a2e1-69dc77291500 +md""" +In order to compute the local sensitivity functions, we will need to load the `ForwardDiff` package: +""" + +# ╔═╡ 4aa73da9-a394-4ca3-a839-d076eb3c3d7f +md""" +We need to write a solution function with as argument a vector of the parameters (that you want the sensitivity on), and that returns the solution (time vector and outputs). +""" + +# ╔═╡ bc201b61-0f58-49d2-a20b-4f18c42fcc96 +function growth_sim_log(params) + μ, Wf = params + u0_log = [:W => 2.0] + tspan = (0.0, 100.0) + oprob_log = ODEProblem(growth_log, u0_log, tspan, [:μ=>μ, :Wf=>Wf]) + osol_log = solve(oprob_log, Tsit5(), saveat=0.5) + return osol_log +end + +# ╔═╡ 11f15a0e-2958-4075-928c-5a24bcc00c69 +md""" +Next we will need to make a function based on the solution function that returns a single output. +""" + +# ╔═╡ d0f3197f-3094-4f6e-b33c-e5b74e0947f6 +growth_sim_W_log(params) = growth_sim_log(params)[:W] + +# ╔═╡ ba4eaa90-b97a-4b71-b3e1-968340c6def5 +md""" +Now make a time vector that is the same as the time vector from the solution. +""" + +# ╔═╡ 71f897a6-1e91-412b-ad58-c5f1e7cd1adb +t_vals_log = 0:0.5:100.0 +# Alternative: +# t_vals_log = tspan[1]:0.5:tspan[2] + +# ╔═╡ b3b97b57-a4f7-4cb6-80c8-6ad893ade75d +md""" +Compute the single output with the given parameter values. +""" + +# ╔═╡ 99864b21-3db0-4d60-a84e-8e96db4de4ae +W_log = growth_sim_W_log([μ_log, Wf_log]) + +# ╔═╡ 344eb3a7-8614-4349-a536-62d9acef6bda +md""" +Use the function `ForwardDiff.jacobian` to compute the sensitivities. This function takes two arguments: the solution function and a vector with the parameter values. +""" + +# ╔═╡ 670ccfa4-24fa-4167-a46a-2a48dc19538b +sens_W_log = ForwardDiff.jacobian(growth_sim_W_log, [μ_log, Wf_log]) + +# ╔═╡ c262d3ed-9f62-45ad-a7ee-ec78ab16f35c +md""" +To get the sensitivities of $W$ on $\mu$, and of $W$ on $W_f$, you need to use indexing with `sens_W_log`: +- `sens_W_log[:,1]` gives the (absolute) sensitivity of $W$ on $\mu$. +- `sens_W_log[:,2]` gives the (absolute) sensitivity of $W$ on $W_f$. +""" + +# ╔═╡ 6600d28e-2522-4069-a5e0-643be43f6117 +sens_W_on_μ_log = sens_W_log[:,1] # sensitivity of W on μ + +# ╔═╡ d9e3a4ac-c138-4b93-a19c-737166a0f0ea +sens_W_on_Wf_log = sens_W_log[:,2] # sensitivity of W on Wf + +# ╔═╡ f07d8205-e172-45fa-b955-792bd95f3023 +md""" +We now calculate the normalized sensitivities. For that we need to multiply by the parameter value and divide by the ouput. Beware that all element wise operations need a dot in front of the operator, e.g. as in `.*` and `./`. +""" + +# ╔═╡ 1c142797-ada6-4d77-a22f-b981ffd38956 +sens_W_on_μ_rel_log = sens_W_on_μ_log .* μ_log ./ W_log + +# ╔═╡ cb397e0b-56f9-420f-a86e-bfab35286b44 +sens_W_on_Wf_rel_log = sens_W_on_Wf_log .* Wf_log ./ W_log + +# ╔═╡ 9e6946a4-f207-4bfb-9e19-aa7b77c2a05b +md""" +We are now ready to plot the two sensitivity functions. We provide the time vector (first argument) and a vector of the two sensitivity functions (second argument). Additionally, you can provide a title, (legend) labels and a x- and/or y-label. +""" + +# ╔═╡ f58ce914-f366-4087-a7d1-8cfe69ac623b +plot(t_vals_log, [sens_W_on_μ_rel_log, sens_W_on_Wf_rel_log], title="Normalized sensitivities", label=["W on μ" "W on Wf"], xlabel="Time (day)") + +# ╔═╡ 05972c7f-f64f-4865-b0a7-f33029d0a6fa +md""" +Notice that in the `label` option there is no comma separating the labels. +""" + +# ╔═╡ 88aa13f6-e16c-40cd-ac10-3ede8b9fb429 +# ╠═╡ disabled = true +#=╠═╡ +md""" +#### Alternative + +For each plot command we need to provide the time vector (first argument) and the column vector with the local sensitivity (second argument). Additionally, you can provide a title, a (legend) label and a x- and/or y-label. + +Beware that if you want to execute multiple commands (here: `plot`) in a single cell, you need to put them in a `begin`-and-`end` block. Also if you want to visualize subsequent graphs in the same plot, the forthcoming plot command names should be followed by a `!`, as in `plot!(...)`. +""" + ╠═╡ =# + +# ╔═╡ dc0507ee-0f67-4556-bdc3-1294177c86b7 +# ╠═╡ disabled = true +#=╠═╡ +begin +plot(t_vals_log, sens_W_on_μ_rel_log, title="Normalized sensitivities", label="W on μ", xlabel="Time (day)") +plot!(t_vals_log, sens_W_on_Wf_rel_log, label="W on Wf") +end + ╠═╡ =# + +# ╔═╡ 04a1d1ad-53a9-4146-be09-65aa422e3730 +md""" +Conclusions: +- From the sensitivity plot of $W$ on $\mu$ it can be seen that $W$ is most sensitive to $\mu$ in the time region $[0, 30]$. The latter corresponds to the region where the yield rate is largest (i.e., when the growth is largest). This makes sense because when looking at the differential equation, $\mu$ is approximately the growth rate for relatively small $W$ values. +- From the sensitivity plot of $W$ on $W_f$ it can be seen that $W$ is most sensitive to $W_f$ in the region where time values are large (cf. operating point). The latter corresponds to the region where the yield rate stagnates (i.e., when the yield reaches a steady value). This makes sense because when looking at the differential equation, $W_f$ is the steady state value. +""" + +# ╔═╡ 79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 +md""" +## Exercises +""" + +# ╔═╡ fa2270d0-2548-409c-a23f-4369d8bce8ec +md""" +### Exercise 1 - Sensitivity analysis of the exponential growth model +""" + +# ╔═╡ dca50b37-b06c-4efe-881e-cf966ebc8fd7 +md""" +Create a *reaction network object* for the exponential growth model. Name it `growth_exp`. +""" + +# ╔═╡ 2836f231-3b90-4829-9012-3ed9a09239ff +# Uncomment and complete the instruction +# growth_exp = @reaction_network begin +# @species missing +# @parameters missing +# missing +# end +growth_exp = @reaction_network begin + @species W(t)=2.0 + @parameters μ=0.02 Wf=10.0 + (μ*Wf, μ), 0 <--> W + # Alternative: + # μ*Wf, 0 --> W + # μ, W --> 0 +end + +# ╔═╡ e8db84be-31a0-415d-a194-064c4c87a293 +md""" +Convert the system to a symbolic differential equation model (name it: `osys_exp`) and verify, by analyzing the differential equation, that your model has been correctly implemented. +""" + +# ╔═╡ 78d175b4-a9b2-49b5-bbc8-eb348799985b +# osys_exp = missing # Uncomment and complete the instruction +osys_exp = convert(ODESystem, growth_exp) + +# ╔═╡ 63952c54-3304-4500-9536-b375c5c8f280 +md""" +Initialize a vector `u0_exp` with the initial condition: +""" + +# ╔═╡ 7dc013f7-7b35-42e4-aa77-0dca3755f389 +# u0_exp = missing # Uncomment and complete the instruction +u0_exp = [:W => 2.0] + +# ╔═╡ 86785c55-b567-449c-ae76-bc15a16223bc +md""" +We will use the same timespan as before, so no need to redefine it. +""" + +# ╔═╡ 05e96232-ce95-452b-98e4-e79817d45ae2 +md""" +For the sake of clarity, we will use the variables `μ_exp` and `Wf_exp` to store the parameter values. +""" + +# ╔═╡ 9a705b3f-3c45-4896-9b89-e9c37d47fd89 +μ_exp = 0.02 + +# ╔═╡ 55a13ebc-09e7-433c-83a8-9b6805e2fbec +Wf_exp = 10.0 + +# ╔═╡ f1293675-b4d0-4827-8714-65a593ecc00e +md""" +Initialize a vector `params_exp` with the parameter values: +""" + +# ╔═╡ 7005d88f-9e28-47fe-9a32-dec023903dc3 +# params_exp = missing # Uncomment and complete the instruction +params_exp = [:μ => μ_exp, :Wf => Wf_exp] + +# ╔═╡ c8853554-26c5-491c-a536-4c07e8c6a986 +md""" +Create the ODE problem and store it in `oprob_exp`: +""" + +# ╔═╡ 025c1154-b2ae-4e1c-af1b-277b24d648a4 +# oprob_exp = missing # Uncomment and complete the instruction +oprob_exp = ODEProblem(growth_exp, u0_exp, tspan, params_exp) + +# ╔═╡ f58825d2-55fc-43f1-b164-9555bf9f5b84 +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol_exp`: +""" + +# ╔═╡ 8044dd5f-9996-4062-a54a-8fb0eee30b46 +# osol_exp = missing # Uncomment and complete the instruction +osol_exp = solve(oprob_exp, Tsit5(), saveat=0.5) + +# ╔═╡ a1752ad4-2c11-416c-8321-9da058f9aaea +md""" +Plot the result: +""" + +# ╔═╡ 0f58fd3d-1495-46c7-9d92-5539395cf12e +# missing # Uncomment and complete the instruction +plot(osol_exp) + +# ╔═╡ 6321774c-5bbe-42a7-bdad-0168c891b5ce +md""" +Write a solution function with as argument a vector of the parameters (that you want the sensitivity on), and that returns the outputs. +""" + +# ╔═╡ eb1e5f97-3e09-43f0-b6ce-cc6cbc42ec2f +# Uncomment and complete the instruction +# function growth_sim_exp(params) +# missing +# ... +# end +function growth_sim_exp(params) + μ, Wf = params + u0_exp = [:W => 2.0] + tspan = (0.0, 100.0) + oprob_exp = ODEProblem(growth_exp, u0_exp, tspan, [:μ=>μ, :Wf=>Wf]) + osol_exp = solve(oprob_exp, Tsit5(), saveat=0.5) + return osol_exp +end + +# ╔═╡ b31b4d24-ae30-4cff-b6b7-348200feaa5d +md""" +Make a function based on the solution function that returns a single output. +""" + +# ╔═╡ f2fa716f-d7b5-4c95-a8c6-6b16fbfe1499 +# growth_sim_W_exp(params) = missing # Uncomment and complete the instruction +growth_sim_W_exp(params) = growth_sim_exp(params)[:W] + +# ╔═╡ 0b413b98-a800-44e9-a917-ba61467bd613 +md""" +Make the time vector. +""" + +# ╔═╡ 6be6fea7-561b-4eec-b249-fa4522a5b039 +# t_vals = missing # Uncomment and complete the instruction +t_vals_exp = 0:0.5:100.0 + +# ╔═╡ 98070d41-e8c5-49e6-9eea-04c718cbff65 +md""" +Compute the output for the given parameter values. +""" + +# ╔═╡ cef751aa-f9c8-46da-9436-9cc2e5d7515c +# W_exp = missing # Uncomment and complete the instruction +W_exp = growth_sim_W_exp([μ_exp, Wf_exp]) + +# ╔═╡ 9e7bb7ec-8fe3-422e-920f-13c2ef055feb +md""" +Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouput(s). +""" + +# ╔═╡ b13b5e77-ca60-465b-9f63-be9e5da0482f +# sens_W_exp = missing # Uncomment and complete the instruction +sens_W_exp = ForwardDiff.jacobian(growth_sim_W_exp, [μ_exp, Wf_exp]) + +# ╔═╡ 735f5191-259e-440f-a379-20a2a70ec72c +md""" +Extract the (absolute) sensitivities of the outputs on the different parameters. +""" + +# ╔═╡ 689556c9-a690-4e9e-9052-7ac66e999d4d +# sens_W_on_μ_exp = missing # Uncomment and complete the instruction +sens_W_on_μ_exp = sens_W_exp[:,1] + +# ╔═╡ b85f0d09-a102-435b-a898-149ba4b29caa +# sens_W_on_Wf_exp = missing # Uncomment and complete the instruction +sens_W_on_Wf_exp = sens_W_exp[:,2] + +# ╔═╡ d067f814-1dd5-4e59-8cfd-3bc0b2d97612 +md""" +Compute the normalized sensitivities. +""" + +# ╔═╡ e01657f3-33ac-4eb8-b39e-e2441a165a8d +# sens_W_on_μ_rel_exp = missing # Uncomment and complete the instruction +sens_W_on_μ_rel_exp = sens_W_on_μ_exp .* μ_exp ./ W_exp + +# ╔═╡ 7231e54e-b293-48fc-b1f0-273f45f51539 +# sens_W_on_Wf_rel_exp = missing # Uncomment and complete the instruction +sens_W_on_Wf_rel_exp = sens_W_on_Wf_exp .* Wf_exp ./ W_exp + +# ╔═╡ 0524a976-9d4d-4ef9-b088-bdd9102415e6 +md""" +Plot both normalized sensitivity functions (with appropriate title and labels): +""" + +# ╔═╡ 33031b53-6f2d-4256-a0aa-0ba97beaa7af +# missing # Uncomment and complete the instruction +plot(t_vals_exp, [sens_W_on_μ_rel_exp, sens_W_on_Wf_rel_exp], title="Normalized sensitivities", label=["W on μ" "W on Wf"], xlabel="Time (day)") + +# ╔═╡ 22b58c97-1aa2-49c4-8a0a-488c63014a90 +md" +Draw your conclusions: + +- missing +- missing +" + +# ╔═╡ 41abf8c4-e67e-4f66-a38a-7204a878d98d +md""" +### Exercise 2 - Sensitivity analysis of the Gompertz growth model +""" + +# ╔═╡ f311e943-c96b-4af3-8757-c662fd302a88 +md""" +Create a *reaction network object* for the Gompertz growth model. Name it `growth_gom`. +""" + +# ╔═╡ 1ae76036-42ef-46e9-88bf-d66d4267addc +# Uncomment and complete the instruction +# growth_gom = @reaction_network begin +# @species missing +# @parameters missing +# missing +# end +growth_gom = @reaction_network begin + @species W(t)=2.0 + @parameters μ=0.09 D=0.04 + μ-D*log(W), W --> 2*W + # Alternative: + # μ, W --> 2*W + # D*log(W), W --> 0 +end + +# ╔═╡ e190ceca-30b1-49e2-baf5-ceb62929f4c0 +md""" +Convert the system to a symbolic differential equation model (name it: `osys_gom`) and verify, by analyzing the differential equation, that your model has been correctly implemented. +""" + +# ╔═╡ acaf5b91-accc-4dfa-9371-6495a47c8736 +# osys_gom = missing # Uncomment and complete the instruction +osys_gom = convert(ODESystem, growth_gom) + +# ╔═╡ ed1150f1-9c75-4867-b7f5-535d605810f4 +md""" +Initialize a vector `u0_gom` with the initial condition: +""" + +# ╔═╡ d35ef155-61aa-4b19-872f-d6e621f96572 +# u0_gom = missing # Uncomment and complete the instruction +u0_gom = [:W => 2.0] + +# ╔═╡ 3f546d58-15ff-442d-a4e8-7c0783ed2fe0 +md""" +We will use the same timespan as before, so no need to redefine it. +""" + +# ╔═╡ b778f786-87bc-4103-a792-2ae5cf004d50 +md""" +For the sake of clarity, we will use the variables `μ_gom` and `D_gom` to store the parameter values. +""" + +# ╔═╡ edd76a4e-e541-4a83-923f-6f593d8fadad +μ_gom = 0.09 + +# ╔═╡ 05f1ae6c-1229-4924-9c41-9554d6ca4261 +D_gom = 0.04 + +# ╔═╡ 70df05e7-4527-4087-a762-942a33e89f74 +md""" +Initialize a vector `params_gom` with the parameter values: +""" + +# ╔═╡ bf9291d5-6fc5-4359-874e-167237a22147 +# params_gom = missing # Uncomment and complete the instruction +params_gom = [:μ => μ_gom, :D => D_gom] + +# ╔═╡ 9e3158c5-d4b2-456c-b920-8431e2047770 +md""" +Create the ODE problem and store it in `oprob_gom`: +""" + +# ╔═╡ 3aab9073-cf4d-4ee5-9e8a-e3db586d9f68 +# oprob_gom = missing # Uncomment and complete the instruction +oprob_gom = ODEProblem(growth_gom, u0_gom, tspan, params_gom) + +# ╔═╡ 30c35371-d106-4673-8569-d07b39edbdfe +md""" +Solve the ODE problem. Use `Tsit5()` and `saveat=0.5`. Store the solution in `osol_gom`: +""" + +# ╔═╡ c01fe052-3701-4e22-9a64-f681d52a445a +# osol_gom = missing # Uncomment and complete the instruction +osol_gom = solve(oprob_gom, Tsit5(), saveat=0.5) + +# ╔═╡ 4f959d21-954b-4116-a498-bafc1aada47a +md""" +Plot the result: +""" + +# ╔═╡ 5f427df2-1b7e-46c7-adb9-66b05deac6e8 +# missing # Uncomment and complete the instruction +plot(osol_gom) + +# ╔═╡ 958854fc-8e23-4364-88c5-1ec060b86a16 +md""" +Write a solution function with as argument a vector of the parameters (that you want the sensitivity on), and that returns the outputs. +""" + +# ╔═╡ be21e269-16c4-42b0-8930-2d009bd91161 +# Uncomment and complete the instruction +# function growth_sim_gom(params) +# missing +# ... +# end +function growth_sim_gom(params) + μ, D = params + u0_gom = [:W => 2.0] + tspan = (0.0, 100.0) + oprob_gom = ODEProblem(growth_gom, u0_gom, tspan, [:μ=>μ, :D=>D]) + osol_gom = solve(oprob_gom, Tsit5(), saveat=0.5) + return osol_gom +end + +# ╔═╡ e57f5916-b753-429b-8434-8a85a67bd1fb +md""" +Make a function based on the solution function that returns a single output. +""" + +# ╔═╡ 69f96b57-7c8d-4e64-9e6a-9804d001b0b1 +# growth_sim_W_gom(params) = missing # Uncomment and complete the instruction +growth_sim_W_gom(params) = growth_sim_gom(params)[:W] + +# ╔═╡ f7f6c56b-4910-4641-a462-eacfa4b4d034 +md""" +Make the time vector. +""" + +# ╔═╡ 84c1bb38-23c3-4f70-be67-7da2300a737b +# t_vals_gom = missing # Uncomment and complete the instruction +t_vals_gom = 0:0.5:100.0 + +# ╔═╡ 09eeab39-79a1-420a-8298-72b7002ec168 +md""" +Compute the output for the given parameter values. +""" + +# ╔═╡ 9e15601f-4ad2-41be-8168-db30548a1c3b +# W_gom = missing # Uncomment and complete the instruction +W_gom = growth_sim_W_gom([μ_gom, D_gom]) + +# ╔═╡ 2b731e4a-d6eb-48f2-91f0-27e916d85683 +md""" +Using `ForwardDiff.jacobian` to compute the sensitivities for the single ouput(s). +""" + +# ╔═╡ 68ad7e05-7f79-4e8d-9031-fefb5f5c0897 +# sens_W_gom = missing # Uncomment and complete the instruction +sens_W_gom = ForwardDiff.jacobian(growth_sim_W_gom, [μ_gom, D_gom]) + +# ╔═╡ 362a8e24-8fac-4978-bc0e-13596a05d39e +md""" +Extract the (absolute) sensitivities of the outputs on the different parameters. +""" + +# ╔═╡ e9114d64-fe3e-420d-9fde-4b0fcbbc8527 +# sens_W_on_μ_gom = missing # Uncomment and complete the instruction +sens_W_on_μ_gom = sens_W_gom[:,1] + +# ╔═╡ 7c9e1112-0a63-417f-a65a-9c2bdfa8bc47 +# sens_W_on_D_gom = missing # Uncomment and complete the instruction +sens_W_on_D_gom = sens_W_gom[:,2] + +# ╔═╡ 94e66042-b352-4bf2-a24b-b15789e10fe3 +md""" +Compute the normalized sensitivities. +""" + +# ╔═╡ 1711346a-c161-415a-96f9-1235d786a584 +# sens_W_on_μ_rel_gom = missing # Uncomment and complete the instruction +sens_W_on_μ_rel_gom = sens_W_on_μ_gom .* μ_gom ./ W_gom + +# ╔═╡ 7a87bfbb-a8c0-4071-8f86-66513ae40968 +# sens_W_on_D_rel_gom = missing # Uncomment and complete the instruction +sens_W_on_D_rel_gom = sens_W_on_D_gom .* D_gom ./ W_gom + +# ╔═╡ 7593a4bf-ad0f-4fd3-aa3c-8b6a5073cb02 +md" +Plot both sensitivity functions (with appropriate title and labels): +" + +# ╔═╡ ad70a1d5-9b68-4cd6-9259-a3c25cad706b +# missing # Uncomment and complete the instruction +plot(t_vals_gom, [sens_W_on_μ_rel_gom, sens_W_on_D_rel_gom], title="Normalized sensitivities", label=["W on μ" "W on D"], xlabel="Time (day)") + +# ╔═╡ 12f64333-36d0-4e2a-9061-e3dcc7a4ae96 +md" +Draw your conclusions: + +- missing +- missing +" + +# ╔═╡ Cell order: +# ╠═5f4fea06-0632-11ef-102e-21f5606d2056 +# ╠═489b5399-fe4c-481a-834f-0101bbe28cea +# ╠═e1e7bc8e-7264-4cbc-98d2-aa73679fa2df +# ╟─427b509f-d08e-4d93-99ba-a79f9c244b28 +# ╟─49e7085c-3691-4182-9630-68dc9371ad18 +# ╟─c41894dd-0f6c-483f-b19f-dbf8148f776f +# ╟─7b58f2f9-cb93-4a20-bf90-62a9006b57d6 +# ╟─abd70f04-1bf8-454f-869a-0d8082d68453 +# ╟─e76aedec-69f2-4301-b015-6960e4503c42 +# ╟─a6a97279-0b64-45c3-8312-c22b1a8425d0 +# ╟─25d3ef62-382c-455c-92bb-fadfc650c5a6 +# ╟─2a544bef-d7fe-4300-bc59-69e6f7429304 +# ╟─7107a3a9-15ef-488e-8709-52a6444d0e1c +# ╟─04e95855-7c4a-4d2c-b836-c5dde291adad +# ╟─4673bfdf-8f4a-42bd-a026-21a66d800f2b +# ╟─8d55bc42-1f8f-4bc1-b9fa-e8152fc125ce +# ╠═22489bd4-ab64-4bf9-ad03-5372ea273935 +# ╠═cdcebbb1-40e0-457f-a6ec-b769f6b1f2e9 +# ╟─676119ce-f4fe-41f9-8121-b2c21f0dd28c +# ╟─5492210b-6ee6-4baf-b37e-d21358cdeb60 +# ╠═18eece66-46fa-4458-aaed-f4c8fa002c20 +# ╟─8bb0b24b-6cce-49cd-a625-5f375b92d9b7 +# ╠═ba181db8-d176-4b83-9168-d5939ffe9661 +# ╟─cd8a7ba1-194a-4b42-8c87-2c9b1fe6b475 +# ╠═cbb2ca49-b019-495b-9310-83fcc00cad26 +# ╠═0b3d35bb-c5b0-44b7-94b3-06fa571d339e +# ╟─be565a3c-31b6-4df1-b73b-08f308a8c09b +# ╠═f62806d1-77e1-470b-9711-33a924c788cc +# ╠═546ed163-26a7-4235-982f-7568ed609488 +# ╠═0da53fa2-5a42-46e6-8bd8-45d6aa903d46 +# ╟─b1298f40-4696-49d0-ac94-896e0cdbc996 +# ╠═270647d2-1371-4272-8bc1-3a6ad77bc716 +# ╠═ac235d86-1d93-4944-aa89-1b4fd38f0e6e +# ╠═3e13efa1-9bc6-456f-8e62-ecd3165e2a65 +# ╟─79fe5411-fb78-490e-a9de-1868d1261aa6 +# ╟─c57938c8-566a-4502-a2e1-69dc77291500 +# ╠═65885bbe-7b73-4efd-a8e7-984a34b80548 +# ╟─4aa73da9-a394-4ca3-a839-d076eb3c3d7f +# ╠═bc201b61-0f58-49d2-a20b-4f18c42fcc96 +# ╟─11f15a0e-2958-4075-928c-5a24bcc00c69 +# ╠═d0f3197f-3094-4f6e-b33c-e5b74e0947f6 +# ╟─ba4eaa90-b97a-4b71-b3e1-968340c6def5 +# ╠═71f897a6-1e91-412b-ad58-c5f1e7cd1adb +# ╟─b3b97b57-a4f7-4cb6-80c8-6ad893ade75d +# ╠═99864b21-3db0-4d60-a84e-8e96db4de4ae +# ╟─344eb3a7-8614-4349-a536-62d9acef6bda +# ╠═670ccfa4-24fa-4167-a46a-2a48dc19538b +# ╟─c262d3ed-9f62-45ad-a7ee-ec78ab16f35c +# ╠═6600d28e-2522-4069-a5e0-643be43f6117 +# ╠═d9e3a4ac-c138-4b93-a19c-737166a0f0ea +# ╟─f07d8205-e172-45fa-b955-792bd95f3023 +# ╠═1c142797-ada6-4d77-a22f-b981ffd38956 +# ╠═cb397e0b-56f9-420f-a86e-bfab35286b44 +# ╟─9e6946a4-f207-4bfb-9e19-aa7b77c2a05b +# ╠═f58ce914-f366-4087-a7d1-8cfe69ac623b +# ╟─05972c7f-f64f-4865-b0a7-f33029d0a6fa +# ╠═88aa13f6-e16c-40cd-ac10-3ede8b9fb429 +# ╠═dc0507ee-0f67-4556-bdc3-1294177c86b7 +# ╟─04a1d1ad-53a9-4146-be09-65aa422e3730 +# ╟─79f0f1dd-850f-4dfd-b895-ff41a2d8adb8 +# ╟─fa2270d0-2548-409c-a23f-4369d8bce8ec +# ╟─dca50b37-b06c-4efe-881e-cf966ebc8fd7 +# ╠═2836f231-3b90-4829-9012-3ed9a09239ff +# ╟─e8db84be-31a0-415d-a194-064c4c87a293 +# ╠═78d175b4-a9b2-49b5-bbc8-eb348799985b +# ╟─63952c54-3304-4500-9536-b375c5c8f280 +# ╠═7dc013f7-7b35-42e4-aa77-0dca3755f389 +# ╟─86785c55-b567-449c-ae76-bc15a16223bc +# ╟─05e96232-ce95-452b-98e4-e79817d45ae2 +# ╠═9a705b3f-3c45-4896-9b89-e9c37d47fd89 +# ╠═55a13ebc-09e7-433c-83a8-9b6805e2fbec +# ╟─f1293675-b4d0-4827-8714-65a593ecc00e +# ╠═7005d88f-9e28-47fe-9a32-dec023903dc3 +# ╟─c8853554-26c5-491c-a536-4c07e8c6a986 +# ╠═025c1154-b2ae-4e1c-af1b-277b24d648a4 +# ╟─f58825d2-55fc-43f1-b164-9555bf9f5b84 +# ╠═8044dd5f-9996-4062-a54a-8fb0eee30b46 +# ╟─a1752ad4-2c11-416c-8321-9da058f9aaea +# ╠═0f58fd3d-1495-46c7-9d92-5539395cf12e +# ╟─6321774c-5bbe-42a7-bdad-0168c891b5ce +# ╠═eb1e5f97-3e09-43f0-b6ce-cc6cbc42ec2f +# ╟─b31b4d24-ae30-4cff-b6b7-348200feaa5d +# ╠═f2fa716f-d7b5-4c95-a8c6-6b16fbfe1499 +# ╟─0b413b98-a800-44e9-a917-ba61467bd613 +# ╠═6be6fea7-561b-4eec-b249-fa4522a5b039 +# ╟─98070d41-e8c5-49e6-9eea-04c718cbff65 +# ╠═cef751aa-f9c8-46da-9436-9cc2e5d7515c +# ╟─9e7bb7ec-8fe3-422e-920f-13c2ef055feb +# ╠═b13b5e77-ca60-465b-9f63-be9e5da0482f +# ╟─735f5191-259e-440f-a379-20a2a70ec72c +# ╠═689556c9-a690-4e9e-9052-7ac66e999d4d +# ╠═b85f0d09-a102-435b-a898-149ba4b29caa +# ╟─d067f814-1dd5-4e59-8cfd-3bc0b2d97612 +# ╠═e01657f3-33ac-4eb8-b39e-e2441a165a8d +# ╠═7231e54e-b293-48fc-b1f0-273f45f51539 +# ╟─0524a976-9d4d-4ef9-b088-bdd9102415e6 +# ╠═33031b53-6f2d-4256-a0aa-0ba97beaa7af +# ╟─22b58c97-1aa2-49c4-8a0a-488c63014a90 +# ╟─41abf8c4-e67e-4f66-a38a-7204a878d98d +# ╟─f311e943-c96b-4af3-8757-c662fd302a88 +# ╠═1ae76036-42ef-46e9-88bf-d66d4267addc +# ╟─e190ceca-30b1-49e2-baf5-ceb62929f4c0 +# ╠═acaf5b91-accc-4dfa-9371-6495a47c8736 +# ╟─ed1150f1-9c75-4867-b7f5-535d605810f4 +# ╠═d35ef155-61aa-4b19-872f-d6e621f96572 +# ╟─3f546d58-15ff-442d-a4e8-7c0783ed2fe0 +# ╟─b778f786-87bc-4103-a792-2ae5cf004d50 +# ╠═edd76a4e-e541-4a83-923f-6f593d8fadad +# ╠═05f1ae6c-1229-4924-9c41-9554d6ca4261 +# ╟─70df05e7-4527-4087-a762-942a33e89f74 +# ╠═bf9291d5-6fc5-4359-874e-167237a22147 +# ╟─9e3158c5-d4b2-456c-b920-8431e2047770 +# ╠═3aab9073-cf4d-4ee5-9e8a-e3db586d9f68 +# ╟─30c35371-d106-4673-8569-d07b39edbdfe +# ╠═c01fe052-3701-4e22-9a64-f681d52a445a +# ╟─4f959d21-954b-4116-a498-bafc1aada47a +# ╠═5f427df2-1b7e-46c7-adb9-66b05deac6e8 +# ╟─958854fc-8e23-4364-88c5-1ec060b86a16 +# ╠═be21e269-16c4-42b0-8930-2d009bd91161 +# ╟─e57f5916-b753-429b-8434-8a85a67bd1fb +# ╠═69f96b57-7c8d-4e64-9e6a-9804d001b0b1 +# ╟─f7f6c56b-4910-4641-a462-eacfa4b4d034 +# ╠═84c1bb38-23c3-4f70-be67-7da2300a737b +# ╟─09eeab39-79a1-420a-8298-72b7002ec168 +# ╠═9e15601f-4ad2-41be-8168-db30548a1c3b +# ╟─2b731e4a-d6eb-48f2-91f0-27e916d85683 +# ╠═68ad7e05-7f79-4e8d-9031-fefb5f5c0897 +# ╟─362a8e24-8fac-4978-bc0e-13596a05d39e +# ╠═e9114d64-fe3e-420d-9fde-4b0fcbbc8527 +# ╠═7c9e1112-0a63-417f-a65a-9c2bdfa8bc47 +# ╟─94e66042-b352-4bf2-a24b-b15789e10fe3 +# ╠═1711346a-c161-415a-96f9-1235d786a584 +# ╠═7a87bfbb-a8c0-4071-8f86-66513ae40968 +# ╟─7593a4bf-ad0f-4fd3-aa3c-8b6a5073cb02 +# ╠═ad70a1d5-9b68-4cd6-9259-a3c25cad706b +# ╟─12f64333-36d0-4e2a-9061-e3dcc7a4ae96 diff --git a/src/exercises/uncert_bitrophic_model.jl b/src/exercises/uncert_bitrophic_model.jl new file mode 100644 index 00000000..fcfc6b2b --- /dev/null +++ b/src/exercises/uncert_bitrophic_model.jl @@ -0,0 +1,168 @@ +### A Pluto.jl notebook ### +# v0.20.4 + +#> [frontmatter] +#> order = "41" +#> title = "7. Uncertainty bitrophic model" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Uncertainty bitrophic model" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 7d2ce89c-de3e-4fff-8f23-8756351b377e +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 079bc7cc-0c53-11ef-2323-8b475737e481 +using Markdown + +# ╔═╡ 3d189b53-8aff-4ca0-a784-11b0bca02374 +using InteractiveUtils + +# ╔═╡ d89c7b51-a94c-42e5-94f1-2dc1e641dfb3 +using Catalyst + +# ╔═╡ 49c93319-f76d-485e-a2e2-e06210e25df3 +using OrdinaryDiffEq, StatsPlots + +# ╔═╡ b8c575fd-a94b-4221-8751-74c51f10ec7d +using Measurements + +# ╔═╡ bedf7419-590c-47fd-8089-b890dded0468 +md""" +### Exercise: Bitrophic model - Uncertainty analysis +""" + +# ╔═╡ 8f795d92-b842-4392-8bc3-956080fcc6a8 +md""" +In one of the previous practicals we were introduced to a bitrophic model in which the dynamic relationship between a field crop $C$ and a voracious insect population $A$ within an ecosystem was modelled. + +$$\begin{eqnarray*} +\frac{dC}{dt} &= \theta C \left(1-\frac{C}{k}\right)-fCA \\ +\frac{dA}{dt} &= \phi f CA -(1 + p)\, \mu A +\end{eqnarray*}$$ +""" + +# ╔═╡ bc2e3724-de5f-4455-a4be-019c6a8accae +md""" +The *reaction network object* for this model could be set-up as: +""" + +# ╔═╡ 90e9da81-7576-43bb-b4ce-1293a48bf3ec +bitrophic_model = @reaction_network begin + θ*(1-C/k), C --> 2C + f, C+A --> (1+ϕ)*A + (1+p)*μ, A --> 0 +end + +# ╔═╡ 465e4ea9-7066-4929-98c4-e5c378ef34d8 +md""" +Assume the uncertainties in the following parameter values: + +- $\theta=0.20 \pm 0.02\;d^{-1}$ +- $\phi=0.20 \pm 0.02$ +- $p=3.0 \pm 0.2$ + +and that the uncertainty in the other parameters values: $k=4000\;kg/ha$, $f=0.001\;ha/(kg\,d)$ and $\mu=0.1\;d^{-1}$ are negligible. Suppose that at the beginning of a season, $100\;kg$ of the crop and $0.5\;kg$ of insects per $ha$ are present. Perform an uncertainty analysis by plotting the uncertainty bands on the simulation results of $C$ and $A$ in a timespan of $[0, 200]\,days$. + +Interpret your results. +""" + +# ╔═╡ 1129972e-3e3d-4b3b-8c2c-3c57eaa3728a +md""" +Initialize a vector `u0` with the initial conditions, and set the timespan: +""" + +# ╔═╡ 79802166-efbd-4944-ae58-fb26a5d9ad4b +# u0 = missing # Uncomment and complete the instruction + +# ╔═╡ 146855a0-5cda-4783-89af-7918c067e952 +# tspan = missing # Uncomment and complete the instruction + +# ╔═╡ 4e8fea9e-e16c-4b17-8a6c-52bd8721cfd8 +md""" +We initialize a vector `params_uncert` with the parameter values and their corresponding uncertainty: +""" + +# ╔═╡ 0588cc16-a4b1-4b3f-8ba9-742680996e11 +# params_uncert = missing # Uncomment and complete the instruction + +# ╔═╡ ff76c3e0-396c-4b01-b979-2b95644203a0 + + +# ╔═╡ 6893ccdb-7f6c-4ec9-8d6b-520e784acd03 +md""" +We create the corresponding ODE problem and store it in `oprob_uncert`: +""" + +# ╔═╡ 60a6fb20-93d2-47d3-8ee1-3087d48c569d +# oprob_uncert = missing # Uncomment and complete the instruction + +# ╔═╡ 0739d816-0456-4cc2-beb7-2e926cf2e229 +md""" +We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert`: +""" + +# ╔═╡ f829fc66-3df8-4ac1-a1bb-8eb2ed722423 +# osol_uncert = missing # Uncomment and complete the instruction + +# ╔═╡ d4d6f69c-844b-488a-9170-b09e7c22dd25 +md""" +Plot the results (simulation of the output variables $C$ and $A$ together with their uncertainty band): +""" + +# ╔═╡ 2152a72b-31cd-44b9-90d8-615610e8d261 +# missing # Uncomment and complete the instruction + +# ╔═╡ 600dd2e9-aed9-41da-a510-9ba78788f207 +md""" +Try to relate the local sensitivity analysis to the uncertainty analysis. Hence, study the effect of the individual parameter uncertainties on the output variables $C$ and $A$ and compare with your local sensitivity results of the corresponding parameter. + +In order to do that, analyse the effect on the uncertainty bands for $C$ and $A$ by taking one uncertainty on a parameter at a time. In other words, analyse the subsequent cases separately: +- Assume uncertainty only in $\theta$ +- Assume uncertainty only in $\phi$ +- Assume uncertainty only in $p$ +""" + +# ╔═╡ 42817641-cca5-47d7-b1f3-57181a207bf4 +md""" +!!! question + Draw your conclusions. +""" + +# ╔═╡ d7cddaef-c1b9-431f-9835-94cece962ca3 +md"- Answer: missing" + +# ╔═╡ Cell order: +# ╠═079bc7cc-0c53-11ef-2323-8b475737e481 +# ╠═3d189b53-8aff-4ca0-a784-11b0bca02374 +# ╠═7d2ce89c-de3e-4fff-8f23-8756351b377e +# ╠═d89c7b51-a94c-42e5-94f1-2dc1e641dfb3 +# ╠═49c93319-f76d-485e-a2e2-e06210e25df3 +# ╠═b8c575fd-a94b-4221-8751-74c51f10ec7d +# ╟─bedf7419-590c-47fd-8089-b890dded0468 +# ╟─8f795d92-b842-4392-8bc3-956080fcc6a8 +# ╟─bc2e3724-de5f-4455-a4be-019c6a8accae +# ╠═90e9da81-7576-43bb-b4ce-1293a48bf3ec +# ╟─465e4ea9-7066-4929-98c4-e5c378ef34d8 +# ╟─1129972e-3e3d-4b3b-8c2c-3c57eaa3728a +# ╠═79802166-efbd-4944-ae58-fb26a5d9ad4b +# ╠═146855a0-5cda-4783-89af-7918c067e952 +# ╟─4e8fea9e-e16c-4b17-8a6c-52bd8721cfd8 +# ╠═0588cc16-a4b1-4b3f-8ba9-742680996e11 +# ╠═ff76c3e0-396c-4b01-b979-2b95644203a0 +# ╟─6893ccdb-7f6c-4ec9-8d6b-520e784acd03 +# ╠═60a6fb20-93d2-47d3-8ee1-3087d48c569d +# ╟─0739d816-0456-4cc2-beb7-2e926cf2e229 +# ╠═f829fc66-3df8-4ac1-a1bb-8eb2ed722423 +# ╟─d4d6f69c-844b-488a-9170-b09e7c22dd25 +# ╠═2152a72b-31cd-44b9-90d8-615610e8d261 +# ╟─600dd2e9-aed9-41da-a510-9ba78788f207 +# ╟─42817641-cca5-47d7-b1f3-57181a207bf4 +# ╠═d7cddaef-c1b9-431f-9835-94cece962ca3 diff --git a/src/exercises/uncert_fermenter_monod.jl b/src/exercises/uncert_fermenter_monod.jl new file mode 100644 index 00000000..a18375a1 --- /dev/null +++ b/src/exercises/uncert_fermenter_monod.jl @@ -0,0 +1,182 @@ +### A Pluto.jl notebook ### +# v0.20.4 + +#> [frontmatter] +#> order = "40" +#> title = "7. Uncertainty fermenter monod" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Uncertainty fermenter monod" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 9d8acf40-e635-4dc4-9938-ec63ab68e3bd +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ 1ec7def2-0c43-11ef-0850-ddefa680d2a4 +using Markdown + +# ╔═╡ 2861bc17-7810-461e-8857-a55b6392977a +using InteractiveUtils + +# ╔═╡ 093679cc-122b-4b0a-84c3-15cf3a6f5c1b +using Catalyst + +# ╔═╡ 0720732e-105a-40c2-bf45-f8fbbc10e5bc +using OrdinaryDiffEq, StatsPlots + +# ╔═╡ 28bb60c9-ab56-497f-b039-0d6577501be5 +using Measurements + +# ╔═╡ 437364b6-a953-428b-a891-e17ffd531656 +md""" +### Exercise: Fermenter - Monod kinetics - Uncertainty analysis +""" + +# ╔═╡ c44e10f3-8452-4a60-b840-f4a7e6ec3238 +md""" +In one of the previous practicals we were introduced to a fermenter in which biomass $X$ [$g/L$] grows by breaking down substrate $S$ [$g/L$]. The reactor is fed with a inlet flow rate $Q_{in}$ [$L/h$], which consist of a (manipulable) input concentration of substrate $S_{in}$ [$g/L$]. This process was modelled using Monod kinetics, resulting in the model below: + +$$\begin{eqnarray*} +S + X \xrightarrow[\quad\quad]{k} (1 + Y) \, X \quad\quad\quad\quad \textrm{with} \quad k = \cfrac{\mu_{max}}{S + K_s} +\end{eqnarray*}$$ +""" + +# ╔═╡ 356822df-bad3-41f9-8556-de04234b0a09 +md""" +The *reaction network object* for this model could be set-up as: +""" + +# ╔═╡ c551c33e-95b0-4ea8-85d4-6c68db72b8bc +fermenter_monod = @reaction_network begin + μmax/(S+Ks), S + X --> (1 + Y)*X + Q/V, (S, X) --> 0 + Q/V*Sin, 0 --> S +end + +# ╔═╡ 69f8c265-fe91-4e4b-971b-395325d4474e +md""" +which resulted in the following differential equations: +""" + +# ╔═╡ 2806654f-7a24-4f8e-8b5a-3129b2431c95 +md""" +$$\begin{eqnarray*} +\cfrac{dS}{dt} &=& \cfrac{Q}{V} \left(S_{in} - S \right) - \mu_{max}\cfrac{S}{S + K_s} X\\ +\cfrac{dX}{dt} &=& -\cfrac{Q}{V} X + Y \mu_{max}\cfrac{S}{S + K_s} X +\end{eqnarray*}$$ +""" + +# ╔═╡ 5d6a881d-421e-4dea-9668-38b3f3378bf1 +# osys = missing + +# ╔═╡ 5802078d-12bc-489e-8566-15f77a11e8ba +md""" +Assume the uncertainties in the following parameter values: + +- $\mu_{max} = 0.40 \pm 0.06\;h^{-1}$ +- $K_s = 0.015 \pm 0.003 \;g/L$ +- $S_{in} = 0.022 \pm 0.004\;g/L$ + +and that the uncertainty in the other parameters values: $Y = 0.67$, $Q = 2.0\;L/h$, $V = 40.0\;L$ are negligible. Suppose that at $t=0$ no substrate $S$ is present in the reactor but that there is initially some biomass with a concetration of $0.0005\;g/L$. Perform an uncertainty analysis by plotting the uncertainty bands on the simulation results of $S$ and $X$ in a timespan of $[0, 100]\,h$. + +Interpret your results. +""" + +# ╔═╡ 562e0006-bb79-4895-98b4-c376ea6ce856 +md""" +Initialize a vector `u0` with the initial conditions, and set the timespan: +""" + +# ╔═╡ 6afb23f2-3da7-43e0-8a3b-fc4bb5eb9bde +# u0 = missing # Uncomment and complete the instruction + +# ╔═╡ 1b09c8cb-d3b7-4a15-b6ed-d03019c5f57b +# tspan = missing # Uncomment and complete the instruction + +# ╔═╡ 40a8c697-56d1-4488-b089-00ddf41cdb5a +md""" +We initialize a vector `params_uncert` with the parameter values and their corresponding uncertainty: +""" + +# ╔═╡ 62f4049b-617f-4268-beba-b2c634b46642 +# params_uncert = missing # Uncomment and complete the instruction + +# ╔═╡ 0e4c73d3-1630-463b-994d-073170c1a6c3 +md""" +We create the corresponding ODE problem and store it in `oprob_uncert`: +""" + +# ╔═╡ 3cad11d6-c2eb-4865-858a-d407aa25d780 +# oprob_uncert = missing # Uncomment and complete the instruction + +# ╔═╡ a7b3b68f-d34e-4e9a-aa44-224919c44fec +md""" +We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert`: +""" + +# ╔═╡ 40168902-0780-4f92-8eca-23982c80511e +# osol_uncert = missing # Uncomment and complete the instruction + +# ╔═╡ 6b2abf2a-f1f3-4c2d-96e8-cc626848fa68 +md""" +Plot the results (simulation of the output variables $S$ and $X$ together with their uncertainty band): +""" + +# ╔═╡ 4d753fdb-089d-41dc-89ef-45a1733af786 +# missing + +# ╔═╡ 4f3900d7-4ff8-4b4b-a59a-f7079ac6c457 +md""" +Try to relate the local sensitivity analysis to the uncertainty analysis. Hence, study the effect of the individual parameter uncertainties on the output variables $S$ and $X$ and compare with your local sensitivity results of the corresponding parameter. + +In order to do that, analyse the effect on the uncertainty bands for $S$ and $X$ by taking one uncertainty on a parameter at a time. In other words, analyse the subsequent cases separately: +- Assume uncertainty only in $\mu_{max}$ +- Assume uncertainty only in $K_s$ +- Assume uncertainty only in $S_{in}$ +""" + +# ╔═╡ ff3e6f75-c02b-4530-8b9a-6ff40915cb5c +md""" +!!! question + Draw your conclusions. +""" + +# ╔═╡ 33b4d1c8-5da2-4490-afe9-4778e11d3cc9 +md"- Answer: missing" + +# ╔═╡ Cell order: +# ╠═1ec7def2-0c43-11ef-0850-ddefa680d2a4 +# ╠═2861bc17-7810-461e-8857-a55b6392977a +# ╠═9d8acf40-e635-4dc4-9938-ec63ab68e3bd +# ╠═093679cc-122b-4b0a-84c3-15cf3a6f5c1b +# ╠═0720732e-105a-40c2-bf45-f8fbbc10e5bc +# ╠═28bb60c9-ab56-497f-b039-0d6577501be5 +# ╟─437364b6-a953-428b-a891-e17ffd531656 +# ╟─c44e10f3-8452-4a60-b840-f4a7e6ec3238 +# ╟─356822df-bad3-41f9-8556-de04234b0a09 +# ╠═c551c33e-95b0-4ea8-85d4-6c68db72b8bc +# ╟─69f8c265-fe91-4e4b-971b-395325d4474e +# ╟─2806654f-7a24-4f8e-8b5a-3129b2431c95 +# ╠═5d6a881d-421e-4dea-9668-38b3f3378bf1 +# ╟─5802078d-12bc-489e-8566-15f77a11e8ba +# ╟─562e0006-bb79-4895-98b4-c376ea6ce856 +# ╠═6afb23f2-3da7-43e0-8a3b-fc4bb5eb9bde +# ╠═1b09c8cb-d3b7-4a15-b6ed-d03019c5f57b +# ╟─40a8c697-56d1-4488-b089-00ddf41cdb5a +# ╠═62f4049b-617f-4268-beba-b2c634b46642 +# ╟─0e4c73d3-1630-463b-994d-073170c1a6c3 +# ╠═3cad11d6-c2eb-4865-858a-d407aa25d780 +# ╟─a7b3b68f-d34e-4e9a-aa44-224919c44fec +# ╠═40168902-0780-4f92-8eca-23982c80511e +# ╟─6b2abf2a-f1f3-4c2d-96e8-cc626848fa68 +# ╠═4d753fdb-089d-41dc-89ef-45a1733af786 +# ╟─4f3900d7-4ff8-4b4b-a59a-f7079ac6c457 +# ╟─ff3e6f75-c02b-4530-8b9a-6ff40915cb5c +# ╠═33b4d1c8-5da2-4490-afe9-4778e11d3cc9 diff --git a/src/exercises/uncert_intro.jl b/src/exercises/uncert_intro.jl new file mode 100644 index 00000000..f676d283 --- /dev/null +++ b/src/exercises/uncert_intro.jl @@ -0,0 +1,637 @@ +### A Pluto.jl notebook ### +# v0.20.21 + +#> [frontmatter] +#> order = "39" +#> title = "7. Uncertainty intro" +#> tags = ["exercises"] +#> layout = "layout.jlhtml" +#> description = "Uncertainty intro" +#> +#> [[frontmatter.author]] +#> name = "Gauthier Vanhaelewyn" + +using Markdown +using InteractiveUtils + +# ╔═╡ 8349306c-e98c-4221-9a1b-322fde3e18cb +# Running this yourself? Point this at your own environment — +# we advise one shared project in the parent folder: Pkg.activate("..") +using Pkg; Pkg.activate("../../pluto-deployment-environment") + +# ╔═╡ d2c4d230-0943-11ef-3aad-5719e74bb20e +using Markdown, InteractiveUtils + +# ╔═╡ 83f3d978-aefa-40c1-a647-b26e837aeed6 +using OrdinaryDiffEq, Catalyst + +# ╔═╡ 5185d0eb-7392-4775-9336-3e0f9e1449ce +using Measurements + +# ╔═╡ 1c8ea6fe-650b-4c52-ba14-47efe3bd3e39 +using StatsPlots, PlutoUI; TableOfContents() + +# ╔═╡ 3eb3e651-afcf-41f8-a652-154a4f7ae07f +using Turing + +# ╔═╡ 241a8a65-c59f-44f1-be39-d5edd1321b49 +md""" +# Introduction to uncertainty analysis +""" + +# ╔═╡ 1f5e389e-e003-4e73-8f18-b5ad6340a912 +Catalyst.ModelingToolkit.NaNMath.log(x::Measurement) = Measurements.log(x) + # using `log` on a Measurement-type variable in the reaction rate of a Catalyst model will otherwise error in the current Catalyst version + +# ╔═╡ 0f518e36-bc96-4599-807e-728504e5ca7b +md""" +## Goal of this practicum +""" + +# ╔═╡ da3cdc89-b911-4af9-9d4a-526301cba581 +md""" +Parameter uncertainty plays a crucial role in shaping the behavior of output variables. Model equations describe how systems evolve, often incorporating parameters representing various aspects of the system's characteristics. However, these parameters are rarely known with absolute certainty and often carry inherent uncertainty due to measurement errors, variability in real-world conditions, or incomplete knowledge of the system. This uncertainty can propagate through the model equations, leading to uncertainties in the predicted outcomes. Consequently, understanding the influence of parameter uncertainty becomes essential for assessing the reliability and robustness of the model predictions, as well as for making informed decisions based on these predictions. Techniques such as sensitivity analysis and uncertainty quantification are employed to explore and quantify the impact of parameter uncertainty on the output variables, providing insights into the system's behavior and guiding the refinement of models for improved accuracy and reliability. +""" + +# ╔═╡ 78afaded-5a19-4386-aa76-7974977ea354 +md" +Uncertainty in model parameters manifests as variability in the predicted outcomes, resulting in error bars around the output variables. These error bars represent the range of potential values that the output variables could take due to the uncertainty in the parameters. As the uncertainty in parameters increases, the width of these error bars typically expands, reflecting the increased variability and unpredictability in the model's predictions. +" + +# ╔═╡ eae11742-14c6-4b1b-a939-232710dfa10e +md" +We will now compute the variability in the output variables replected as error bars assuming some uncertainty in the model parameters. To illustrate this concept, we first revisit the three simple models modelling the growth of grass. +" + +# ╔═╡ e5ab4490-6fd4-4b51-bfa7-1366438efefc +md""" +## Grass growth models +""" + +# ╔═╡ 3e2750e7-220a-47b2-b445-eb3315734dec +md""" +In this notebook, three different models will be used, each modelling the yield of grass in a grassland: + +- Logistic growth model: $\cfrac{dW}{dt} = \mu \left( 1 - \cfrac{W}{W_f} \right) W$ +- Exponential growth model: $\cfrac{dW}{dt} = \mu \left( W_f - W \right)$ +- Gompertz growth model: $\cfrac{dW}{dt} = \left( \mu - d \ln(W) \right) W$ + +with output $W$ the grass yield, and $W_f$, $\mu$ and $D$ parameters. The table below show some typical values for the parameters together with their uncertainties: + +| | $\mu$ | $W_f$ | $d$ | +|:----------- |:----------:|:-----------:|:------------:| +| Logistic | 0.07$\pm$0.02 | 10.0$\pm$0.15 | | +| Exponential | 0.02$\pm$0.01 | 10.0$\pm$0.15 | | +| Gompertz | 0.09$\pm$0.01 | | 0.040$\pm$0.002 | + +We will use an initial condition of $W_0 = 2.0$ for each and a simulation time of $100$ days. +""" + +# ╔═╡ c874a08c-7d82-4631-b611-c598dcaded09 +md""" +We will illustrate how to compute the error bars, due to model parameter uncertainty, in conjunction with the output variable simulation for the logistic model. The same will be left as exercises below for the exponential and gompertz models. + +**Important:** +- We will use consequently `_log`, `_exp` and `_gom` appended to relevant variables names in order to indicate their model origin **and** to prevent cell-disabling that occurs when using the same variables names in these Notebooks. +""" + +# ╔═╡ 87272d30-d95a-4b36-84ef-84bb9363c19e +md""" +### Uncertainty analysis of the logistic growth model +""" + +# ╔═╡ 9d3192bc-ee69-44a1-9273-06b8a55c60ef +md""" +We will start by modelling our system and simulating using the aforementioned parameter values and uncertainties, initial condition, and timespan. +""" + +# ╔═╡ 8acb7ea2-54ef-428b-bb4f-364377d2c38d +md""" +Implementation of the system: +""" + +# ╔═╡ 460d98ef-2177-49bf-87a4-35412f4183ed +growth_mod_log = @reaction_network begin + @species W(t)=2.0 + @parameters μ Wf + μ*(1-W/Wf), W --> 2W +end + +# ╔═╡ 38e0f746-1d04-46ba-81cc-21522b9ce6a4 +md""" +Convert the *reaction model* to check that we work with the correct differential equation: +""" + +# ╔═╡ 2538bd6e-027f-47fe-813c-db0a02586d2c +osys_log = convert(ODESystem, growth_mod_log) + +# ╔═╡ 72b53ba6-dd05-4f2d-a65e-52783e76a1e9 +md""" +In order to use uncertainties in the parameter values, we will need to load the package `Measurements` (see above). +""" + +# ╔═╡ 1a67db96-6973-446d-aa5b-253dd0008a73 +md""" +Setting initial conditions: +""" + +# ╔═╡ 048112b0-10f1-47c5-834f-c3851d3078e8 +u0_log = [:W => 2.0] + +# ╔═╡ 037cbf2b-dc0c-4b62-879b-aa702c964877 +md""" +Set the timespan for the simulation: +""" + +# ╔═╡ 40c1192c-bc76-4d3a-8144-dd76ba72fba2 +tspan = (0.0, 100.0) + +# ╔═╡ 0b5493eb-6ce6-43c1-9289-4c2e6f20d014 +md""" +To see the effect of the individual parameter uncertainties on the output variable, we will analyse this (only for didactical reasons), assuming different study cases: + +1. $\mu$ has a nominal value of $0.07$ and a standard deviation of $0.02$ while $W_f$ is exactly known and equal to $10.0$. +2. $W_f$ has a nominal value of $10.0$ and a standard deviation of $0.15$ while $\mu$ is exactly known and equal to $0.07$. +3. $\mu$ has a nominal value of $0.07$ and a standard deviation of $0.02$ and similarily $W_f$ has a nominal value of $10.0$ and a standard deviation of $0.15$. + +The last case study is the true case because, in reality, all uncertainties will contribute simultaneously. +""" + +# ╔═╡ b4c76f21-9839-4db5-b952-43869d3a1efe +md""" +### Case study 1 +""" + +# ╔═╡ 6ab73fc5-7315-4ec6-a3ee-9b5c689ce82e +md""" +We initialize a vector `parms1_uncert_log` with the parameter values and uncertainty (standard deviation) **only in the first parameter**: +""" + +# ╔═╡ b0f32f69-b6a2-4050-97e2-8018d07463d8 +parms1_uncert_log = [:μ => 0.07±0.02, :Wf => 10.0] + +# ╔═╡ c1a0702c-6fa5-47d7-bd72-6bfcbdeba93f +md" +We create the corresponding ODE problem and store it in `oprob1_uncert_log`: +" + +# ╔═╡ 2fc91add-8468-4402-bcd8-42da5544f612 +oprob1_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, parms1_uncert_log) + +# ╔═╡ 5fa951d3-23ee-4510-873c-8158df4f2faf +md""" +We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol1_uncert_log`: +""" + +# ╔═╡ d54635e8-1693-42bc-91f4-7e504a8661c7 +osol1_uncert_log = solve(oprob1_uncert_log, Tsit5(), saveat=2.0) + +# ╔═╡ 707c6023-49ae-4a13-a220-d827a76a352c +md""" +Plot the results (simulation of the output variable $W$ and uncertainty band): +""" + +# ╔═╡ 48e92497-f6b3-484c-bc5f-3ce69f4f1a91 +plot(osol1_uncert_log) + +# ╔═╡ 9b30b23b-6e92-49cf-bf93-e12a187fa2ba +md" +When thinking back of the sensitivity of $W$ to the parameter $\mu$, we saw that the corresponding sensitivity function had a maximum around $t=33\;s$. Looking at the above plot with error bars, we can see that de largest error bars (largest uncentrainty in the output variable) occurs at the timepoints where the sensitivity is strongest. +" + +# ╔═╡ 5dcb1ef0-d2a8-41fa-9592-2a86cf8364e6 +md""" +### Alternative: Monte Carlo uncertainty propagation +""" + +# ╔═╡ 0d883d09-1c92-4bb5-9bbf-3c87c856a5fa +md""" +In uncertainty propagation, we are interested in the effect of uncertainties (or errors) on the final output of the model. Monte Carlo simulations are a straightforward way for testing different parameters and checking the results of the outcomes. + +Below a Turing model is defined that samples values from a normal distribution with a 5% standard deviation. This is our Prior belief of how accurate the estimate of our intial parameters is. Remember that this is similar to what we did in practical 4. +""" + +# ╔═╡ 0e0dc268-faaf-462e-a980-9398c6015e02 +@model function logistic_deviation() + μ_dev ~ Normal(0.07, 0.0035) # 5% standard deviation + Wf_dev ~ Normal(10, 0.5) # 5% standard deviation + return solve(remake(oprob_log, p = [:Wf => Wf_dev, :μ => μ_dev]), saveat=0.5); +end + +# ╔═╡ e34f4b23-ac1b-4b88-b959-65085cef4b4f +md""" +Using the `sample()` function we can sample values from our distribution. +""" + +# ╔═╡ 738448dd-d1c9-4fdc-af5d-fad4d91adf38 +μ_model = logistic_deviation(); + +# ╔═╡ 51579212-dd33-42b9-a19e-221ab7630c1a +chain = sample(μ_model, Prior(), 500); + +# ╔═╡ 22bf3f50-1909-4a2c-820f-45b87f6bce14 +solutions = generated_quantities(μ_model, chain); + +# ╔═╡ 616ee1d4-1f56-4c4a-a658-9f9351949f59 +md""" +Finally, we can use theses perturbed values to model the effects of these small perturbations on the output of the model. +""" + +# ╔═╡ 6b4a862e-6fb6-40ba-9e3f-43d57f169994 +md""" +Firstly, solve the original system (Wf = 10 and μ = 0.07) and afterwards we can compare this with the outputs of the Monte Carlo simulations. +""" + +# ╔═╡ 62afc495-29c3-4baa-b45a-94b84149d15d +oprob_log = ODEProblem(growth_mod_log, u0_log,tspan, [:μ => 0.07, :Wf => 10]); + +# ╔═╡ 691a8b68-935e-4012-aa25-9cfdc323b1de +osol_log = solve(oprob_log, Tsit5(), saveat = 0.5); + +# ╔═╡ f49a0085-396f-469e-afb8-2b7884f9bb1b +md""" +Let's now plot the solution of the Monte Carlo experiments. +""" + +# ╔═╡ ff5fcd0a-a37a-46d5-8b4b-f3f7a9a457a3 +begin + p1 = plot(title="Monte Carlo of logistic growth") + for solution in solutions + plot!(solution, label = false, color =:gray, alpha = 0.4, + linestyle = :dot) + end + plot!(osol_log, label = "Original simulation", linewidth = 3) + p1 +end + +# ╔═╡ c0e72c96-23b6-4903-bd31-2978f6aebb47 +md""" +An advantage of using Monte Carlo over the error bars, is that the resulting graphs create a distribution from which we can calculate probabilities. We could now for example calculate what the average yield is after 50 days: +""" + +# ╔═╡ 988e9cd3-29a8-4fb1-a706-82d8356b9f20 +md""" +It is worth noting that you can obtain the solution at a specific time point by indexing it using parentheses `()`, as shown below. However, the result is still returned as a vector (indicated by the square brackets `[]`). To extract the actual value, you need to index the first element of that vector. +""" + +# ╔═╡ 980596b3-52e3-4f15-9678-f15f13f22688 +osol_log(50) # gives a vector with inside the value of W at time = 50 + +# ╔═╡ 851a7da1-ab7f-4a7a-ae1e-07a05c1e8052 +osol_log(50)[1] # This gives the value of W at time = 50 + +# ╔═╡ 8a9ec64f-2492-4f86-98d5-0e7c9512f5bc +x = [solution(50)[1] for solution in solutions]; + +# ╔═╡ 67f1348d-979c-42c4-a9d4-3031dbb5179a +md""" +Looking at the histogram, we observe that the yield is approximately normally distributed at 50 days, near the inflection point where logistic growth begins to plateau. This is intuitive, as both the carrying capacity $W_f$ and the growth rate μ are normally distributed, and at this point in the growth curve their combined influence on the yield remains approximately linear, preserving normality. Note, however, that this need not hold at other time points or for other models +""" + +# ╔═╡ d4b354f7-369a-497b-bc9d-68ab6cb5f2b4 +histogram(x, xlabel="W", ylabel="Count", title="Histogram of W at t=50") + +# ╔═╡ 6859b3f8-bd1c-47b7-a4e8-0c38501f5342 +p = mean(x) + +# ╔═╡ 63975327-5391-4de0-a538-4dcb503211b0 +md""" +What can you see on the resulting graph? What does this tell us about the uncertainty? +- missing +- missing +- missing +""" + +# ╔═╡ 86f8d937-c42a-47ff-a0f6-6cae9d637fcf +md""" +### Case study 2 +""" + +# ╔═╡ 0755ea1c-50a0-4c8a-9adc-be2d63948a10 +md""" +We initialize a vector `parms2_uncert_log` with the parameter values and uncertainty (standard deviation) **only in the second parameter**: +""" + +# ╔═╡ 7befd387-f3a6-4b34-90b0-5f04b5429252 +parms2_uncert_log = [:μ => 0.07, :Wf => 10.0±1.5] + +# ╔═╡ 9e969176-f197-482f-9e2e-8b6d89de8f03 +md""" +We create the corresponding ODE problem and store it in `oprob2_uncert_log`: +""" + +# ╔═╡ 50cc4ee1-6601-442c-bac7-4d23b07db87e +oprob2_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, parms2_uncert_log) + +# ╔═╡ 6d69f64e-8e3a-46e5-bb2d-bfb6f55e129e +md""" +We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol2_uncert_log`: +""" + +# ╔═╡ ee548df5-1334-47dc-9258-a25062de3f67 +osol2_uncert_log = solve(oprob2_uncert_log, Tsit5(), saveat=2.0) + +# ╔═╡ afdb7450-e883-477b-b9a2-8e8585d16c40 +md""" +Plot the results (simulation of the output variable $W$ and uncertainty band): +""" + +# ╔═╡ 25bfbf46-69b1-47eb-a473-788aad066d4a +plot(osol2_uncert_log) + +# ╔═╡ b8896997-ddec-4d4a-a7c4-0e1008089d1b +md""" +When thinking back to the sensitivity of $W$ to the parameter $W_f$, we saw that the corresponding sensitivity function was strongest in the tail of the curve around the steady state value. Looking at the above plot with error bars, we can see that the largest error bars (the largest uncertainty in the output variable) occur at the tail of the curve, where the sensitivity is strongest. +""" + +# ╔═╡ 5522a5c2-ff7e-4093-86d0-ffb879fcc6e8 +md" +### Case study 3 +" + +# ╔═╡ 5e3da6ca-656e-41ad-ba04-bf3d6b51939d +md""" +We initialize a vector `parms_uncert_log` with the parameter values and uncertainty (standard deviation) **in all parameters**: +""" + +# ╔═╡ 7be29c69-7fa0-4f41-80c9-f69aa01e8ea4 +parms_uncert_log = [:μ => 0.07±0.02, :Wf => 10.0±1.5] + +# ╔═╡ d48c378e-df1c-45f7-9c3a-c5e9640e25a5 +md""" +We create the corresponding ODE problem and store it in `oprob_uncert_log`: +""" + +# ╔═╡ f209f4fb-985c-4a9b-b4dd-d50cc3fae1cf +oprob_uncert_log = ODEProblem(growth_mod_log, u0_log, tspan, parms_uncert_log) + +# ╔═╡ de96646b-b752-4f72-ac78-2a503478bf50 +md""" +We solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert_log`: +""" + +# ╔═╡ 6b1a3c2a-0a88-41ad-8866-41a33da5cd90 +osol_uncert_log = solve(oprob_uncert_log, Tsit5(), saveat=2.0) + +# ╔═╡ 127ead82-2e2d-4c2e-9417-0490b6839514 +md""" +Plot the results (simulation of the output variable $W$ and uncertainty band): +""" + +# ╔═╡ a5c254ad-b629-4a9e-89e9-90460e84620c +plot(osol_uncert_log) + +# ╔═╡ c181d973-4fcc-4fbe-b37c-5f71dd33b0d2 +md""" +Now you have the combined effect of the uncertainty in the parameter $\mu$ as well as in the parameter $W_f$. +""" + +# ╔═╡ f4406d4e-d9f9-45fe-865c-405dbbb3127f +md""" +## Exercises +""" + +# ╔═╡ e9f49fbc-d877-4db7-b320-a2db964ecaaa +md""" +### Exercise 1 - Uncertainty analysis of the exponential growth model + +Perform an uncertainty analysis of the exponential growth model. Use the parameter uncertainties mentioned in the *Table* in the *Grass growth models* sections. +""" + +# ╔═╡ 5201995b-3298-4984-8839-dd25dc73a20f +md" +A possible *reaction network object* for the exponential growth model can be implemented as follows: +" + +# ╔═╡ 97bdfce1-a670-469a-b8a9-34e1d1964409 +growth_exp = @reaction_network begin + @species W(t)=2.0 + @parameters μ Wf + μ*Wf, 0 --> W + μ, W --> 0 +end + +# ╔═╡ 52991cca-0365-48fb-b157-62a71c5573ee +md" +The vector `u0_exp` with the initial condition is: +" + +# ╔═╡ f7241453-755e-466c-a963-a504bc9ea446 +u0_exp = [:W => 2.0] + +# ╔═╡ 11b81610-90b1-4d71-aeba-0bf33709dbda +md""" +Initialize a vector `parms_uncert_exp` with the parameter values and their uncertainties (standard deviation) **in all parameters**.\ +**Remark**: You can use the same variable and leave a single uncertainty if you want to see the effect of the uncertainty in only one parameter later on. +""" + +# ╔═╡ 1af01af7-f5ee-4029-94ff-418c2f9748f2 +# parms_uncert_exp = missing + +# ╔═╡ c25160d9-471a-414c-be99-10aa2efbb6d4 +md""" +Create the corresponding ODE problem and store it in `oprob_uncert_exp`: +""" + +# ╔═╡ 7fcec514-192b-4694-b149-14f5f87bae17 +# oprob_uncert_exp = missing + +# ╔═╡ bd120dbc-827a-499a-a11c-25423ec5f397 +md" +Solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert_exp`: +" + +# ╔═╡ 2dd58666-80ae-4346-ae15-34792eea2d25 +# osol_uncert_exp = missing + +# ╔═╡ 8059e00b-8e9c-4c11-a484-bea2a0bf155e +md""" +Plot the results (simulation of the output variable $W$ and uncertainty band): +""" + +# ╔═╡ a89677f3-602b-4ef4-8f1a-ab203584f8df +# missing + +# ╔═╡ 2a470290-bca7-459f-b4f8-a5ac9fc031ed +md""" +Draw your conclusions: + +- missing +""" + +# ╔═╡ ded3aa76-ba09-499e-8c2d-52ff81ccfe02 +md""" +### Exercise 2 - Uncertainty analysis of the exponential Gompertz model + +Perform an uncertainty analysis of the Gompertz growth model. Use the parameter uncertainties mentioned in the *Table* in the *Grass growth models* sections. +""" + +# ╔═╡ e245e5dd-bb11-455b-8de3-ae0ec1763399 +md" +A possible *reaction network object* for the Gompertz growth model can be implemented as follows: +" + +# ╔═╡ 3e16bd3a-898a-49f9-9ed1-8998be7dc045 +growth_gom = @reaction_network begin + @species W(t)=2.0 + @parameters μ d + μ-d*log(W), W --> 2W +end + +# ╔═╡ db2f002f-20b7-4b5c-8d59-85b089fb3e59 +md" +The vector `u0_gom` with the initial condition is: +" + +# ╔═╡ 22a0013b-04f9-4148-9344-10bee1def0cf +u0_gom = [:W => 2.0] + +# ╔═╡ f4fff573-6533-4ac3-897d-840a9aaf55f1 +md""" +Initialize a vector `parms_uncert_gom` with the parameter values and their uncertainties (standard deviation) **in all parameters**.\ +**Remark**: You can use the same variable and leave a single uncertainty if you want to see the effect of the uncertainty in only one parameter later on. +""" + +# ╔═╡ 5ab3aca2-e570-4802-815b-5614680b427d +# parms_uncert_gom = missing + +# ╔═╡ 890fe099-28ae-4e4f-aa1e-8d9ab6240130 +md""" +Create the corresponding ODE problem and store it in `oprob_uncert_log`: +""" + +# ╔═╡ 1aed2273-7b3e-4a49-a8d0-91251c58b700 +# oprob_uncert_gom = missing + +# ╔═╡ 76da6edd-9e9f-4f5b-8673-b737c8f9c5f8 +md" +Solve the ODE problem. Use `Tsit5()` and `saveat=2.0`. Store the solution in `osol_uncert_gom`: +" + +# ╔═╡ 8b10af49-e3d0-426e-9147-7666262d1c54 +# osol_uncert_gom = missing + +# ╔═╡ 6e8318bb-c884-4726-bb3d-9e0cfb7f40c1 +md""" +Plot the results (simulation of the output variable $W$ and uncertainty band): +""" + +# ╔═╡ 92785455-14d2-4adb-9731-8575527c5217 +# missing + +# ╔═╡ 9d11df31-57b2-4825-a9db-34b422d5f084 +md""" +Draw your conclusions: + +- missing +""" + +# ╔═╡ Cell order: +# ╟─241a8a65-c59f-44f1-be39-d5edd1321b49 +# ╠═d2c4d230-0943-11ef-3aad-5719e74bb20e +# ╠═8349306c-e98c-4221-9a1b-322fde3e18cb +# ╠═83f3d978-aefa-40c1-a647-b26e837aeed6 +# ╠═5185d0eb-7392-4775-9336-3e0f9e1449ce +# ╠═1c8ea6fe-650b-4c52-ba14-47efe3bd3e39 +# ╠═3eb3e651-afcf-41f8-a652-154a4f7ae07f +# ╟─1f5e389e-e003-4e73-8f18-b5ad6340a912 +# ╟─0f518e36-bc96-4599-807e-728504e5ca7b +# ╟─da3cdc89-b911-4af9-9d4a-526301cba581 +# ╟─78afaded-5a19-4386-aa76-7974977ea354 +# ╟─eae11742-14c6-4b1b-a939-232710dfa10e +# ╟─e5ab4490-6fd4-4b51-bfa7-1366438efefc +# ╟─3e2750e7-220a-47b2-b445-eb3315734dec +# ╟─c874a08c-7d82-4631-b611-c598dcaded09 +# ╟─87272d30-d95a-4b36-84ef-84bb9363c19e +# ╟─9d3192bc-ee69-44a1-9273-06b8a55c60ef +# ╟─8acb7ea2-54ef-428b-bb4f-364377d2c38d +# ╠═460d98ef-2177-49bf-87a4-35412f4183ed +# ╟─38e0f746-1d04-46ba-81cc-21522b9ce6a4 +# ╠═2538bd6e-027f-47fe-813c-db0a02586d2c +# ╟─72b53ba6-dd05-4f2d-a65e-52783e76a1e9 +# ╟─1a67db96-6973-446d-aa5b-253dd0008a73 +# ╠═048112b0-10f1-47c5-834f-c3851d3078e8 +# ╟─037cbf2b-dc0c-4b62-879b-aa702c964877 +# ╠═40c1192c-bc76-4d3a-8144-dd76ba72fba2 +# ╟─0b5493eb-6ce6-43c1-9289-4c2e6f20d014 +# ╟─b4c76f21-9839-4db5-b952-43869d3a1efe +# ╟─6ab73fc5-7315-4ec6-a3ee-9b5c689ce82e +# ╠═b0f32f69-b6a2-4050-97e2-8018d07463d8 +# ╟─c1a0702c-6fa5-47d7-bd72-6bfcbdeba93f +# ╠═2fc91add-8468-4402-bcd8-42da5544f612 +# ╟─5fa951d3-23ee-4510-873c-8158df4f2faf +# ╠═d54635e8-1693-42bc-91f4-7e504a8661c7 +# ╟─707c6023-49ae-4a13-a220-d827a76a352c +# ╠═48e92497-f6b3-484c-bc5f-3ce69f4f1a91 +# ╟─9b30b23b-6e92-49cf-bf93-e12a187fa2ba +# ╟─5dcb1ef0-d2a8-41fa-9592-2a86cf8364e6 +# ╟─0d883d09-1c92-4bb5-9bbf-3c87c856a5fa +# ╠═0e0dc268-faaf-462e-a980-9398c6015e02 +# ╟─e34f4b23-ac1b-4b88-b959-65085cef4b4f +# ╠═738448dd-d1c9-4fdc-af5d-fad4d91adf38 +# ╠═51579212-dd33-42b9-a19e-221ab7630c1a +# ╠═22bf3f50-1909-4a2c-820f-45b87f6bce14 +# ╟─616ee1d4-1f56-4c4a-a658-9f9351949f59 +# ╟─6b4a862e-6fb6-40ba-9e3f-43d57f169994 +# ╠═62afc495-29c3-4baa-b45a-94b84149d15d +# ╠═691a8b68-935e-4012-aa25-9cfdc323b1de +# ╟─f49a0085-396f-469e-afb8-2b7884f9bb1b +# ╠═ff5fcd0a-a37a-46d5-8b4b-f3f7a9a457a3 +# ╟─c0e72c96-23b6-4903-bd31-2978f6aebb47 +# ╟─988e9cd3-29a8-4fb1-a706-82d8356b9f20 +# ╠═980596b3-52e3-4f15-9678-f15f13f22688 +# ╠═851a7da1-ab7f-4a7a-ae1e-07a05c1e8052 +# ╠═8a9ec64f-2492-4f86-98d5-0e7c9512f5bc +# ╟─67f1348d-979c-42c4-a9d4-3031dbb5179a +# ╠═d4b354f7-369a-497b-bc9d-68ab6cb5f2b4 +# ╠═6859b3f8-bd1c-47b7-a4e8-0c38501f5342 +# ╟─63975327-5391-4de0-a538-4dcb503211b0 +# ╟─86f8d937-c42a-47ff-a0f6-6cae9d637fcf +# ╟─0755ea1c-50a0-4c8a-9adc-be2d63948a10 +# ╠═7befd387-f3a6-4b34-90b0-5f04b5429252 +# ╟─9e969176-f197-482f-9e2e-8b6d89de8f03 +# ╠═50cc4ee1-6601-442c-bac7-4d23b07db87e +# ╟─6d69f64e-8e3a-46e5-bb2d-bfb6f55e129e +# ╠═ee548df5-1334-47dc-9258-a25062de3f67 +# ╟─afdb7450-e883-477b-b9a2-8e8585d16c40 +# ╠═25bfbf46-69b1-47eb-a473-788aad066d4a +# ╟─b8896997-ddec-4d4a-a7c4-0e1008089d1b +# ╟─5522a5c2-ff7e-4093-86d0-ffb879fcc6e8 +# ╟─5e3da6ca-656e-41ad-ba04-bf3d6b51939d +# ╠═7be29c69-7fa0-4f41-80c9-f69aa01e8ea4 +# ╟─d48c378e-df1c-45f7-9c3a-c5e9640e25a5 +# ╠═f209f4fb-985c-4a9b-b4dd-d50cc3fae1cf +# ╟─de96646b-b752-4f72-ac78-2a503478bf50 +# ╠═6b1a3c2a-0a88-41ad-8866-41a33da5cd90 +# ╟─127ead82-2e2d-4c2e-9417-0490b6839514 +# ╠═a5c254ad-b629-4a9e-89e9-90460e84620c +# ╟─c181d973-4fcc-4fbe-b37c-5f71dd33b0d2 +# ╟─f4406d4e-d9f9-45fe-865c-405dbbb3127f +# ╟─e9f49fbc-d877-4db7-b320-a2db964ecaaa +# ╟─5201995b-3298-4984-8839-dd25dc73a20f +# ╠═97bdfce1-a670-469a-b8a9-34e1d1964409 +# ╟─52991cca-0365-48fb-b157-62a71c5573ee +# ╠═f7241453-755e-466c-a963-a504bc9ea446 +# ╟─11b81610-90b1-4d71-aeba-0bf33709dbda +# ╠═1af01af7-f5ee-4029-94ff-418c2f9748f2 +# ╟─c25160d9-471a-414c-be99-10aa2efbb6d4 +# ╠═7fcec514-192b-4694-b149-14f5f87bae17 +# ╟─bd120dbc-827a-499a-a11c-25423ec5f397 +# ╠═2dd58666-80ae-4346-ae15-34792eea2d25 +# ╟─8059e00b-8e9c-4c11-a484-bea2a0bf155e +# ╠═a89677f3-602b-4ef4-8f1a-ab203584f8df +# ╠═2a470290-bca7-459f-b4f8-a5ac9fc031ed +# ╟─ded3aa76-ba09-499e-8c2d-52ff81ccfe02 +# ╟─e245e5dd-bb11-455b-8de3-ae0ec1763399 +# ╠═3e16bd3a-898a-49f9-9ed1-8998be7dc045 +# ╟─db2f002f-20b7-4b5c-8d59-85b089fb3e59 +# ╠═22a0013b-04f9-4148-9344-10bee1def0cf +# ╟─f4fff573-6533-4ac3-897d-840a9aaf55f1 +# ╠═5ab3aca2-e570-4802-815b-5614680b427d +# ╟─890fe099-28ae-4e4f-aa1e-8d9ab6240130 +# ╠═1aed2273-7b3e-4a49-a8d0-91251c58b700 +# ╟─76da6edd-9e9f-4f5b-8673-b737c8f9c5f8 +# ╠═8b10af49-e3d0-426e-9147-7666262d1c54 +# ╟─6e8318bb-c884-4726-bb3d-9e0cfb7f40c1 +# ╠═92785455-14d2-4adb-9731-8575527c5217 +# ╠═9d11df31-57b2-4825-a9db-34b422d5f084